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?


Category: Reversing

Solver: Pandoron


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 

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)
while (true)

We see, that it is displayed once some socket connection fails. Let us take a look at that function ourselves:

mov     eax, 0x29
mov     edi, 0x2
mov     esi, 0x1
mov     edx, 0x0
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
add     rsp, 0xc
retn     {var_10+0x4}

We basically create a tcp socket on localhost ( with port 8000. Hm.. maybe we can just create our own server there..?

nc -l -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 -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 -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:

  1. Input length has to be 8 characters
  2. If the ASCII-character of Input[i] is <“A” or Input[i] >“Z”, it has to be Input[i] == Key[i]
  3. Otherwise if Input[i] + 0x11 <= “Z”, it has to be Input[i] + 0x11 == Key[i]
  4. 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