LA CTF 2024
On this page
This is the second time I have participated in the LA CTF 2024 event
https://ctftime.org/event/2102
CHALL’S SOLVED
Category | Challenge |
---|---|
CRYPTOGRAPHY | very-hot |
REVERSE ENGINEERING | shattered-memories |
REVERSE ENGINEERING | aplet321 |
very-hot
Description
Author: Red Guy
I didn’t think that using two primes for my RSA was sexy enough, so I used three.
Provided chall source src.py
1from Crypto.Util.number import getPrime, isPrime, bytes_to_long
2from flag import FLAG
3
4FLAG = bytes_to_long(FLAG.encode())
5
6p = getPrime(384)
7while(not isPrime(p + 6) or not isPrime(p + 12)):
8 p = getPrime(384)
9q = p + 6
10r = p + 12
11
12n = p * q * r
13e = 2**16 + 1
14ct = pow(FLAG, e, n)
15
16print(f'n: {n}')
17print(f'e: {e}')
18print(f'ct: {ct}')
And out.txt
n: 10565111742779621369865244442986012561396692673454910362609046015925986143478477636135123823568238799221073736640238782018226118947815621060733362956285282617024125831451239252829020159808921127494956720795643829784184023834660903398677823590748068165468077222708643934113813031996923649853965683973247210221430589980477793099978524923475037870799
e: 65537
ct: 9953835612864168958493881125012168733523409382351354854632430461608351532481509658102591265243759698363517384998445400450605072899351246319609602750009384658165461577933077010367041079697256427873608015844538854795998933587082438951814536702595878846142644494615211280580559681850168231137824062612646010487818329823551577905707110039178482377985
The provided Python script is implementing a basic RSA encryption scheme with a twist. It generates a prime number p
and ensures that p+6
and p+12
are also prime, assigning these to q
and r
respectively. The product of p
, q
, and r
forms the RSA modulus n
. The public exponent e
is set to 2^16 + 1
, a common choice in RSA systems. The script then converts a secret flag into a long integer and encrypts it using RSA, resulting in the ciphertext ct
. The output file contains the values of n
, e
, and ct
, which are part of the public key and the encrypted message in the RSA encryption scheme.
Final solver.py
1from sympy import nextprime, root
2from Crypto.Util.number import long_to_bytes
3
4def factorize(n):
5 p = nextprime(int(root(n, 3)))
6 while n % p != 0:
7 p = nextprime(p)
8 n //= p
9 q = nextprime(p)
10 while n % q != 0:
11 q = nextprime(q)
12 r = n // q
13 return p, q, r
14
15def decrypt(ct, p, q, r, e):
16 phi = (p - 1) * (q - 1) * (r - 1)
17 d = pow(e, -1, phi)
18 return pow(ct, d, n)
19
20n = 10565111742779621369865244442986012561396692673454910362609046015925986143478477636135123823568238799221073736640238782018226118947815621060733362956285282617024125831451239252829020159808921127494956720795643829784184023834660903398677823590748068165468077222708643934113813031996923649853965683973247210221430589980477793099978524923475037870799
21e = 65537
22ct = 9953835612864168958493881125012168733523409382351354854632430461608351532481509658102591265243759698363517384998445400450605072899351246319609602750009384658165461577933077010367041079697256427873608015844538854795998933587082438951814536702595878846142644494615211280580559681850168231137824062612646010487818329823551577905707110039178482377985
23
24p, q, r = factorize(n)
25flag = decrypt(ct, p, q, r, e)
26print(long_to_bytes(flag))
To decrypt an RSA-encrypted message. It first factorizes the RSA modulus n
into its prime factors p
, q
, and r
using a function called factorize
. This function exploits the fact that p
, q
, and r
are close together, which allows for efficient factorization. Then uses these factors to compute the RSA private key and decrypt the ciphertext ct
using a function called decrypt
. The decrypted message is converted back to bytes and printed out, revealing the original message.
$ python3 solver.py
b'lactf{th4t_w45_n0t_so_53xY}'
FLAG
lactf{th4t_w45_n0t_so_53xY}
shattered-memories
Description
Author: aplet123
I swear I knew what the flag was but I can’t seem to remember it anymore… can you dig it out from my inner psyche?
Provided executable binary file ./shattered-memories
And here’s the decompiled of the main function
undefined8 main(void)
{
int iVar1;
size_t sVar2;
undefined8 uVar3;
char local_98 [8];
char acStack_90 [8];
char acStack_88 [8];
char acStack_80 [8];
char acStack_78 [108];
int local_c;
puts("What was the flag again?");
fgets(local_98,0x80,stdin);
strip_newline(local_98);
sVar2 = strlen(local_98);
if (sVar2 == 0x28) {
local_c = 0;
iVar1 = strncmp(acStack_90,"t_what_f",8);
local_c = local_c + (uint)(iVar1 == 0);
iVar1 = strncmp(acStack_78,"t_means}",8);
local_c = local_c + (uint)(iVar1 == 0);
iVar1 = strncmp(acStack_80,"nd_forge",8);
local_c = local_c + (uint)(iVar1 == 0);
iVar1 = strncmp(local_98,"lactf{no",8);
local_c = local_c + (uint)(iVar1 == 0);
iVar1 = strncmp(acStack_88,"orgive_a",8);
local_c = local_c + (uint)(iVar1 == 0);
switch(local_c) {
case 0:
puts("No, that definitely isn\'t it.");
uVar3 = 1;
break;
case 1:
puts("I\'m pretty sure that isn\'t it.");
uVar3 = 1;
break;
case 2:
puts("I don\'t think that\'s it...");
uVar3 = 1;
break;
case 3:
puts("I think it\'s something like that but not quite...");
uVar3 = 1;
break;
case 4:
puts("There\'s something so slightly off but I can\'t quite put my finger on it...");
uVar3 = 1;
break;
case 5:
puts("Yes! That\'s it! That\'s the flag! I remember now!");
uVar3 = 0;
break;
default:
uVar3 = 0;
}
}
else {
puts("No, I definitely remember it being a different length...");
uVar3 = 1;
}
return uVar3;
}
The function first prompts the user for input. It then checks if the length of the input is 40 characters (0x28 in hexadecimal). If the length is correct, it compares segments of the input with predefined strings. If the length is not correct will returns 1. The function seems to be checking if the input matches a specific format or pattern. The correct input would likely result in the function returning 0.
Base on strcmp
they start with the strings t_what_f
, t_means}
, nd_forge
, lactf{no
, and orgive_a
. Put into correct order.
$ ./shattered-memories
What was the flag again?
lactf{not_what_forgive_and_forget_means}
Yes! That's it! That's the flag! I remember now!
FLAG
lactf{not_what_forgive_and_forget_means}
aplet321
Description
Author: kaiphait
Unlike Aplet123, Aplet321 might give you the flag if you beg him enough.
Provided executable binary file ./aplet321
And here’s the decompiled of the main function
undefined8 main(void)
{
int iVar1;
size_t sVar2;
char *pcVar3;
int iVar4;
int iVar5;
char local_238;
char acStack_237 [519];
setbuf(stdout,(char *)0x0);
puts("hi, i\'m aplet321. how can i help?");
fgets(&local_238,0x200,stdin);
sVar2 = strlen(&local_238);
if (5 < sVar2) {
iVar4 = 0;
iVar5 = 0;
pcVar3 = &local_238;
do {
iVar1 = strncmp(pcVar3,"pretty",6);
iVar5 = iVar5 + (uint)(iVar1 == 0);
iVar1 = strncmp(pcVar3,"please",6);
iVar4 = iVar4 + (uint)(iVar1 == 0);
pcVar3 = pcVar3 + 1;
} while (pcVar3 != acStack_237 + ((int)sVar2 - 6));
if (iVar4 != 0) {
pcVar3 = strstr(&local_238,"flag");
if (pcVar3 == (char *)0x0) {
puts("sorry, i didn\'t understand what you mean");
return 0;
}
if ((iVar5 + iVar4 == 0x36) && (iVar5 - iVar4 == -0x18)) {
puts("ok here\'s your flag");
system("cat flag.txt");
return 0;
}
puts("sorry, i\'m not allowed to do that");
return 0;
}
}
puts("so rude");
return 0;
}
It first prints a greeting message, then reads a line of input from the user. If the input length is more than 5 characters, it checks for the occurrence of the words “pretty” and “please” in the input. If the word “please” is found, it further checks for the word “flag”. If “flag” is also found and the sum of the occurrences of “pretty” and “please” equals 54 (0x36
in hexadecimal) and the difference between the occurrences of “pretty” and “please” equals -24 (-0x18
in hexadecimal), it prints a message and executes the command cat flag.txt
. If these conditions are not met, it prints a refusal message. If the input length is 5 characters or less, it prints a message indicating that the user is being rude. The function returns 0 before it ends.
Final solver.py
1from pwn import *
2
3def exploit(io):
4 io.recvuntil(b"how can i help?")
5
6 payload = b"please" * 39
7 payload += b"pretty" * 15
8 payload += b"flag"
9 io.sendline(payload)
10
11 # output = io.recvall()
12 # print(output)
13 io.interactive()
14
15if __name__ == "__main__":
16 context.update(log_level='debug')
17 try:
18 io = remote('chall.lac.tf', 31321)
19 exploit(io)
20 except:
21 io = process('./aplet321')
22 exploit(io)
In order to exploit. The payload consists of the word “please” repeated 39 times, “pretty” repeated 15 times, and “flag”. The sum of the occurrences of “pretty” and “please” equals 54 (0x36
in hexadecimal) and the difference between the occurrences of “pretty” and “please” equals -24 (-0x18
in hexadecimal). After sending the payload.
$ python3 solver.py
[+] Opening connection to chall.lac.tf on port 31321: Done
[DEBUG] Received 0x22 bytes:
b"hi, i'm aplet321. how can i help?\n"
[DEBUG] Sent 0x149 bytes:
b'pleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyflag\n'
[*] Switching to interactive mode
[DEBUG] Received 0x14 bytes:
b"ok here's your flag\n"
ok here's your flag
[DEBUG] Received 0x35 bytes:
b"lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}\n"
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to chall.lac.tf port 31321
FLAG
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}