IRCware
7 minutes to read
We have a binary called 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
Basic analysis
If we open it in Ghidra straight away, the decompiled C code will be very difficult to understand because the binary is stripped (there are not symbols). Instead, we can try to execute it:
$ ./ircware
EXCEPTION! ABORT
Well, nothing useful. With ltrace
we can log all external function calls:
$ ltrace ./ircware
EXCEPTION! ABORT+++ exited (status 1) +++
Again, useless. Finally, with strace
we can see all system calls:
$ 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 +++
As can be seen, the program is trying to connect to 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)
The program fails because there is nothing listening on that port. Let’s use nc
for that, in another 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
Alright, it looks like we are connected to a server (probably, IRC).
Decompilation
Now in Ghidra, we can search for strings like USER
or NICK
and find this function via cross-references:
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;
}
If we analyze the code a bit deeper, the interesting part is at the bottom. We can rename some variables and change types to make the code more readable:
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;
}
And also, the variable named expected
is set to "RJJ3DSCP"
some lines above.
Basically, we have a loop over the characters of a password, which are checked one by one. If after the operations, the result byte does not match with the expected one, the program will say "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
Decryption
"RJJ3DSCP"
is not the password, it is the expected result of the byte operations applied on the password. These are the byte operations:
_b = b;
if ((('@' < b) && (b < '[')) && (_b = b + 0x11, 'Z' < _b)) {
_b = b - 9;
}
Instead of reversing the operations, I will write a bit of C code using the above algorithm and try to find which input bytes give the expected result "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');
}
Once compiled and executed, we find the expected password:
$ gcc solve.c; ./a.out
ASS3MBLY
Flag
Now, if we enter this password, we will see the flag with the corresponding command (we can also execute commands with /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}