Pwnable. Fd 문제 풀이. 2번.
문제를 보면 "엄마가 말씀하시길, 로그인 시스템에 기반을 둔 패스코드를 만들라고 했다."
무슨 뚱딴지 같은 소리인지 모르겠다... 즉 해석해보면 로그인 시스템을 기반으로 한 passcode 프로그램이다. 아무 에러없이 C코드가 컴파일이 되었고, 근데 컴파일에 경고가 있다라고 해석할 수 있었다.
바로 접속 후 디렉터리를 확인하고, C 코드를 확인했었다.
일단 되게 길다..........
천천히 살펴보자면, 대략 메인 함수 실행이 welcome() => login() 순으로 진행되는 것을 확인
할 수 있었다.
Welcome()함수를 보면, 100byte만큼의 char형 배열에 입력을 받는 부분이 존재한다.
그 다음 실행되는 login()함수를 보면 passcode1==338150, passcode2==13371337이라는 값이 되어야 system()함수에서 flag를 출력해주는 부분을 확인이 가능했다.
passcode1, passcode2를 좀 더 살펴보자면,
scanf("%d",passcode1); scanf("%d",passcode2); 라고 관련 코드가 존재한다.
이 코드들은 즉시 변수에 값을 저장시키는 코드가 아니라 scanf()에서 값을 입력받은 결과를
passcode1,passcode2 변수의 값에 해당하는 주소에 변수의 값을 저장시키는 코드다.
즉 입력 값을 scanf("%d", &passcode1); 형식에서 주는 것처럼 주게 되면, 쓰레기 값에 해당하는 주소에 입력 값을 입력하게 되어 아래 화면처럼 segmentation fault가 발생하게 된다.
passcode@ubuntu:~$ ./passcode Toddler's Secure Login System 1.0 beta. enter you name : a Welcome a! enter passcode1 : 338150 enter passcode2 : 13371337 Segmentation fault passcode@ubuntu:~$
이어서 말하자면 passcode1,passcode2 변수는 선언만 되어 있고, 초기화되는 부분은 없으니 일단 쓰레기 값이 들어가 있을 것이다. 그러면 쓰레기 값에 해당하는 주소의 위치에 값을 저장하게 되고, 사용자가 원하는 방식으로 할 수 없다.
즉 int a = \xbfbfbfbf; 라는 것은 a = \xbfbfbfbf로 초기화가 된다.
scanf("%d", &a); a라는 변수의 이름에 해당하는 주소를 참조해서 주소에 입력받은 값을 저장한다. 또한 scanf("%d",a); a(\xbfbfbfbf)라는 주소에 입력받은 값을 저장하게 된다.
일단 여러 방식을 고민해본 끝에, 그전에 호출된 welcome함수에서 passcode 1,2 변수에 영향을 주는 부분이라고 고민해봤고, 그대로 리버싱을 시도해봤다.
name 배열이 어느 주소 공간에 할당되었는지 확인을 해봤다.
먼저 0x0804860c <+3> = sub esp,0x88 부분을 보면 esp를 0x88만큼 빼서 그 만큼 더미나 변수를 사용해서 할당한다는 것을 확인했지만, 아직 잘 모르겠어서 좀 더 코드를 확인했다.
0x0804862f <+38> = lea edx, [ebp-0x70]을 보면 ebp-70이라는 스택에서의 주소를 edx레지스터에 담은 후 0x08048632 <+41> = mov DWORD PTR [esp+0x4], edx를 통해 이어서 이 값을 esp+4에 저장한다. 또한 0x08048636 <+45> = mov DWORD PTR [esp], eax를 통해 eax에
담긴 값 0x80487dd를 esp 위치에 저장하고 그 다음 바로 scanf()를 호출해주는 코드가 존재하는 것으로 봤을 때, 두 레지스터인 eax, edx가 scanf()의 전달인자로 스택에 들어가는 것을 알 수 있었다. 0x80487dd에는 %100s 문자열이 ebp-\x70는 name[100]는 배열의 시작 주소이고,
ebp-70 ~ ebp-c 까지가 name[100] 배열의 영역이라는 것을 확인 할 수 있었다.
즉 이 영역이 임의의 값을 쓸 수 있다는 공간이라는 것을 확인 할 수 있었다.
Login을 열어 봤다. scanf()의 전달 인자를 볼 때, ebp-0x10이 scanf()의 전달 인자로 있는 것을 봤을 때, passcode1이라는 변수가 ebp-10의 영역에 위치하고 있는 것을 확인했다.
다시 말해 welcome()함수와 연계해 생각해보면, welcome()에서 사용하는 name[100] 배열은
ebp-70부터 100바이트만큼 login()함수에서 사용하는 passcode1 변수는 ebp-10에서 4바이트
만큼의 영역에 접근한다. ebp-10 ~ ebp-c 부분까지 welcome()함수에서 임의의 값을 저장한 영역이라는 것을 알 수 있었고, 즉 login()함수의 passcode1 변수에는 welcome()의 name 배열 중 끝의 4바이트(ebp-10 ~ ebp-c까지)에 값이 저장된다는 것을 알 수 있었다.
즉 ebp-0x70, ebp-0x10 ==0x60 == 96.
결국 scanf()를 통해 welcome()의 name 배열 중 끝의 4바이트(ebp-10 ~ ebp-c)에 먼저 입력해놓은 값이 저장된 login()함수의 passcode1 변수의 값에 해당하는 위치에 임의의 값을 입력 가능하는 것을 확인했다. 끝의 4바이트는 name[97] ~ name[100] 부분에 저장된 값에 해당하는 주소에 scanf를 통해 임의의 주소를 입력할 수 있고, 즉 4바이트에 4바이트를 임의로 작성할 수 있다는 것이다.
Login 코드를 보면, system()함수의 전달인자로 “/bin/cat flag”를 실행하기 위해선 if 조건문의 조건식인 passcode1==338150 && passcode2 == 13371337로 만족해야 한다.
일단 passcode1은 값을 입력할 수 있어도, passcode2는 값을 입력할 수 없을 거 같았다.
4바이트만큼의 주소 공간에 특정 값을 덮어 씌우는 방법==즉 exploit를 해야한다는 사실을 인지하면, 주소==4바이트 è system(“/bin/cat flag”); 부분의 시작 주소==4바이트를 입력하면 안되나라는 생각도 들었다. 다시 말해 printf(“enter passcode1 : “);, scanf(“%d”, passcode1);
여기까지는 정상적으로 프로그램이 실행되어야 exploit가 가능하다는 것을 확인했다.
Cf)exploit == 특정 주소에 특정 값을 채워 넣는다.
그 아래 코드는 fflush(stdin); 이 부분은 코딩 할 때 입력 버퍼에 값을 남겨둔 다음 scanf()에서 이 버퍼에 있는 값을 빼서 사용하는 것, 즉 exploit를 실행 못하게 입력 버퍼를 지워버리는 부분이다. 만약에 fflush() 호출 부분에 해당하는 주소에 system(“/bin/cat flag”); 부분의 시작 주소를 덮어 씌울 수 있다면, fflush()가 호출되는 대신 system(“/bin/cat flag”); 부분부터 코드가 진행되어서 exploit 가능하다. Fflush()는 라이브러리 함수이고 시작 주소는 PLT&GOT을 알아야 한다.
cf)
PLT(Procedure Linkage Table) = 외부 프로세스를 연결해주는 테이블 PLT를 통해 다른 라이브러리에 있는 프로시저를 호출 가능하다.
GOT(Global Offset Table) = PLT가 참조하는 테이블. 프로시저의 주소가 내재.
PLT & GOT은 설명할 부분이 상당히 많기에 나중에 따로 언급하겠고, 구글링을 해보면 많은 내용들이 검색 가능하다.
일단 어느 정도 인지했다고 생각하고 PLT&GOT을 통해 exploit 해보겠다.
DL(Dynamic Link = 동적 링크)를 통해 만든 파일에서 fflush()가 호출되면, fflush()의 PLT에 적혀 있는 부분으로 이동하고, 거기에는 GOT가 있다. 함수가 이미 호출되어 있다면 GOT에 실제 함수의 주소가 적혀 있고, 함수가 처음 호출된 경우라면 GOT에는 위 문제의 PLT+6 부분에 해당하는 부분으로 이동해서 코드를 진행하고 실제 함수의 주소를 구할 수 있다.
즉 fflush()의 GOT에 적혀 있는 값을 system(“/bin/cat flag”); 부분으로 돌리면
exploit 할 수 있을거라는 것을 알 수 있다.
이제는 exploit를 위한 payload를 생성해봤다.
일단 4바이트 만큼의 주소에 특정 값을 덮어 씌워야 한다.
즉 위에서 봤던 name[97] ~ name[100] 주소에 값 입력 받는다.
fflush()의 GOT도 알아야 하고, system(“/bin/cat flag”); 이 시작되는 주소도 알아야 한다.
name[97] ~ name[100]은 위에서 설명했다.
fflush()의 GOT.
Login()에서 fflush()가 실행되는 부분을 따라가본다.
위 코드에서 fflush()가 호출되면, 0x8048430 부분인 fflush의 PLT를 참조한다.
0x8048430는 PLT부분이고, fflush의 PLT를 보기 위해 자세히 확인한다.
Fflush의 PLT에는 0x804a004부분으로 JUMP라는 것이 나온다.
즉 PLT이 참조하는 곳이니까 이 부분이 fflush()의 GOT.
위의 코드를 보면, 호출되기 전이니 GOT에 적혀 있는 0x8048436은 PLT+6 부분인 것을 확인 가능하다. 즉 fflush()의 GOT 주소 == 0x0804a004.
system(“/bin/cat flag”); 이 시작되는 주소가 어디인지 보면 login()을 열 수 있다.
일단 전달인자로 “/bin/cat flag”를 주고, system함수를 호출하는 코드가 이어지는 부분이 있는 것을 확인 가능했다.
위의 코드를 보면 +127를 보면 알 수 있다.
즉 system(“/bin/cat flag”); 시작되는 주소는 0x080485e3이다.
Exploit하기 위한 payload 정보를 다 구했다.
Name[97] ~ name[100] 주소에 값 입력받고, fflush()의 GOT 주소인 0x0804a004, system(“/bin/cat flag”);이 시작되는 주소 == > 0x080485e3 다 구했다.
Payload == (python -c ‘print “\x90\*96+\x04\xa0\x04\x08”+”134514147”’) |./passcode
134514147에 뭐냐면, 0x080485e3의 10진수 형태이고, scanf()에서 %d를 통해 10진 정수로 입력받기에 바꿔서 썼다.
passcode@ubuntu:~$ (python -c 'print "\x90"*96+"\x18\xa0\x04\x08"+"134514147"') | ./passcode Toddler's Secure Login System 1.0 beta. enter you name : Welcome ?????????????????????????????????????????????????????????????????? ????????????????????????????????????????! enter passcode1 : enter passcode2 : checking... Login Failed! Sorry mom.. I got confused about scanf usage :( Now I can safely trust you that you have credential :)
cf) 빡세다.................정말 연관된 개념도 많이 필요하고, 계속 복습해야겠다.
Exploit 설명. PLT & GOT 개념 및 설명.
이 방법 말고도 GOT의 readelf or objump를 통해 GOT 영역을 통해 문제를 구하는 등 여러 방법이 있겠지만, 아직은 이 방법도 빡세다;;.
'# Related site issues > PW.KR' 카테고리의 다른 글
Pwnable.flag. (0) | 2018.04.06 |
---|---|
Pwnable.bof. (0) | 2018.04.05 |
Pwnable.collision. (0) | 2018.04.05 |
Pwnable.UAF. (0) | 2018.04.05 |
Pwnable. Fd (0) | 2018.03.28 |