We participated in this 3 day long capture the flag (CTF) competition hosted by HackRush at IITGN and secured the 1st position. We had no previous experience of solving CTF challenges and had participated with the hope to know something about hacking. It is needless to say that this competition provided us the experience we needed.
We enjoyed solving the questions, although we had to spent lots of time going through the resources and understanding the questions. But after spending time and effort, when you come across a hex byte or a string that starts as "Hack", the feeling of exhilaration is extremely rewarding.
Problem | Category | Points | Flag |
---|---|---|---|
cliff | Binary Exploitation | 300 | HackRushCTF{N0w_Y0u_kn0w_ab0ut_form4t_5tr1ng5} |
simple_login | Binary Exploitation | 500 | - |
echo_back | Binary Exploitation | 1000 | - |
real_hack | Binary Exploitation | 1500 | - |
simple_check | Reverse Engineering | 200 | HackRushCTF{x86_f1r5t_t1me?} |
so_slooow | Reverse Engineering | 500 | - |
mixed_up | Reverse Engineering | 1000 | HackRushCTF{Gh1dr4_1s_Tru!y_4w3s0m3} |
Ancient | Cryptography | 50 | HackRushCTF{asoka} |
prime magic 1 | Cryptography | 100 | HackRushCTF{RSA_1s_c00l} |
prime magic 2 | Cryptography | 300 | HackRushCTF{10_1s_b3tt3r_th4n_2?} |
Double the trouble | Cryptography | 800 | HackRushCTF{7w1c3_1s_n0t_b3tt3r} |
-
Challenge
Have you heard about garbage in, garbage out? Connect to the actual challenge using:nc 3.142.26.175 12345
The C code and compiled binary are given.
Solution
After seeing the c code, we found out that, there is aflag
variable in main function. The main function is opening a txt file and reading it and storing its value inflag
. There is no way we could get the value of flag without the file, hence we can only get the flag variable by some exploitation on the sever end.Since the variable
input
which has data taken from user used inprintf
asprintf(input)
instead ofprintf("%s",input)
, there is a vulnerability in code which we can use.char input[512]; fgets(input, 512, stdin); printf(input);
We used format string vulnerability to obtain the hex values of the flag. As mentioned in this article,
printf
can leak information from the stack if we input%llx
. This gives the long long hex value from the stack. Using this information, we gave the input as a long string consisting of%llx
as follows -%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx
to the given server IP and port (3.142.26.175, 12345).
We then ran the above input multiple times and noticed that only some values repeated whereas other values changed on each execution. This meant that the values that were changing must be garbage values whereas the values that did not change must represent some actual information. We observed that value starting from
%10$llx
remained the same. Here, 10 means the 10th value returned by the long string of llx input as shown above. Converting the output hex of%10$llx
to ASCII we got “hsuRkcaH” as the ASCII value. This result motivated us and we felt that we were on the right track. We then fetched values uptill%15$llx
after which the output again changed on different iterations. On decoding the hex values obtained from%10$llx
to%15$llx
, and reversing each string, we got the desired flag.
FLAG: HackRushCTF{N0w_Y0u_kn0w_ab0ut_form4t_5tr1ng5}
-
Challenge
Check this out, you can test if your flag is valid or not!
The C code and compiled binary are given.
Solution
Since we had been provided the code (c file) of this problem, solving this became very easy.Manually find each character by finding its ascii value and converted to its character.
char flag[50] = "HackRushCTF{"; for(int i = 0; i < 12; i++) { if(flag[i] != input[i]) { fail(); } } // This part provides the intial part of the flag
if(input[12] != 120) { fail(); } // Throgh this part we know that 13th charecter is corresponding ascii value of 120 which is "x" // Similary we found out all the charecters ascii and corresponding charecter
FLAG: HackRushCTF{x86_f1r5t_t1m3?}
-
Challenge
What a terrible mess
Here is the compiled binary.
Solution
We have been provided only a binary file and nothing else. Solution of simple check question had become easy because we had a code file. So, we tried to figure out some ways through which we could reverse or decompile the binary file.First we tried to use the
strings
command to list out all the strings present in the binary file to see if the flag was stored as plaintext. This was not the case. So, we tried to analyse the binary file using tools like gdb and radare2.Using radare2, we made some important conclusion. First off, the
main
function called acheck_flag
function. Incheck_flag
, there seemed to be some sort of while loop that was run until the counter was incremented from 0 to 36. This could mean that the flag has a length of 36 which was confirmed later on. We made a few more deductions, but the assembly code was very complex to understand. After going through some online resources and this video, we came across a wonderful tool called Ghidra.Ghidra is an awesome tool that decompiles a binary file into a C file. Now, analysing the code was relatively easy. We found that the input to
check_flag
was first encrypted using themixup
function and then it was checked against an array calledflag
. Theflag
array contained hex values. On further investigation, we found that the input was reversed and then compared withflag
, meaning that we needed to reverseflag
in order to obtain the correct value. Also, the input was compared only with the corresponding multiples of 4 (0, 4, 8, and so on) in theflag
array. Thus, we had to decrypt the values present at multiples of 4 starting from index 0 and ending at index 140 ((36-1) * 4). On converting to ASCII and reversing, we captured the flag.
main
function of mixed_up decompiled using Ghidra
Decryption:
First, we copied the hex values stored in theflag
array to a python list. Then, we converted the hex to decimal values and removed the elements which were not at index that were multiples of 4. These values are given by theflag_values
array. Then, we used the code below to crack the flag.#include <stdio.h> int main() { int flag_values[] = {190, 204, 182, 12, 206, 204, 238, 44, 250, 158, 132, 174, 78, 42, 250, 206, 140, 250, 44, 78, 38, 140, 22, 226, 222, 98, 42, 194, 22, 206, 174, 74, 214, 198, 134, 18}; int local_14, param_1=1; int local_10; int local_c; for (int i = 0; i < 36; i++) { while (local_14 != flag_values[i]) // i is decimal value to corresponding character stored in FLAG array { local_14 = 0; local_10 = 0; while (local_10 < 4) { local_14 = local_14 | (1 << (local_10 & 31) & param_1) << ((local_10 *( -2) + 7 & 31) & 255); local_10 = local_10 + 1; } local_c = 4; while (local_c < 8) { local_14 = local_14 | (int)(1 << (local_c & 31) & param_1) >> (local_c * '\x02' - 7 & 31) & 0xffU; local_c = local_c + 1; } param_1++; } param_1--; printf("%d %c\n", local_14, (char)param_1); } return 0; }
PS: The flag truly lives up to its name :)
FLAG: HackRushCTF{Gh1dr4_1s_Tru!y_4w3s0m3}
-
Challenge
I found some wierd text, Can you find out what this means?
(Text is not included as it cannot be represented by markdown)Solution
After searching few letters it was easy to know that symbols belongs to brahmi scriptUsing above table we decrypted 𑀅 and 𑀓.
Then we found fb post, where it is mentioned 𑀅𑀰𑁄𑀓 = Aśōka.
FLAG: HackRushCTF{asoka}
-
Challenge
This should be simple.
The correct output is given at the end of the script in comments
Here is the attached python file.
Solution
After reading resource about RSA from this website, we concluded that we had to break thebig_num
into its prime factors.big_number = 25992347861099219061069221843214518860756327486173319027118759091795941826930677
We obtained the prime factors using this tool.
a = 3757160792909754673945392226295475594863 b = 6918082374901313855125397665325977135579
Doing lcm of (a-1) and (b-1) using thi site.
c = 12996173930549609530534610921607259430372826121502753979294844150952160187100118
Then using simple code we found out private key
for i in range(int(10e4)): z = (1+i*c)//exponent if (1+i*c)%exponent==0: d=z print(i,z,(1+i*c)%exponent) break // (Private key) d = 4143141461021605992362476555001609339438476832271190913368130656664837920397375
Then using this code we found out the hex of flag
message = pow(encrypted_flag,d,big_number) m = hex(message) print(m) // m = 0x4861636b527573684354467b5253415f31735f6330306c7d
Converting m to ascii we got the desire flag
FLAG: HackRushCTF{RSA_1s_c00l}
-
Challenge
The same challenge again?
Here is the attached python file.
Solution
We used the same tools as above to obtain the prime factors of thebig_num
. However, we were surprised that instead of only two prime factors,big_num
had multiple prime factors.big_num = 13269353506569762322866448443179444023604712744966341096534397703952746262066379915270
The prime factors of
big_num
area = 2 b = 3 c = 5 d = 7 e = 11 f = 13 g = 17 h = 3757160792909754673945392226295475594863 i = 6918082374901313855125397665325977135579
So, we read online about multi prime RSA cryptography from here and here. We used this tool to then obtain the decrypted flag.
FLAG: HackRushCTF{10_1s_b3tt3r_th4n_2?}
-
Challenge
Wow, double the security! No one can know the flag now!
Here is the attached python file.
Solution
We first proceeded by taking a for loop that was nested 6 times to find the last three bytes in both the keys.L = list(string.printable) print(len(L)) for i in L: for j in L: for k in L: for m in L: for n in L: for o in L: key1 = b"0"*29 + i.encode() + j.encode() + k.encode() key2 = b"0"*29 + m.encode() + n.encode() + o.encode()
This was followed by checking whether the key is correct and then decrypting. As the number of printable characters in ASCII is 100, the code had to go through a total of 100^6 iterations. This is clearly not feasible but still we kept the code running for quite some time. However, as expected we did not get any answer.
After searching for resources online, we edited the above code as follows:
import base64 from Crypto.Cipher import AES import hashlib from Crypto.Random import get_random_bytes import string import base64 test = b'testing 1..2..3!' encrypted_test = b'v8MshgtU1CfDNDuajMHzkQ==' flag = b'aUyXnj4SxFmYht39qIppFKIVDjQ/tTBbPwpSLo2IoHo=' encrypted_test = base64.b64decode(encrypted_test) flag = base64.b64decode(flag) iv = hashlib.md5(b"Goodluck!").digest() alphabet = string.printable key_base = '0'*29 # decrypting phase1 = {} data = encrypted_test for a in string.printable: for b in string.printable: for c in string.printable: key = key_base+a+b+c key = key.encode() cipher_decrypt = AES.new(key, AES.MODE_CBC, IV=iv) ciphertext = cipher_decrypt.decrypt(data) phase1[key] = ciphertext print('P1 done') # encrypting phase2 = {} data = test for a in string.printable: for b in string.printable: for c in string.printable: key = key_base+a+b+c key = key.encode() cipher_encrypt = AES.new(key, AES.MODE_CBC, IV=iv) ciphertext = cipher_encrypt.encrypt(data) phase2[key] = ciphertext print('P2 done') s1 = set(phase1.values()) s2 = set(phase2.values()) s3 = s1 & s2 match = s3.pop() for k,v in phase1.items(): if v == match: key1 = k print(f'Key1: {key1}') for k,v in phase2.items(): if v == match: key2 = k print(f'Key2: {key2}') # decrypt flag data = flag cipher_decrypt = AES.new(key1, AES.MODE_CBC, IV=iv) ciphertext = cipher_decrypt.decrypt(data) cipher_decrypt = AES.new(key2, AES.MODE_CBC, IV=iv) ciphertext = cipher_decrypt.decrypt(ciphertext) print(ciphertext.decode('utf-8'))
Source: https://vulndev.io/writeup/2019/12/28/ctf-2aes.html
The above algorithm is very clever as it encrypts with one key and decrypts with the other. Now, both must be equal and we can find the corresponding keys. Once we get the keys, we can find the flag.
This reduces the number of iterations from 100^6 to 2 * 100^3.
FLAG: HackRushCTF{7w1c3_1s_n0t_b3tt3r}