zj3t

email: euntaejang@gmail.com

Latest Posts

pwnable.kr -dragon p75

By 오후 5:52 ,



10점 이하들 문제만 풀어보다가 친구가 준 과제로 접해본 문제입니다. 약 1주일 정도....걸렸던 것 같아요. 3학년이라 시간도 많이없고 노트북 마져 고장난 상황이라서..더 오래걸렸던 것 같습니다.

어려웠습니다. 저한테는.....UAF에 대한 개념이 아직 안잡혀 있어서 그런가.........난이도가 좀 있는 문제였습니다.


게임을 다들 하셔서 알다시피 1번과 2번은 캐릭터를 선택하는 작업입니다.
3번은 소스를 보면 알겠지만 password를 입력해서 정답 유무에 따라 system() 함수를 할 수 있을지 없을지로 분기합니다.(ida로 까서 보세요)

하지만 password를 입력해서 dragon권한을 얻기에는 많이 힘들어 보입니다.


그렇다면 푸는 방법이 따로 있겠죠?? 답은 소스의 취약점에 있습니다.

소스에 대한 설명은 파일을 첨부하겠습니다. 
main함수부터 시작해서 중요한 함수들을 헥스레이해서 간단한 주석을 달아놨습니다.


일단 main 함수인데요 ida에서 F5를 눌러주면 어셈블리를 보기좋은 C코드로 바꿔줍니다.
정말 대단한 기능이죠.....




main함수에서 호출하는 함수들 중 가장 중요한 함수는 누가 뭐라해도 FightDragon() 함수 입니다.

ida 어셈블리로 대충보니 0x10만큼씩 malloc으로 동적할당을 해주는 것을 볼 수가 있었습니다.

그럼 자세히 보기 위해서 C코드로 바꿔보았습니다.




자!!!! C코드로 깔끔하게 바꿔주니 훨~씬 보기가 쉬워졌습니다.
흠...게임을 몇번 실행하다가 봐서 그런지 첫 번째 if ~ else 문은 Dragon의 정보를 만드는 구조체인것 같아요

첫번째는 80이 있는 걸 보니 MaMa Dragon 이고 50이 있는걸 보니 Baby Dragon 같습니다.

그리고 그 밑 if ~ else 문은 프리스트와 기사 즉, 사용자의 캐릭터에 대한 정보를 만드는 구조체 인거 같아요

마지막 if ~ else 문은 선택한 캐릭터로 싸우는 함수의 리턴값입니다.


이 부분을 좀더 세밀하게 보면

if ( v3 )
{
puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}
else
{
puts("\nYou Have Been Defeated!");
}
 
v30이 라면 드래곤에게 당했다는 메시지를 출력해주고
v30이아닌 값이라면 새로운 동적할당을 v2 변수에 해주고 그 값에 어떠한 값을 입력받습니다.
 
v3에 어ᄄᅠᇂ게 값이 입력되는지 보도록 하겠습니다.
 
v3 = PriestAttack((int)ptr, v5);
 
첫 번째 매개변수에 Priest의 정보두 번째 매개변수에 드래곤의 정보를 넣고 함수를 호출합니다.
그리고 이 함수의 리턴 값을 v3에 저장합니다.
 
 
함수를 보도록 하겠습니다.
int __cdecl PriestAttack(int a1, void *ptr)
{
.....
.....
.....
if ( *(_DWORD *)(a1 + 4) <= 0 )
{
free(ptr);
return 0;
}
}
while ( *((_BYTE *)ptr + 8) > 0 );
free(ptr);
return 1;
}
 
쉽게 말해 플레이어가 죽으면 리턴 값이 0 이고, 드래곤을 죽이면 리턴 값이 1입니다.
 
그리고 리턴값이 1이면
 
puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}
 
이러한 코드가 실행이 됩니다.

그럼 v5에서 free한 메모리에 v2에서 입력받은 것이 쓰여져서
((void (__cdecl *)(_DWORD *))*v5)(v5);를 호출하게 됩니다.
 
그렇다면 v2에서 scanf에서 값을 써준 값을 call하게 되는 것입니다.
 
그럼 v2에는 무엇을 써주어야 할ᄁᆞ요?? 제생각에는
 
.text:08048DBF mov dword ptr [esp], offset command ; "/bin/sh"
.text:08048DC6 call _system
 
, 08048DBF로 호출 방식을 바꿔준다면 쉘을 얻을 수 있을 것 같습니다.


하지만 이는 Dragon을 죽였을 때의 이야기 입니다. 
우리는 드래곤을 죽여야 이과 같이 함수의 호출 방식을 바꿔줄 수가 있습니다.

그럼 어떻게 죽여야 할까요??

답은 FightDragon() 함수에 있습니다. 게임을 해본 분들이라면 아시다시피 정상적인 방법으로는 Dragon을 잡을 수가 없습니다.

하지만 답은 여기있습니다.
(한참을 해맸습니다. 죽이기위해....죽이는 방법이 어딘가 숨어있을 것이라 생각은했지만.....아주 자세히 분석을 해야만 찾을 수 있는 곳!!!)



이 부분!!! 조금더 크게보면


 이 부분입니다.

이곳
 
*((_BYTE *)v5 + 8) = 80;
*((_BYTE *)v5 + 9) = 4;
 
부분입니다. v5+8Mama dragon의 체력 부분입니다.
그 밑 v5+9는 매 턴이 끝난뒤 체력이 4씩 회복되는 부분입니다.
 
여기서 주목할 만한 점은 mama dragon의 체력을 1바이트로 표헌한다는 점입니다.
따라서 0부터 127까지의 체력을 가질 수가 있습니다.
 
여기서 이해를 하기위해선 자료형 char을 생각해보면 쉬울 겁니다.
1바이트를 부호를 표시하고 나머지 7바이트로만 값을 표현합니다. 즉 범위가 128~127 까지 가질 수 있는데 여기서는 양수인 0부터 127의 체력을 가집니다.
 
그렇다면 우리는 생각해 볼 수가 있습니다.
 
플레이어가 죽기전에 드래곤의 체력을 128이상으로 만들 수 있다면..??
mama 드래곤의 체력은 80입니다. 그리고 매 턴마다 4씩 체력을 회복합니다.
 
만약 플레이어가 죽지 않고 드래곤의 체력이 점점 쌓이도록 한다면
80 84 88 92 96 100 104 .... 124에서 128이 되면서 체력이 0이되면서 죽게 된다는 가설을 한번 세워 볼 수가 있습니다.

한번 위의 가설을 진리로 만들어 보겠습니다!!!



어?? 드래곤이 잡혔습니다. 이제 scanf로 입력 할 수 있는 부분이 나옵니다.
이 부분을 아까 말한
.text:08048DBF mov dword ptr [esp], offset command ; "/bin/sh"
.text:08048DC6 call _system

이 부분으로 돌려줄 수 있다면 우리는 

puts("Well Done Hero! You Killed The Dragon!");
puts("The World Will Remember You As:");
v2 = malloc(0x10u);
__isoc99_scanf("%16s", v2);
puts("And The Dragon You Have Defeated Was Called:");
((void (__cdecl *)(_DWORD *))*v5)(v5);
}

이 부분에서 v5를 호출할 때(v5는 드래곤이 없어지면서 free되었었슴) UAF의 취약점에 따라 free된 메모리(v5)에 v2에 입력한 값이 써지게 되고 그에따라 v5을 호출 할때 v2에서 쓰인 주소가 call이 된다는 것입니다. (중요!!!!!!!!!!)

이제 저 상태에서 값을 써주어야 합니다.

하지만....
08048DBFint로 변환시켜서 입력했는데도 안되고...
\xbf\x8d\x04\x08 을 입력해도 안되고 해서

gdb로 까봤습니다.




eax는 함수가 v5를 호출할 때(v2로 쓴부분) 호출되는 주소가 담긴 레지스터입니다.

맨처음에는 0x00000000이 들어있다가
드래곤을 잡고 AAAA를 입력했더니

0x41414141이 들어가 있는 것을 확인할 수가있습니다.

따라서 저 0x41414141을 호출하게 됩니다. (AAAA를 넣었을 때 )

하지만 우리가 입력해야할 주소는 문자로 표현할 수 없는, 즉 , 아스키코드 범위 밖입니다.
따라서 스크립트를 작성해서 풀어야합니다.



원래 socket라이브러리를 이용해서 풀려했는데....안풀려서 pwntool이라는 라이브러리를 이용했습니다.

이 라이브러리를 처음써봐서 함수의 용도만 찾아서 썼는데 (공부를 더 열심해 해야할 것같아요)

스크립트의 내용은 위에서 다 설명한 내용임으로 생략하겠습니다.


실행시켜 보겠습니다.





휴 성공했습니다.


https://drive.google.com/folderview?id=0B9mSiiTEByfodGtpTVZZTml2bUk&usp=sharing

C 소스& 주석 첨부합니다.

질문이나 지적 감사하겠습니다.

You Might Also Like

0 개의 댓글