POSTS
MRMCDCTF2019: Slicer
Solution to Slicer (easy) from MRMCDCTF 2019
Slicer is one of the Windows challenges I have written for MRMCDCTF 2019. It’s another easy challenge, but this time the binary is an .exe (PE32) executable.
The challenge was also tested under wine (4.0), so if you want to try out some of the tricks for debugging wine applications, this would be a good opportunity.
But for this text, I will stick to Ghidra .
When running the executable (in Windows or wine), it will behave just like many other challenges: It will display a request for a password in the console, and tell us that we are wrong once we enter the wrong password.
So let’s got to work: Import the executable and open it in the CodeBrowser (just as we did with Cereal).
You should notice some differences: A Windows Portable Executable (PE) file differs in many ways from the Executable and Linkable (ELF) files used on Linux. Many of theses differences are abstracted away by Ghidra, but one is important here: PE files do not have a symbol table like ELF files. The symbol table contains, among many other things, the names of the functions in the executable. Some functions added by the compiler will be named by Ghidra, but for all programmer-created functions (including ‘main’) we are on out own - Ghidra will assign them names based on their address in memory (like ‘FUN_00401000’).
So how do we identify the interesting functions? Well, we know some text strings used by the application, like the “What is the password?” message. We can start by looking at the code that references these.
Open the Ghidra’s strings view (‘Defined Strings’ in the ‘Window’ menu) and search for the “The password is the flag. What is the password?” string (it’s at 00402130). When you double click the string, the ‘Listing’ view (in the middle) will move to the strings location.
The complete line looks like this:
s_The_password_is_the_flag._What_i_00402130 XREF[1]: FUN_00401050:00401068(*)
00402130 ds "The password is the flag. What is the passwor
00402161 ?? 00h
The ‘XREF’ entry (at the end) describes an instruction that references (uses) this string in FUN_00401050 at 00401068. A double click on the address brings you there.
The complete function FUN_00401050 decompiles to this:
void FUN_00401050(void)
{
FILE *_File;
int *piVar1;
int *piVar2;
uint uVar3;
bool bVar4;
char *_Format;
char *local_c;
uint local_8;
local_8 = DAT_00403000 ^ (uint)&stack0xfffffffc;
printf("The password is the flag. What is the password?\n"); [1]
memset(&DAT_00403568,0,0x80);
_File = __iob_func();
fgets(&DAT_00403568,0x80,_File); [2]
strtok_s(&DAT_00403568,"\n",&local_c);
FUN_00401000(); [3]
piVar1 = &DAT_00403468; [4]
piVar2 = &DAT_00403018;
uVar3 = 0x7c;
do {
if (*piVar1 != *piVar2) { [5]
_Format = "Wrong. Try again.\n"; [6]
goto LAB_004010df; [7]
}
piVar1 = piVar1 + 1;
piVar2 = piVar2 + 1;
bVar4 = 3 < uVar3;
uVar3 = uVar3 - 4;
} while (bVar4);
_Format = [8]
"\n##############################################\n### Congratulations! This is the flag! ###\n##############################################\n\n"
;
LAB_004010df:
printf(_Format); [9]
printf("<press any key to continue>\n");
getchar();
FUN_00401106();
return;
}
What do we see here? The “What is the password?” is displayed ([1]) and the password/flag is read into the buffer DAT_00403568 ([2]). This means that DAT_00403568 is the input buffer that stores our password. Note the name here: the ‘DAT_’ reflects that the variable is located in the data segment, not on the stack. DAT_00403568 therefore is a global variable, not a local one.
Then the function FUN_00401000 is called ([3]) and two pointers to other global arrays are assigned ([4], note the ‘DAT_’ names again). A loop iterates over them: If the entries in both array are different ([5]), the variable ‘_Format’ is set to the “Wrong” message ([6]) and the code jumps out of the loop ([7]). If the loop runs till the end, ‘_Format’ is instead set to the “Congratulations” message ([8]).
At the end, ‘_Format’ (with either “Wrong” or “Congratulations”) is printed to the console ([9]).
What does this mean? The process in the loop seems like a comparison between the flag and the input. That means that at this point either DAT_00403468 or DAT_00403018 contain the flag, and the other one the input (or some converted version of it).
But which one is which? When checking out the content (double click on the variable), DAT_00403468 turns out not to be initialised:
DAT_00403464 XREF[1]: 00401181(R)
00403464 undefined4 ??
DAT_00403468+1 XREF[3,3]: FUN_00401000:00401031(W),
DAT_00403468+2 FUN_00401050:004010b3(*),
DAT_00403468+3 FUN_00401050:004010c2(R),
DAT_00403468 FUN_00401000:00401031(W),
FUN_00401000:00401038(W),
FUN_00401000:00401038(W)
00403468 undefined4 ??
DAT_0040346c XREF[1]: FUN_00401050:004010c2(R)
0040346c undefined4 ??
00403470 ?? ??
00403471 ?? ??
00403472 ?? ??
00403473 ?? ??
00403474 ?? ??
00403475 ?? ??
00403476 ?? ??
00403477 ?? ??
[...]
On the other hand, DAT_00403018 clearly contains some form of data:
DAT_00403018 XREF[2]: FUN_00401050:004010b8(*),
FUN_00401050:004010c4(R)
00403018 undefined4 0502040Dh
DAT_0040301c XREF[1]: FUN_00401050:004010c4(R)
0040301c undefined4 0403040Dh
00403020 ?? 04h
00403021 ?? 04h
00403022 ?? 03h
00403023 ?? 04h
00403024 ?? 04h
00403025 ?? 05h
00403026 ?? 06h
00403027 ?? 04h
00403028 ?? 0Bh
00403029 ?? 07h
0040302a ?? 05h
0040302b ?? 06h
[...]
So we can conclude that DAT_00403018 is the saved flag, and DAT_00403468 will contain our input. But the values in DAT_00403018 are definitely neither printable ASCII nor unicode. And how does our input come from the input buffer at DAT_00403568 to DAT_00403468?
Well, we haven’t checked out FUN_00401000 (called at [3]). This is what it looks like:
void FUN_00401000(void)
{
byte bVar1;
int iVar2;
iVar2 = 0xf;
do { [1]
iVar2 = iVar2 + -1;
} while (-1 < iVar2);
iVar2 = 0; [2]
do {
bVar1 = (&DAT_00403568)[iVar2]; [3]
*(byte *)((int)&DAT_00403468 + iVar2 * 2) = bVar1 & 0xf; [4]
*(byte *)((int)&DAT_00403468 + iVar2 * 2 + 1) = (char)bVar1 >> 4 & 0xf;
iVar2 = iVar2 + 1;
} while (iVar2 < 0x80);
return;
}
The first loop ([1]) doesn’t seem to do much: iVar2, the only variable it works with, is later overwritten with a constant ([2]).
The second loop is more interesting: It takes every byte of the input ([3]) and then splits them in two: The lower 4 bits an the input byte are extracted at [4], the higher 4 bits in the following line. Both are copied over to the buffer at DAT_00403468 - the buffer that is later compared to the saved flag.
The process becomes a bit clearer once you assign the variables proper names and types:
void convert_flag(void)
{
int loop_counter;
int unused_dummy_variable;
byte input_byte;
unused_dummy_variable = 0xf;
do {
unused_dummy_variable = unused_dummy_variable + -1;
} while (-1 < unused_dummy_variable);
loop_counter = 0;
do {
input_byte = input_buffer[loop_counter];
converted_input[loop_counter * 2] = input_byte & 0xf;
converted_input[loop_counter * 2 + 1] = (char)input_byte >> 4 & 0xf;
loop_counter = loop_counter + 1;
} while (loop_counter < 0x80);
return;
}
With this knowledge, we now take a second look at DAT_00403018 (here renamed to encoded_flag):
encoded_flag[4] XREF[2,1]: FUN_00401050:004010b8(*),
encoded_flag FUN_00401050:004010c4(R),
FUN_00401050:004010c4(R)
00403018 db[256]
00403018 [0] Dh, 4h, 2h, 5h
0040301c [4] Dh, 4h, 3h, 4h
00403020 [8] 4h, 4h, 3h, 4h
00403024 [12] 4h, 5h, 6h, 4h
00403028 [16] Bh, 7h, 5h, 6h
0040302c [20] 6h, 7h, 5h, 6h
00403030 [24] 2h, 7h, 9h, 7h
00403034 [28] Fh, 5h, 8h, 6h
00403038 [32] 1h, 6h, Ch, 6h
0040303c [36] 6h, 6h, Fh, 5h
00403040 [40] 2h, 6h, 9h, 7h
00403044 [44] 4h, 7h, 5h, 6h
00403048 [48] Fh, 5h, 3h, 6h
0040304c [52] Fh, 6h, 5h, 7h
00403050 [56] Eh, 6h, 4h, 7h
00403054 [60] 3h, 7h, Dh, 7h
00403058 [64] 0h, 0h, 0h, 0h
0040305c [68] 0h, 0h, 0h, 0h
00403060 [72] 0h, 0h, 0h, 0h
00403064 [76] 0h, 0h, 0h, 0h
[...]
These numbers are the low and high four bits of every flag byte. All that’s left to do is to merge them back together and print them.
We can do this with python:
#!/usr/bin/env python
# to get the hexstring out of ghidra select the variable,
# select "Copy special..." on right click,
# and chose "byte string (no spaces)"
hexstring = "0d0402050d04030404040304040506040b07050606070506020709070f05080601060c0606060f0502060907040705060f0503060f0605070e06040703070d07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
bytes = bytearray(hexstring.decode("hex"))
bytes.reverse() # allows us to pop from the end
flagstr = ""
while len(bytes):
low = bytes.pop()
high = bytes.pop()
fullbyte = (high<<4) | low
flagstr += chr(fullbyte)
print(flagstr)
And this script will finally give you the flag.