Rebuilding
4 minutos de lectura
Se nos proporciona un binario llamado 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
Si lo ejecutamos, nos pide poner un argumento de línea de comandos:
$ ./rebuilding
Preparing secret keys
Missing required argument
Vamos a abrirlo en Ghidra para descompilarlo. Esta es la función main
:
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;
}
Lo primero que vemos es que el argumento tiene que ser de 32 bytes, vamos a comprobarlo:
$ ./rebuilding $(python3 -c 'print("A" * 32)')
Preparing secret keys
Calculating .
The password is incorrect
Genial, ahora tiene la longitud correcta. Luego, el programa realiza una operación XOR con las variables globales encrypted
y key
y comprueba que el resultado es igual que el argumento pasado. Vamos a ver qué hay en encrypted
y en key
:
$ readelf -s rebuilding | grep -E 'encrypted|key'
48: 0000000000201020 34 OBJECT GLOBAL DEFAULT 23 encrypted
51: 0000000000201042 7 OBJECT GLOBAL DEFAULT 23 key
Para ver el contenido con xxd
, tenemos que restar 0x20000
a ambas direcciones. Y tenemos los siguientes contenidos:
$ 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
Aquí vemos que key
es "humans"
(una cadena de 6 caracteres), y encrypted
son solo bytes. Si hacemos la operación XOR entre estos dos, deberíamos obtener el parámetro esperado (la flag). Para esto, podemos usar CyberChef:
Pero no parece correcto. Vamos a usar GDB para ver qué pasa:
$ 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.
Podemos poner un breakpoint en la instrucción cmp
y ejecutar el programa:
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
Parece que el valor esperado de $rax
es 0x48
(H
). Vamos a cambiarlo y continuamos:
gef➤ set $rax = $rcx
gef➤ continue
Continuing.
Calculating .
Breakpoint 1, 0x00005555554009b6 in main ()
gef➤ p/c $rcx
$3 = 0x54
Ahora el carácter esperado es 0x54
(T
). Podemos continuar este proceso manual hasta obtener la flag HTB{...}
. Pero en este punto, podemos crear un script en Python con pwntools
para automatizar el proceso de reconstruir la 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)
El script completo se puede encontrar aquí: solve.py
.
Sin embargo, vamos a ver qué hay en 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"
Vale, la variable key
ha cambiado. Usando "aliens"
somos capaces de descifrar la operación XOR con CyberChef para conseguir la flag:
Y el problema era que la variable key
se modificaba en un constructor (_INIT_1
en 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;
}