PatriotCTF 2020 - Malloc
This challenge was interesting in that I found the flag more or less by accident. It was also a good example of using my pet project revenge to aid in reversing.
My solution steps involved identifying that malloc was being run a bunch, using revenge to trace the output of each malloc, then accidentally discovering that the flag was ascii art.
Triage
As usual, let’s give it a run.
$ ./malloc
I think you missed the flag
Not much to go on there. strings doesn’t show anything of interest.
Running ltrace shows a ton of malloc
calls:
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c360
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c380
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c3a0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c3c0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c3e0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c400
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c420
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c440
[pid 8868] [0x556dcade1988] malloc(64) = 0x556dcc26c460
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c4b0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c4d0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c4f0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c510
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c530
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c550
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c570
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c590
[pid 8868] [0x556dcade1988] malloc(64) = 0x556dcc26c5b0
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c600
[pid 8868] [0x556dcade174d] malloc(8) = 0x556dcc26c620
A quick peek at the decompiled output:
int main(void)
{
int iVar1;
undefined8 *puVar2;
void *pvVar3;
void *pvVar4;
int local_38;
int local_34;
puVar2 = (undefined8 *)malloc(0xa8);
*puVar2 = 0x201010;
puVar2[1] = 0x201018;
puVar2[2] = 0x201020;
puVar2[3] = 0x201028;
puVar2[4] = 0x201030;
puVar2[5] = 0x201038;
puVar2[6] = 0x201040;
puVar2[7] = 0x201048;
puVar2[8] = 0x201050;
puVar2[9] = 0x201058;
puVar2[10] = 0x201060;
puVar2[0xb] = 0x201068;
puVar2[0xc] = 0x201070;
puVar2[0xd] = 0x201078;
puVar2[0xe] = 0x201080;
puVar2[0xf] = 0x201088;
puVar2[0x10] = 0x201090;
puVar2[0x11] = 0x201098;
puVar2[0x12] = 0x2010a0;
puVar2[0x13] = 0x2010a8;
puVar2[0x14] = 0x2010b0;
local_38 = 0;
while (local_38 < 0x15) {
pvVar3 = malloc((long)DAT_002010b8 << 3);
local_34 = 0;
while (local_34 < DAT_002010b8) {
pvVar4 = FUN_00000730(*(byte *)((long)local_34 + puVar2[local_38]));
*(void **)((long)local_34 * 8 + (long)pvVar3) = pvVar4;
local_34 = local_34 + 1;
}
*(void **)(puVar2 + local_38) = pvVar3;
local_38 = local_38 + 1;
}
iVar1 = puts("I think you missed the flag");
return iVar1;
}
We can see that we’re looping and performing malloc
calls. My initial guess
was that the flag would be hidden inside of one of those calls. I was sorta
right and sorta wrong..
Tracing the Function
To get more information about what was going on here, I decided to use my
personal tool revenge. You could definitely do this sort of tracing
in many other ways, but I chose to use revenge
.
The function that’s being called over and over is at 0x730
. I was interested
in what it was returning. For brevity, I’ll show my enumeration script with
comments inline.
#!/usr/bin/env python
from revenge import Process, common, types
# Standard loading up an elf file
p = Process("./malloc")
# This is the function I'd like to trace
thing = p.memory['malloc:0x730']
# I will keep return values in here
msgs = []
# This function will get called every time the function is run
def catch_message(x,y):
global msgs
# Simply append the message so we can look at it later
msgs.append(x['payload'])
# Setting up my function for tracing
thing.return_type = types.Pointer
# Only one argument, and it's technically a byte, but int8 works
thing.argument_types = types.Int8
# Every time we get a message, I want catch_message to be notified
thing.replace_on_message = catch_message
# Here is where I'm actually replacing the function with my js (via frida)
# The js simply calls the function, reads the return as a pointer to a string,
# and sends that string back
thing.replace = """function (s) { var ret=original(s); send(ret.readUtf8String()); return ret;}"""
# This will cause binary execution to continue
p.memory[p.entrypoint].breakpoint = False
Wouldn’t you know, to my surprise when I looked at the results of my trace, i saw this:
In [3]: msgs
Out[3]:
[' ###### ',
' # #',
' # #',
' ###### ',
' # ',
' # ',
' # ',
' ',
' ##### ',
' # #',
' # ',
' # ',
' # ',
' # #',
' ##### ',
' ',
' #######',
' # ',
' # ',
' # ',
' # ',
' # ',
' # ',
' ',
# clipped
Well obviously the game was up now. Clearly all I had to do was combine the lines assuming an 8 length array to get the flag:
out = ['']*8
for i in range(0, len(msgs), 8):
for j in range(8):
out[j] += msgs[i+j]
[' ###### ##### ####### ####### ### ####### # # # # # # ####### # # ####### ##### # # # # ###### ####### # ## ',
' # # # # # # # # # # # # ## ## # # # # # # # # # # # # # # # # ',
' # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ',
' ###### # # ##### ## # # # # # # # # # ##### # ##### ##### ####### # # ###### # ## ',
' # # # # # # # # # # # # # # # # # # # # # # # # # ',
' # # # # # # # # # # # # # # # # # # # # # # # # # # # ',
' # ##### # # ### ####### ## ## # # # ####### # ####### ##### # # ##### # # # # ## ',
' ####### ####### ####### ']
# The above likely won't display well...
# PCTF{OW_MY_EYES_HURT}
Score one for accidental solve.
Downloads
Let me know what you think of this article on twitter @bannsec!