Rebuilding
4 minutes to read
We have a binary called rebuilding
:
$ file rebuilding
rebuilding: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c7a145f3a4b213cf895a735e2b26adffc044c190, not stripped
If we run it, it requires to use a command line argument:
$ ./rebuilding
Preparing secret keys
Missing required argument
Let’s open it in Ghidra and decompile it. This is the main
function:
undefined8 main(int argc, long argv) {
int __c;
size_t length;
undefined8 ret;
int checks;
int i;
int j;
if (argc != 2) {
puts("Missing required argument");
/* WARNING: Subroutine does not return */
exit(-1);
}
checks = 0;
length = strlen(*(char **) (argv + 8));
if (length == 32) {
for (i = 0; i < 32; i = i + 1) {
printf("\rCalculating");
for (j = 0; j < 6; j = j + 1) {
if (j == i % 6) {
__c = 0x2e;
} else {
__c = 0x20;
}
putchar(__c);
}
fflush(stdout);
checks = checks + (uint) ((byte) (encrypted[i] ^ key[i % 6]) ==
*(byte *) ((long) i + *(long *) (argv + 8)));
usleep(200000);
}
puts("");
if (checks == 32) {
puts("The password is correct");
ret = 0;
} else {
puts("The password is incorrect");
ret = 0xffffffff;
}
} else {
puts("Password length is incorrect");
ret = 0xffffffff;
}
return ret;
}
First thing to notice is that the argument must be 32 bytes long, let’s verify it:
$ ./rebuilding $(python3 -c 'print("A" * 32)')
Preparing secret keys
Calculating .
The password is incorrect
Alright, so we have the correct length. Then the program computes a XOR operation with encrypted
and key
global variables and checks that the result equals the passed argument. Let’s check what’s in encrypted
and key
:
$ readelf -s rebuilding | grep -E 'encrypted|key'
48: 0000000000201020 34 OBJECT GLOBAL DEFAULT 23 encrypted
51: 0000000000201042 7 OBJECT GLOBAL DEFAULT 23 key
To read their contents with xxd
, we need to substract 0x20000
to both addresses. So we have the following contents:
$ xxd rebuilding | grep -A 2 1020:
00001020: 2938 2b1e 0642 055d 0702 3110 5108 5a16 )8+..B.]..1.Q.Z.
00001030: 3142 0f33 0a55 0000 151e 1c06 1a43 1359 1B.3.U.......C.Y
00001040: 1400 6875 6d61 6e73 0047 4343 3a20 2855 ..humans.GCC: (U
Here we have that key
is "humans"
(a 6-byte string), and encrypted
are just raw bytes. If we do the XOR operation between these two, we should get the expected parameter (the flag). For this purpose, we can use CyberChef:
But it is not correct. Let’s use GDB to see what happens:
$ gdb -q rebuilding
Reading symbols from rebuilding...
(No debugging symbols found in rebuilding)
gef➤ disassemble main
Dump of assembler code for function main:
...
0x0000000000000991 <+266>: lea rax,[rip+0x2006aa] # 0x201042 <key>
0x0000000000000998 <+273>: movzx eax,BYTE PTR [rdx+rax*1]
0x000000000000099c <+277>: xor esi,eax
0x000000000000099e <+279>: mov ecx,esi
0x00000000000009a0 <+281>: mov rax,QWORD PTR [rbp-0x20]
0x00000000000009a4 <+285>: add rax,0x8
0x00000000000009a8 <+289>: mov rdx,QWORD PTR [rax]
0x00000000000009ab <+292>: mov eax,DWORD PTR [rbp-0x8]
0x00000000000009ae <+295>: cdqe
0x00000000000009b0 <+297>: add rax,rdx
0x00000000000009b3 <+300>: movzx eax,BYTE PTR [rax]
0x00000000000009b6 <+303>: cmp cl,al
0x00000000000009b8 <+305>: sete al
0x00000000000009bb <+308>: movzx eax,al
...
End of assembler dump.
We can set a breakpoint at the cmp
instruction and run the program:
gef➤ break *main+303
Breakpoint 1 at 0x9b6
gef➤ run $(python3 -c 'print("A" * 32)')
Starting program: ./rebuilding $(python3 -c 'print("A" * 32)')
Preparing secret keys
Calculating.
Breakpoint 1, 0x00005555554009b6 in main ()
gef➤ x/i $rip
=> 0x5555554009b6 <main+303>: cmp cl,al
gef➤ p/c $rcx
$1 = 0x48
gef➤ p/c $rax
$2 = 0x41
So the expected value for $rax
is 0x48
(H
). Let’s change it and continue:
gef➤ set $rax = $rcx
gef➤ continue
Continuing.
Calculating .
Breakpoint 1, 0x00005555554009b6 in main ()
gef➤ p/c $rcx
$3 = 0x54
Now the expected character is 0x54
(T
). We can continue this manual process until get the flag HTB{...}
. But at this point, we can create a Python script using pwntools
to automate this process of rebuilding the flag:
$ python3 solve.py
[+] Starting local process '/usr/bin/gdb': pid 24165
[+] Flag: HTB{h1d1ng_c0d3s_1n_c0nstruct0r5}
[*] Stopped process '/usr/bin/gdb' (pid 24165)
The full script can be found in here: solve.py
.
However, let’s see what’s in key
:
gef➤ disassemble main
Dump of assembler code for function main:
...
0x000055555540098e <+263>: movsxd rdx,edx
0x0000555555400991 <+266>: lea rax,[rip+0x2006aa] # 0x555555601042 <key>
0x0000555555400998 <+273>: movzx eax,BYTE PTR [rdx+rax*1]
0x000055555540099c <+277>: xor esi,eax
...
gef➤ x/s 0x555555601042
0x555555601042 <key>: "aliens"
Alright, the key
variable has changed. Using "aliens"
we are able to decrypt the XOR cipher using CyberChef to get the flag:
And the issue was that the key
variable was modified in a constructor (_INIT_1
in Ghidra):
void _INIT_1() {
puts("Preparing secret keys");
key[0] = 'a';
key[1] = 'l';
key[2] = 'i';
key[3] = 'e';
key[4] = 'n';
key[5] = 's';
return;
}