#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(void)
{
char c, t[4] = "RPS";
int i, p, r;
srand(time(NULL));
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
for(i = 1; i <= 10; i++)
{
printf("Round %d of 10\n", i);
printf("Put your hand out(R, P, S): ");
scanf("%c", &c);
while(getchar() != '\n');
switch(c)
{
case 'R':
p = 0;
break;
case 'P':
p = 1;
break;
case 'S':
p = 2;
break;
default:
printf("Nope!\n");
return 0;
}
sleep(1);
r = rand() % 3;
printf("You: %c Computer: %c\n", t[p], t[r]);
if((r - p + 1) % 3)
{
printf("Nope!\n");
return 0;
}
}
int fd = open("./flag", O_RDONLY);
char flag[64] = { 0, };
read(fd, flag, 64);
printf("Flag is %s\n", flag);
close(fd);
return 0;
}
내가 입력한 것과 srand()로 난수를 생성해서 나온 결과값으로 10번 연속 이겨야 하는 코드이다. Bruteforce가 가장 먼저 생각났지만, 1/3^10의 확률은 죽어도 안나올것이라는 생각이 먼저 들었다.
gdb로 확인해 보고 싶었는데, 이렇게 나오더라.

분명 main 함수는 C 코드에서 있는데, 왜 main의 어셈블리 언어를 볼 수가 없는 것인가...?
그래서 어쩔수 없이 C 코드를 계속 분석해야 겠다.
일단 rand함수를 공략해야 실마리가 보일 것 같아서 계속 들여다 보니, rand함수 안의 값이 Time(NULL)로 되어 있으니 현재 시간을 기준으로 난수를 생성한다는 것을 알 수 있다. 그러면 "만약 그 서버에서 사용하는 현재 시간을 가져 올 수 있다면, 똑같은 난수를 출력할 테니, 10번 다 이길 수 있는 방법이 생기지 않을까?" 했다
그래서 파이썬에서 rand함수를 사용할 수 있는 지 검색해보니, 많은 사람들이 이렇게 생각했나보다.
# windows
from ctypes import *
libc=CDLL("msvcrt")
libc.srand(seed)
libc.rand()
# linux
from ctypes import *
libc=CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(seed)
libc.rand()
서버에 연결한 다음 그 시간에 맞춰 시드를 생성하고, 난수를 생성하여 pwntools로 하면 되겠다.
이를 이용한 익스플로잇 코드는 아래와 같다.
from pwn import *
from ctypes import *
import time
# p = process("./chall")
p = remote("host1.dreamhack.games", 24427)
libc = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(libc.time(0))
def rockps(k):
if k==0:
return "R"
elif k==1:
return "P"
elif k==2:
return "S"
for i in range(10):
com = rockps(libc.rand()%3)
#print(f"COM : {com}")
if com == "P":
p.sendline(b"S")
elif com == "S":
p.sendline(b"R")
else:
p.sendline(b"P")
time.sleep(1)
p.interactive()
결과는 아래와 같다.
$ python3 rock_paper_cissor.py
[+] Opening connection to host1.dreamhack.games on port 24427: Done
[*] Switching to interactive mode
Round 1 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 2 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 3 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 4 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 5 of 10
Put your hand out(R, P, S): You: S Computer: P
Round 6 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 7 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 8 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 9 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 10 of 10
Put your hand out(R, P, S): You: R Computer: S
Flag is DH{***********************}
[*] Got EOF while reading in interactive
다양성을 생각해서 다른 사람들 풀이도 봤는데, 이런 풀이도 있었다. 도대체 이런 생각은 어떻게 하는지...
import subprocess
host = "host1.dreamhack.games"
port = 24427
for i in range(11):
subprocess.Popen([
'gnome-terminal', '--', 'bash', '-c', f'nc {host} {port}; exec bash'
])
11개의 프로세스를 동시에 열어서 같은 시간에 만들어졌기 때문에 각 라운드에서 나올 값들은 동등하게 나온다. 직접 해보니까 11개의 터미널이 열리면서 노가다성이긴 하지만 나오긴 한다.
rand함수의 특성을 파악 할 수 있고, 관련된 파이썬 라이브러리를 찾을 수 있는, 나중에 비슷한 문제가 나오면 풀 수 있는 좋은 문제였다.