Dynamic Secrets
Challenge
- CTF: Hack The Boo 2023 - Practice Official Writeup
- Name: Dynamic Secrets
- Category: Reversing
- Difficulty: Very Easy
- Points: 400
- Description: When I put this file in Ghidra the flag comparison looks like gibberish, maybe it is doing something at runtime?
- Objective: Dynamic Analysis, XOR, Runtime Decryption
Files
Download: rev_dynamicsecrets.zip
Writeup
The challenge contains a single file of dynamic_secrets
. Analyzing using the file
utility, we see its a Linux ELF binary.
1
2
$ file dynamic_secrets
dynamic_secrets: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7cb1d1190f842ea429f16f589d9d8a8834751d9b, for GNU/Linux 3.2.0, not stripped
Running the binary, we are shown it just spits out an unhappy face :(
. However, when sending an argument it says Wrong Password!
.
1
2
3
4
./dynamic_secrets
:(
./dynamic_secrets test
Wrong Password!
We can use dogbolt or ghidra to reverse the newly binary back to C as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int32_t main(int32_t argc, char** argv, char** envp)
{
..[snip]..
if ( verifyArgc(argc) )
{
decrypt(&verifyPasswordLength, main);
..[snip]..
_BYTE *__fastcall decrypt(_BYTE *a1, _BYTE *a2)
{
_BYTE *result; // rax
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = a1;
if ( a1 == a2 )
break;
*a1++ ^= i;
}
return result;
}
Looking at the main function, it calls the decrypt()
function with the addresses of verifyPasswordLength
and main
. The decryption starts at the address of the verifyPasswordLength
function (0x11c6) and ends at the address of the main
function (0x1759).
The following below is the IDA view showing both functions:
1
2
3
4
5
6
7
8
9
10
11
12
.text:00000000000011C6 public verifyPasswordLength
.text:00000000000011C6 verifyPasswordLength dw 3827 ; CODE XREF: main+5D↓p
.text:00000000000011C6 ; DATA XREF: main+45↓o
...[snip]...
.text:0000000000001759
.text:0000000000001759 ; =============== S U B R O U T I N E =======================================
.text:0000000000001759
.text:0000000000001759 ; Attributes: bp-based frame
.text:0000000000001759
.text:0000000000001759 ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:0000000000001759 public main
.text:0000000000001759 main proc near ; DATA XREF: _start+21↑o
Decryption was done on the addresses discussed above using Python to help with further analysis.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Read input file as bytes
f = open("dynamic_secrets", "rb")
data = list(f.read())
f.close()
# Start/End addresses
start = 0x11c6 # verifyPasswordLength()
end = 0x1759 # main()
# Decryption - Equivalent of C decrypt() function
i = 0
while start != end:
data[start] = data[start] ^ (i&0xff)
start += 1
i+=1
# Write out new binary
f = open("decrypted", "wb")
f.write(bytes(data))
f.close()
1
$ python3 decrypt_binary.py
Post-Decryption
We can use dogbolt or ghidra to reverse the newly decrypted binary back to C as shown below:
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
uint64_t verifyPasswordLength(int32_t arg1, void* arg2)
{
int32_t var_c = arg1;
uint64_t rax_3;
rax_3 = strlen(*(arg2 + 8)) == 0x2d;
return rax_3;
}
uint64_t verifyPasswordPart1(int32_t arg1, void* arg2)
{
int32_t var_1c = arg1;
int32_t var_c_3 = ((((0 | **(arg2 + 8) != 0x48) | *(*(arg2 + 8) + 1) != 0x54) | *(*(arg2 + 8) + 2) != 0x42) | *(*(arg2 + 8) + 3) != 0x7b);
int32_t var_c_6 = (((var_c_3 | *(*(arg2 + 8) + 4) != 0x54) | *(*(arg2 + 8) + 5) != 0x48) | *(*(arg2 + 8) + 6) != 0x33);
int32_t var_c_9 = (((var_c_6 | *(*(arg2 + 8) + 7) != 0x5f) | *(*(arg2 + 8) + 8) != 0x53) | *(*(arg2 + 8) + 9) != 0x33);
int32_t var_c_12 = (((var_c_9 | *(*(arg2 + 8) + 0xa) != 0x43) | *(*(arg2 + 8) + 0xb) != 0x52) | *(*(arg2 + 8) + 0xc) != 0x33);
int32_t var_c_15 = (((var_c_12 | *(*(arg2 + 8) + 0xd) != 0x54) | *(*(arg2 + 8) + 0xe) != 0x53) | *(*(arg2 + 8) + 0xf) != 0x5f);
int32_t var_c_18 = (((var_c_15 | *(*(arg2 + 8) + 0x10) != 0x34) | *(*(arg2 + 8) + 0x11) != 0x52) | *(*(arg2 + 8) + 0x12) != 0x33);
int32_t var_c_21 = (((var_c_18 | *(*(arg2 + 8) + 0x13) != 0x5f) | *(*(arg2 + 8) + 0x14) != 0x52) | *(*(arg2 + 8) + 0x15) != 0x33);
int32_t rax_132;
rax_132 = (var_c_21 & 1) == 0;
return rax_132;
}
uint64_t verifyPasswordPart2(int32_t arg1, void* arg2)
{
int32_t var_1c = arg1;
int32_t var_c_2 = (((0 | *(*(arg2 + 8) + 0x16) != 0x56) | *(*(arg2 + 8) + 0x17) != 0x33) | *(*(arg2 + 8) + 0x18) != 0x34);
int32_t var_c_5 = (((var_c_2 | *(*(arg2 + 8) + 0x19) != 0x4c) | *(*(arg2 + 8) + 0x1a) != 0x33) | *(*(arg2 + 8) + 0x1b) != 0x44);
int32_t var_c_8 = (((var_c_5 | *(*(arg2 + 8) + 0x1c) != 0x5f) | *(*(arg2 + 8) + 0x1d) != 0x31) | *(*(arg2 + 8) + 0x1e) != 0x4e);
int32_t var_c_11 = (((var_c_8 | *(*(arg2 + 8) + 0x1f) != 0x5f) | *(*(arg2 + 8) + 0x20) != 0x54) | *(*(arg2 + 8) + 0x21) != 0x48);
int32_t var_c_14 = (((var_c_11 | *(*(arg2 + 8) + 0x22) != 0x33) | *(*(arg2 + 8) + 0x23) != 0x5f) | *(*(arg2 + 8) + 0x24) != 0x44);
int32_t var_c_17 = (((var_c_14 | *(*(arg2 + 8) + 0x25) != 0x33) | *(*(arg2 + 8) + 0x26) != 0x42) | *(*(arg2 + 8) + 0x27) != 0x55);
int32_t var_c_20 = (((var_c_17 | *(*(arg2 + 8) + 0x28) != 0x47) | *(*(arg2 + 8) + 0x29) != 0x47) | *(*(arg2 + 8) + 0x2a) != 0x33);
int32_t rax_139;
rax_139 = (((var_c_20 | *(*(arg2 + 8) + 0x2b) != 0x52) | *(*(arg2 + 8) + 0x2c) != 0x7d) & 1) == 0;
return rax_139;
}
int32_t main(int32_t argc, char** argv, char** envp)
{
int32_t rax_2;
if (verifyArgc(argc) == 0)
{
puts(&data_2004);
rax_2 = 1;
}
else
{
decrypt(verifyPasswordLength, main);
if (verifyPasswordLength(argc, argv) == 0)
{
puts("Wrong Password!");
rax_2 = 2;
}
else if (verifyPasswordPart1(argc, argv) == 0)
{
puts("Wrong Password!");
rax_2 = 3;
}
else if (verifyPasswordPart2(argc, argv) != 0)
{
puts("Correct Password :)");
rax_2 = 0;
}
else
{
puts("Wrong Password!");
rax_2 = 3;
}
}
return rax_2;
}
Looking at the code above, the password length is 45 (0x2d) characters in length. We can obtain the entire password by just converting the Hex to ASCII equivalent in the verifyPassword1
/verifyPassword2
functions. Using the following Python script, we can automate this process via Regex:
1
2
3
4
5
6
7
import re
with open("decrypted.c", "r") as file:
content = file.read()
matches = re.finditer(r'!= 0x([0-9a-fA-F]+)', content)
for m in matches:
converted = chr(int(m.group(1), 16))
print(converted, end="")
1
2
$ python3 extract_password.py
HTB{TH3_S3CR3TS_4R3_R3V34L3D_1N_TH3_D3BUGG3R}
Flag: HTB{TH3_S3CR3TS_4R3_R3V34L3D_1N_TH3_D3BUGG3R}