pwnable.kr 의 다섯 번째 문제 passcode 를 풀어보자.
ssh 로 접속해보자.
이전 문제들과 같이 세 개의 파일이 존재한다.
소스코드를 훑어보자.
우선 눈여겨 봐야 할 부분은 login() 함수 부분이다.
scanf() 로 정수를 입력받고 있다.
여기서 주의해야 할 부분이 있는데, passcode 변수에 정수값을 저장하는 것이 아니다.
우리는 흔히 scanf() 를 이용해 정수를 입력받을 때 다음과 같이 진행한다.
scanf("%d", &passcode1);
scanf() 함수에서 두 번째 인자에 해당하는 "&passcode1" 에 정수를 입력하라는 의미이다.
여기서 & (ampersand) 는 변수의 주소값을 가리킨다.
즉 두 번째 인자로 전달된 &passcode1 은 passcode1 변수의 주소값을 의미한다.
하지만 문제 소스코드에선 & 가 존재하지 않는다.
즉, scanf("%d", passcode1) 과 같은 코드에서
두 번째 인자로 전달된 passcode1 변수가 가지고 있는 값이 정수가 저장되는 주소가 된다.
하지만 위에서 passcode1 변수는 초기화되지 않았으므로, dummy 값이 들어있을 것이다.
예를 들어 passcode1 변수에 들어 있는 dummy 값이 0x12345678 이라면,
0x12345678 에 해당하는 주소값에 우리가 입력한 정수가 저장되게 된다.
이제 gdb 를 이용해 코드를 분석해보자.
먼저 welcome() 함수 부분을 살펴보자.
welcome() 함수에서는 name 이라는 이름의 100byte 짜리 배열을 생성하고, 입력받고 있다.
이 부분이 printf("enter you name : "); 함수 호출 후
name[100] 이 차지할 공간을 의미한다.
ebp 기준 -0x70 에 입력을 받고 있으니, 100byte 면 ebp-0xC 까지일것이다.
이제 login() 함수를 살펴보자.
위에서 login() 함수의 소스코드를 확인해보면,
printf() 함수 호출 후,
ebp-0x10 위치가 passcode1 변수의 위치임을 확인할 수 있다.
그 밑의 비교 구문에서도 확인할 수 있다.
if 문에서 passcode1 과 338150, passcode2 와 13371337 을 비교하고 있으니,
각각 ebp-0x10 이 passcode1, ebp-0xC 가 passcode2 이다.
방금 위에서 welcome() 함수 내에서 name[100] 배열의 범위가
ebp-0x70 에서 ebp-0xC 임을 확인했다.
name[100] 배열과 passcode1 의 위치를 확인해보았더니,
(ebp-0x70) - (ebp-0x10) = -0x60 즉, 96byte 차이인 것을 확인할 수 있다.
그 말은 name[100] 배열의 마지막 4byte 로 passcode1 의 값을 조작할 수 있다는 말이다.
이 문제에서 핵심은 두 가지인데, 여기가 첫 번째 핵심이다.
우리는 이 코드에서,
scanf() 로 입력받는 정수가 passcode1 변수에 저장되어 있는 값으로 저장된다.
위에서 언급했듯이, passcode1 변수에 어떠한 주소값을 저장한다면
그 주소값으로 정수가 저장된다는 의미이다.
그렇다면, name[100] 에 값을 입력할 때 마지막 4byte 에 무엇을 입력해야 할까?
밑에서 PLT 와 GOT 에 대한 얘기를 하고 나서 마저 알아보도록 하자.
이제 우리는 코드의 흐름을 바꿔 최종 목표인 system("/bin/cat flag") 를 호출하면 된다.
이제 눈여겨봐야 할 부분은 fflush() 함수이다.
출력버퍼를 비우는 함수인데, fflush(stdin) 으로 입력버퍼를 비우기도 한다.
(물론 이 방식은 잘못된 방식이다)
그럼 이제 PLT 와 GOT 에 대한 얘기를 해보자.
PLT 는 Procedure Linkage Table로, 외부 프로시저를 연결해주는 테이블이다.
외부 라이브러리를 연결해주는 역할이라고 간단히 생각하자.
GOT 는 Global Offset Table로, PLT 가 참조하는 테이블이다.
즉, 어떤 외부 라이브러리의 함수를 호출할 때 그 함수의 주소를 알아야 하기 때문에
PLT 를 참조한다. PLT 는 GOT 를 참조해서 실제 함수의 주소를 알게 되고,
최종적으로 그 함수에 찾아갈 수 있게된다.
자세한 내용은 다음에 한 번 글로 정리하도록 하고, 여기서는 이것만 알고 넘어가자.
그렇다면 fflush() 함수의 GOT 에 system() 함수가 시작되는 주소를 덮어씌울 수 있다면?
우리는 flag 를 얻을 수 있다.
이제 문제를 해결하는 핵심은 다 확인했고, 해결해보자.
과정은 이렇다.
name[100] 마지막 4byte 에 fflush() GOT 를 입력한다.
이후, login() 함수에서 scanf() 로 입력을 받을 텐데,
passcode1 변수가 가지고 있는 값(특정 주소) 에 입력을 받는다.
방금 우리는 passcode1 변수에 fflush() 함수의 GOT 를 이미 넣어놨다.
즉, 우리가 scanf() 로 입력하는 수가 fflush() 함수에 GOT 에 들어가게 된다.
이제 우리는 system() 함수가 시작하는 부분의 주소를 입력해주면 문제는 해결된다.
이제 fflush() 함수의 GOT 를 확인해보면 되겠다.
fflush() 함수 PLT의 정보를 확인해보자.
PLT 정보를 봤더니 0x804a004 주소로 점프뛰는 것을 볼 수 있다.
여기가 fflush() 함수의 GOT 주소일 것이다.
GOT 에는 또 다른 주소가 들어있다.
이제 우리는 이 fflush() 함수의 GOT 에 system() 함수의 시작 주소만 넣어주면 끝이다.
system() 함수에 인자를 전달해주는 부분이 보인다.
0x080485e3 주소를 GOT 에 덮어씌우자.
이제 페이로드를 작성해보자.
먼저 name[100] 마지막 4byte 에 fflush() GOT 주소를 넣어주자.
(python -c 'print "A"*96 + "\x04\xa0\x04\x08")
이후 system() 함수의 시작부분 주소를 넣어주자.
이 때, scanf() 함수에서 %d로 입력을 받고 있으니 10진수로 입력하자.
(python -c 'print "A"*96 + "\x04\xa0\x04\x08" + "134514147"')|./passcode
flag 획득 !