crackme (re1200)
Attention all UTCTF players, asper is in great danger, and he needs YOUR help to reverse engineer this binary and figure out the password. To do this, he needs IDA Pro and a couple of breakpoints. To help him, all he needs is your credit card number, the three numbers on the back, and the expiration month and date. But you gotta be quick so that asper can secure the flag, and achieve the epic victory R O Y A L.
Note: the flag is the password with utflag{} wrapped around it.
by jitterbug_gang
Also, this binary was compiled a little differently, and you may need to install some extra dependencies to run it. (Or you can try solving this with just static analysis.) Try installing libc++abi-dev and libcxxtools-dev to run this challenge.
If that doesn’t work for you, try to preload this libc file with LD_PRELOAD.
int main(void) {
int correct;
size_t len;
double dVar1;
int counter;
int index;
char input[0x40];
setbuf(stdin, 0);
setbuf(stdout, 0);
printf("Please enter the correct password.\n>");
fgets(input, 0x40, stdin);
len = strlen(input);
try {
dVar1 = divide(0x20,0);
}
catch {
// ghidra doesn't decompile this part properly
}
index = 0;
while (index < len) {
input[index] = input[index] ^ 0x27;
index = index + 1;
}
counter = 0;
while (counter < 0xcb) {
stuff[counter] = stuff[counter] - 1 ^ stuff2[(0xca - counter)];
counter = counter + 1;
}
(*(code *)stuff)(input,len);
correct = memcmp(test,input,0x40);
if (correct == 0) {
printf("Correct Password!");
}
else {
printf("Incorrect password.\n");
printf(
"utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}\n"
);
}
return 0;
}
After opening the given binary in GHIDRA, we can obtain the pseudocode above in main
. It looks rather straightforward, with the program doing the following
- Read 0x40 bytes of input
- Calculates 0x20/0 ???
- Xors all input bytes with 0x27
- Does some xor computations with bytes in
stuff
- Calls
stuff
as a function with our input and length as arguments - Checks if our input matches
test
Extracting the encryption function
From this, we can speculate that stuff
does something to our input, before memcmp
is used to check it. So, the obvious plan is to extract stuff
after it is manipulated, and reverse whatever is being done in it.
We can achieve that by setting a breakpoint right before memcmp
is called, since at that point stuff
would have contained the correct opcodes. Let’s do that in GDB. But hmm, nothing happens when I run the binary, and the process just exited.
gef➤ break *0x400d8c
Breakpoint 1 at 0x400d8c
gef➤ r
Starting program: /root/ctfs/utctf19/crackme/crackme
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Inferior 1 (process 1745) exited with code 01]
There could have been some anti-debug measures in place. When trying to search for references to ptrace()
in the binary, I found this in csu_init()
.
csu_init
is called before main
void _csu_init(void)
{
long ret;
ret = ptrace(PTRACE_TRACEME, 0, 1, 0);
if (ret == -1) {
exit(1);
}
}
This is a standard way to prevent GDB to attach to the process and debug it, since every process is only allowed to have one tracer.
No big deal, we can just patch the call
instruction to a nop
instead. (There is currently an issue with GHIDRA when trying to patch binaries after auto-detecting its format. Check this issue.)
Now with our patched binary, we can attach GDB to break before memcmp
is called and dump the contents of stuff
. From the loop in the pseudocode above, we know that stuff
contains 0xcb bytes. Dumping memory in GDB is very simple.
gef➤ dump memory stuff 0x602090 0x60215b
After that, we can open stuff
in GHIDRA.
So many instructions… How about looking at the pseudocode.
void stuff(char* input, ulong len) {
int index = 0;
while (index < len) {
input[index] = in[index] ^ (index + 0x33);
index++;
}
}
Way better.
Reversing it
The next logical step is to apply the reverse of this function to test
, which is compared with our input after being modified by this function. I wrote a simple C program that would take in test
as input and print out after decrypting it.
#include <stdio.h>
int main() {
char flag[0x41];
fgets(flag, 0x40, stdin);
for (int i = 0; i < 0x40; ++i) {
flag[i] = flag[i] ^ (0x33 + i);
flag[i] ^= 0x27;
}
flag[0x39] = 0;
puts(flag);
}
With this in place, we need to also dump the contents of test
so that we can decrypt it.
gef➤ dump memory test 0x602230 0x602270
Then,
~/ctfs/utctf19/crackme ➤ gcc solve.c
~/ctfs/utctf19/crackme ➤ cat flag | ./a.out
1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3stf_cspp3rstick6
Before submitting the flag, I entered it into the given binary for a sanity check. But it tells me this is wrong?
Recall the divide(0x20, 0)
enclosed in a try/catch
block. Apparently in the catch
block there are some manipulations being done to our input as well, which GHIDRA did not show in the pseudocode.
try {
dVar1 = divide(0x20,0);
}
catch {
// ghidra doesn't decompile this part properly
}
Since dividing anything by 0 would cause an exception, the code inside the catch
block will be executed.
Perhaps this was a way to hide the code.
But anyways, it wasn’t very complicated, just 2 additional lines.
try {
dVar1 = divide(0x20,0);
}
catch {
// ghidra doesn't decompile this part properly
input[47] ^= 0x44;
input[52] ^= 0x43;
}
Adding this to our decryption code, we get the correct flag.
~/ctfs/utctf19/crackme ➤ cat flag | ./a.out
1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st_0f_c0pp3rstick6