IRCWare
Overview
IRCWare is a program that connects to 127.0.0.1:8000
and acts as an IRC
Client, when it first connects it sends the following 'commands':
NICK ircware_5115
USER ircware 0 * :ircware
JOIN #secret
Analyzing the program in radare2 we see that it uses no library functions, and does all IO by making syscalls itself:
After connecting to the socket and printing it's opening message, the program enters a loop of reading input into a buffer, and processing the input:
The program accepts the following commands:
PING :<message>
: which causes it to reply withPONG :<message>
PRIVMSG #secret :@pass <password>
: which checks if the password is correct, setting a flag if it was.PRIVMSG #secret :@exec <cmd>
: which executes/bin/sh -c '<cmd>'
if the password has been entered previouslyPRIVMSG #secret :@flag
: which prints the flag if the password has been entered previously.
Cracking the password
To check if the password is correct, the program first copies 'RJJ3DSCP' into a buffer, then loops over each input of the input password, mutating the performing some obscure checks along the way, if the end of the loop is reached then the password was correct:
To solve this I rewrote the password checking code as a C program:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
char user_inp[8];
memcpy(user_inp, argv[1], 8);
char pw[] = "RJJ3DSCP";
char pw2[] = "RJJ3DSCP";
int i = 0;
while (1) {
char x = user_inp[i];
pw2[i] = x;
if ((x == 0) || (x == 10) || (x == 0xd)) {
if (i == 8) {
printf("good\n");
}
break;
}
if (i >= 8)
break;
char y = x;
if (((0x40 < x) && (x < 0x5b)) && ((y = x + 0x11), 0x5a < y)) {
y = x - 9;
}
if (y != pw[i]) {
break;
}
i += 1;
}
printf("bad");
}
And then wrote a wrapper that would find the password using Angr:
import angr
import claripy
input_len = 8
p = angr.Project("a.out", main_opts={"base_addr": 0}, auto_load_libs=False)
input_ = [claripy.BVS(f'inp_{i}', 8) for i in range(input_len)]
input_a = claripy.Concat(*input_, 0)
st = p.factory.entry_state(
args=["./a.out", input_a],
add_options={angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY},
)
sm = p.factory.simulation_manager(st)
sm.explore(find=0x11f8, avoid=0x128b)
for x in sm.found:
path = x.solver.eval_upto(input_a, 1, cast_to = bytes)
print(path)
Running this, angr quickly found the flag:
Now we can claim the flag: