IRCware
7 minutos de lectura
Tenemos un binario llamado ircware
:
$ file ircware
ircware: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped
Análisis básico
Si lo abrimos en Ghidra de inmediato, el código en C descompilado será muy difícil de entender porque el binario está despojado de sus símbolos. En su lugar, podemos intentar ejecutarlo:
$ ./ircware
EXCEPTION! ABORT
Bueno, nada útil. Con ltrace
podemos registrar todas las llamadas a funciones externas:
$ ltrace ./ircware
EXCEPTION! ABORT+++ exited (status 1) +++
De nuevo, inútil. Finalmente, con strace
podemos ver todas las llamadas al sistema:
$ strace ./ircware
execve("./ircware", ["./ircware"], 0x7ffc3604c900 /* 35 vars */) = 0
brk(NULL) = 0x2415000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffed94b1390) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=37355, ...}) = 0
mmap(NULL, 37355, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8415efc000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300A\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029592, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8415efa000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\30x\346\264ur\f|Q\226\236i\253-'o"..., 68, 880) = 68
mmap(NULL, 2037344, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8415d08000
mmap(0x7f8415d2a000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f8415d2a000
mmap(0x7f8415ea2000, 319488, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19a000) = 0x7f8415ea2000
mmap(0x7f8415ef0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f8415ef0000
mmap(0x7f8415ef6000, 13920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8415ef6000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f8415efb4c0) = 0
mprotect(0x7f8415ef0000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f8415f33000, 4096, PROT_READ) = 0
munmap(0x7f8415efc000, 37355) = 0
getrandom("\x19\x21\xd8\x33", 4, 0) = 4
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(8000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
write(1, "EXCEPTION! ABORT\0", 17EXCEPTION! ABORT) = 17
exit(1) = ?
+++ exited with 1 +++
Como se puede ver, el programa está tratando de conectarse a 127.0.0.1:8000
:
connect(3, {sa_family=AF_INET, sin_port=htons(8000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
El programa falla porque no hay nada escuchando en ese puerto. Usemos nc
para eso, en otra terminal:
$ ./ircware
$ nc -nlvp 8000
Listening on 0.0.0.0 8000
Connection received on 127.0.0.1 43140
NICK ircware_4056
USER ircware 0 * :ircware
JOIN #secret
Muy bien, parece que estamos conectados a un servidor (probablemente, IRC).
Descompilación
Ahora en Ghidra, podemos buscar strings como USER
o NICK
y encontrar esta función a través de referencias cruzadas:
void FUN_00400349() {
char cVar1;
char cVar2;
byte bVar3;
byte bVar4;
long lVar5;
long lVar6;
ulong uVar7;
long lVar8;
byte *pbVar9;
char *pcVar10;
char *pcVar11;
byte *pbVar12;
char *pcVar13;
char *pcVar14;
lVar8 = 0x1000;
do {
lVar6 = -lVar8;
pcVar10 = (char *) (lVar6 + 0x6031a9);
if (*pcVar10 == '\0') {
return;
}
if (*pcVar10 == 'P') {
lVar5 = 7;
pcVar11 = pcVar10;
pcVar13 = "PING :";
do {
if (lVar5 == 0) break;
lVar5 = lVar5 + -1;
cVar1 = *pcVar11;
cVar2 = *pcVar13;
pcVar11 = pcVar11 + 1;
pcVar13 = pcVar13 + 1;
} while (cVar1 == cVar2);
if (lVar5 == 0) {
DAT_006021aa = 0x4f;
FUN_004002fb();
lVar8 = _DAT_00601010;
_DAT_00601010 = _DAT_00601010 + 1;
if (lVar8 == 0) {
FUN_004002fb();
FUN_004002fb();
FUN_004002fb();
}
return;
}
lVar5 = 0x18;
pcVar11 = pcVar10;
pcVar13 = "PRIVMSG #secret :@pass ";
do {
if (lVar5 == 0) break;
lVar5 = lVar5 + -1;
cVar1 = *pcVar11;
cVar2 = *pcVar13;
pcVar11 = pcVar11 + 1;
pcVar13 = pcVar13 + 1;
} while (cVar1 == cVar2);
if (lVar5 == 0) {
pbVar12 = (byte *) (lVar6 + 0x6031c0);
pcVar10 = "RJJ3DSCP";
pbVar9 = &DAT_00601147;
uVar7 = 0;
break;
}
lVar5 = 0x18;
pcVar11 = pcVar10;
pcVar13 = "PRIVMSG #secret :@exec ";
do {
pcVar14 = pcVar13;
if (lVar5 == 0) break;
lVar5 = lVar5 + -1;
pcVar14 = pcVar13 + 1;
cVar1 = *pcVar11;
cVar2 = *pcVar13;
pcVar11 = pcVar11 + 1;
pcVar13 = pcVar14;
} while (cVar1 == cVar2);
if (lVar5 == 0) {
if (_DAT_00601008 == 0) {
FUN_00400485(pcVar14, "Requires password", &DAT_006021a9, 0x12);
return;
}
DAT_0060108a = lVar6 + 0x6031c0;
FUN_00400592();
return;
}
lVar6 = 0x17;
pcVar11 = pcVar10;
pcVar13 = "PRIVMSG #secret :@flag";
do {
pcVar14 = pcVar13;
if (lVar6 == 0) break;
lVar6 = lVar6 + -1;
pcVar14 = pcVar13 + 1;
cVar1 = *pcVar11;
cVar2 = *pcVar13;
pcVar11 = pcVar11 + 1;
pcVar13 = pcVar14;
} while (cVar1 == cVar2);
if (lVar6 == 0) {
if (_DAT_00601008 == 0) {
FUN_00400485(pcVar14, "Requires password", &DAT_006021a9, 0x12);
return;
}
FUN_004004df(pcVar14,pcVar10);
FUN_00400485();
FUN_004004df();
return;
}
}
lVar8 = lVar8 + -1;
if (lVar8 == 0) {
return;
}
} while (true);
LAB_00400401:
bVar3 = *pbVar12;
*pbVar9 = bVar3;
if (((bVar3 == 0) || (bVar3 == 10)) || (bVar3 == 0xd)) {
if (uVar7 == 8) {
_DAT_00601008 = _DAT_00601008 + 1;
FUN_00400485(pcVar10, "Accepted", 8, 9);
} else {
LAB_00400466:
_DAT_00601008 = 0;
FUN_00400485(pcVar10, "Rejected", uVar7, 9);
}
return;
}
if (8 < uVar7) goto LAB_00400466;
bVar4 = bVar3;
if (((0x40 < bVar3) && (bVar3 < 0x5b)) && (bVar4 = bVar3 + 0x11, 0x5a < bVar4)) {
bVar4 = bVar3 - 9;
}
if (*pcVar10 != bVar4) goto LAB_00400466;
uVar7 = uVar7 + 1;
pbVar9 = pbVar9 + 1;
pbVar12 = pbVar12 + 1;
pcVar10 = (char *) ((byte *) pcVar10 + 1);
goto LAB_00400401;
}
Si analizamos el código un poco más en profundidad, la parte interesante está en la parte inferior. Podemos cambiar el nombre de algunas variables y cambiar tipos para que el código sea más legible:
LAB_00400401:
b = *p_input_;
*p_input = b;
if (((b == '\0') || (b == '\n')) || (b == '\r')) {
if (i == 8) {
_DAT_00601008 = _DAT_00601008 + 1;
FUN_00400485(expected, "Accepted", 8, 9);
} else {
LAB_00400466:
_DAT_00601008 = 0;
FUN_00400485(expected, "Rejected", i, 9);
}
return;
}
if (8 < i) goto LAB_00400466;
_b = b;
if ((('@' < b) && (b < '[')) && (_b = b + 0x11, 'Z' < _b)) {
_b = b - 9;
}
if (*expected != _b) goto LAB_00400466;
i = i + 1;
p_input = p_input + 1;
p_input_ = p_input_ + 1;
expected = (char *) ((byte *) expected + 1);
goto LAB_00400401;
}
Y también, la variable nombrada expected
contiene "RJJ3DSCP"
, en unas líneas más arriba.
Básicamente, tenemos un bucle sobre los caracteres de una contraseña, que se verifican uno por uno. Si después de las operaciones, el byte de resultado no coincide con el esperado, el programa dirá "Rejected"
:
$ nc -nlvp 8000
Listening on 0.0.0.0 8000
Connection received on 127.0.0.1 37534
NICK ircware_1552
USER ircware 0 * :ircware
JOIN #secret
PRIVMSG #secret :@pass ABCD
PRIVMSG #secret :Rejected
PRIVMSG #secret :@pass RJJ3DSCP
PRIVMSG #secret :Rejected
Descifrado
"RJJ3DSCP"
no es la contraseña, es el resultado esperado de las operaciones aplicadas a cada byte de la contraseña. Estas son las operaciones:
_b = b;
if ((('@' < b) && (b < '[')) && (_b = b + 0x11, 'Z' < _b)) {
_b = b - 9;
}
En lugar de revertir las operaciones, escribiré un poco de código en C usando el algoritmo anterior e intentaré encontrar qué bytes de entrada dan el resultado esperado "RJJ3DSCP"
:
#include <stdio.h>
void main() {
int i;
char b;
char _b;
char expected[8] = "RJJ3DSCP";
for (i = 0; i < 8; i++) {
for (b = 0; b < 0x7f; b++) {
_b = b;
if ((('@' < b) && (b < '[')) && (_b = b + 0x11, 'Z' < _b)) {
_b = b - 9;
}
if (_b == expected[i]) {
putchar(b);
break;
}
}
}
putchar('\n');
}
Una vez compilado y ejecutado, encontramos la contraseña esperada:
$ gcc solve.c; ./a.out
ASS3MBLY
Flag
Ahora, si ingresamos esta contraseña, veremos la flag con el comando correspondiente (también podemos ejecutar comandos con /bin/sh
):
$ nc -nlvp 8000
Listening on 0.0.0.0 8000
Connection received on 127.0.0.1 46910
NICK ircware_2760
USER ircware 0 * :ircware
JOIN #secret
PRIVMSG #secret :@pass ASS3MBLY
PRIVMSG #secret :Accepted
PRIVMSG #secret :@exec ASS3MBLY
PRIVMSG #secret :/bin/sh: 1: ASS3MBLY: not found
PRIVMSG #secret :Done!
PRIVMSG #secret :@exec whoami
PRIVMSG #secret :rocky
PRIVMSG #secret :Done!
PRIVMSG #secret :@flag
PRIVMSG #secret :HTB{m1N1m411st1C_fL4g_pR0v1d3r_b0T}