During a routine check on our servers we found this suspicious binary, but when analyzing it we couldn’t get it to do anything. We assume it’s dead malware but maybe something interesting can still be extracted from it?
HTB{m1N1m411st1C_fL4g_pR0v1d3r_b0T}
Category: Reversing
Solver: Pandoron
Writeup
We start by trying to execute this binary on a linux system, since this is an ELF64 binary, which immediately returns with an exception: “EXCEPTION! ABORT”:
pandoron@kali:~/Desktop/CTF$ ./ircware.file
EXCEPTION! ABORT
So let us just dive into the static analysis of this challenge and find where the error message is referenced. I used the program “binary ninja” to disassemble and also partially decompile it. All symbols where stripped from the binary, so all symbols you will see here are manually annotated by me using binary ninja.
The string “EXCEPTION! ABORT” is only referenced once, which is directly in the _start
method of the binary: (decompilation)
syscall(sys_getrandom {0x13e}, buf: USER_ID, buflen: 4, flags: 0)
*USER_ID = *USER_ID & 0x7070707
*USER_ID = *USER_ID | 0x30303030
if (connect_socket() s< 0)
syscall(sys_write {1}, fd: 1, buf: EXCEPTION_ABORT, count: *EXCEPTION_ABORT_LEN)
syscall(sys_exit {0x3c}, status: 1)
noreturn
send_irc(NICK_MSG)
send_irc(USER_MSG)
send_irc(JOIN_MSG)
while (true)
read_server_reply()
dispatch_server_reply()
We see, that it is displayed once some socket connection fails. Let us take a look at that function ourselves:
connect_socket:
mov eax, 0x29
mov edi, 0x2
mov esi, 0x1
mov edx, 0x0
syscall
mov qword [rel SOCKET_FD], rax
mov eax, 0x2a
mov rdi, qword [rel SOCKET_FD]
push 0x100007f {var_8}
push 0x401f {var_10}
push 0x2 {var_18}
mov rsi, rsp {var_18}
mov edx, 0x10
syscall
add rsp, 0xc
retn {var_10+0x4}
We basically create a tcp socket on localhost (127.0.0.1) with port 8000. Hm.. maybe we can just create our own server there..?
nc -l 127.0.0.1 -p 8000
And indeed, this works, since we immediately receive a series of messages that resemble some form of IRC-chat protocol:
NICK ircware_2050
USER ircware 0 * :ircware
JOIN #secret
This tells us basically that we can participate in an IRC protocol, using the channel “#secret”. I assume that we will use chat messages to trigger some actions on the client to give us a flag or something. But to know which messages we can send we need to search the binary for valid IRC-message strings. Let us search for “#secret” and we find the following valid messages that get processed in the clients response-loop:
PRIVMSG #secret :@exec
PRIVMSG #secret :@flag
PRIVMSG #secret :@pass
@flag looks promising, let us just send this message via our improvised nectat-IRC-server:
pawel@kali:~/Desktop/CTF$ nc -l 127.0.0.1 -p 8000
NICK ircware_2050
USER ircware 0 * :ircware
JOIN #secret
PRIVMSG #secret :@flag
PRIVMSG #secret :Requires password
We get a reply telling us that we need to provide a password, that is what the @pass message is probably for!
pawel@kali:~/Desktop/CTF$ nc -l 127.0.0.1 -p 8000
NICK ircware_2050
USER ircware 0 * :ircware
JOIN #secret
PRIVMSG #secret :@flag
PRIVMSG #secret :Requires password
PRIVMSG #secret :@pass TEST
PRIVMSG #secret :Rejected
As we statically analyse the binary more deeply (I will provide a symbols and analysis file that contains all the annotations and comments that I made in binary ninja to understand the binary.), we find that once a correct password is given, we get the flag when we enter the @flag command. We can try to just patch the binary to always provide the flag, but this will not work since the flag is XOR-encrypted using the correct password! We have to understand the password-check function in order to understand what the password is! This function can be found here (the input-string is found at the “rsi”-register):
add rsi, qword [rel PASS_MSG_LEN]
dec rsi
lea rdi, [rel KEY_1]
lea rbx, [rel KEY_0]
xor rdx, rdx {0x0}
mov rcx, rsi
mov al, byte [rsi]
mov byte [rbx], al // overwrite entered key into memory for decryption
cmp al, 0x0
je 0x40043e
cmp al, 0xa
je 0x40043e
cmp al, 0xd
je 0x40043e
cmp rdx, qword [rel KEY_LEN]
ja 0x400466 {KEY_LEN}
cmp al, 'A'
jb 0x40042c
cmp al, 'Z'
ja 0x40042c
add al, 0x11
cmp al, 'Z'
jbe 0x40042c
sub al, 0x5a
add al, 0x40
cmp byte [rdi], al
jne 0x400466
inc rdx
inc rbx
inc rsi
inc rdi
jmp 0x400401
mov rsi, rcx
cmp rdx, qword [rel KEY_LEN]
jne 0x400466 {KEY_LEN}
inc qword [rel ACCEPT_FLAG]
lea rsi, [rel ACCEPT_STR]
mov rcx, qword [rel ACCEPT_STR_LEN]
call send_irc_msg
jmp 0x400484
mov qword [rel ACCEPT_FLAG], 0x0
lea rsi, [rel REJECT_STR]
mov rcx, qword [rel REJECT_STR_LEN]
call send_irc_msg
What this boils down to: The entered key has to pass a series of conditions that all need to be met in order for the ACCEPT_STR to be sent, which indicates a success! There is a Key in memory, which is used as a part of these conditions. The key is “RJJ3DSCP”. The conditions are as follows:
- Input length has to be 8 characters
- If the ASCII-character of Input[i] is <“A” or Input[i] >“Z”, it has to be Input[i] == Key[i]
- Otherwise if Input[i] + 0x11 <= “Z”, it has to be Input[i] + 0x11 == Key[i]
- Otherwise it has to be Input[i] + 0x11 - 0x5a + 0x40 == Key[i]
From these conditions we can manually derive (some simple math) the scheme of the correct input, which is simply “ASS3MBLY”!
When we enter this password we get the decrypted flag and we are done:
We can also manually decrypt the flag, if we want to, the encrypted flag can be found by inspecting the procedures that send the flag to the server (python code):
key = "ASS3MBLY" * 5
key = key[:-5]
flag = "\t\x07\x11H s\x02h,gb\x02>6}\x1a\x1e5\x1f\x07*\x1d<\x0bq%bW~0\x13;q\x07.\x00"
clear = [ chr(ord(a) ^ ord(b)) for (a,b) in zip(flag, key) ]
result = ""
for i in clear:
result += i
print(result)