CrySyS SecChallenge 2020: Master boot record
It’s a beginner-friendly challenge. Don’t worry if you are not a binary-ninja, I hope it’s a good point to start to learn reverse engineering. Just check the webpage and show me that, you understand the basics of how binaries work in OS level and in userland, too.
Sorry for linux3 name, nothing connected to linux… but I don’t really know what I am doing. Can you find my flag?
Categories
Reverse Engineering, C language, Stack Overflow
Files
- linux3.iso (provided as downloadable in an online x86 emulator environment)
- Unfortunately I cannot provide the full environment for this, as the server code wasn’t given
Links
Solution
Let’s download the ISO and open it up in our favorite archiver. We can notice MAIN.FLP
with a size of an MBR (512 bytes) pretty easily. Loading it into IDA as 16-bit 8086 code confirms our suspicion. After doing the usual chores of marking functions / strings and naming stuff, we can see this control flow graph:
By following the edges into the keyboard reading part, we can easily deduce the following:
dl
holds count of entered characterscl
is 0
By following the input character (al
) from the reading interrupt, we see this happening:
-
al
is checked for\r
. If true anddl = 54
the password is correct. This must be our length. -
al = al ^ 0x21 ^ 0xFF
. -
al
is checked against0xFF
. For this to be trueal
would need to be0xDE
, which is unlikely. This must be an always false test, therefore we ignore the true branch. -
al = bl = al ^ 0xFF + 5
. Note how xoring by0xFF
cancels the previous xor with0xFF
. -
al = bEncryptedData[dl]
, previous value is trashed, therefore we only need to followbl
forchar ^ 0x21 + 5
andal
forbEncryptedData[count]
. -
Save
ax
tocx
, test for0x3E == 0x62
, then restoreax
. This is always false, so we ignore the other outgoing edge. -
al
andbl
is compared - jackpot! If this is true, we advancedl
.
Based on this we can easily write a C++ program that simply decrypts bEncryptedData
:
constexpr unsigned char bEncryptedData[55] = {
0x6E, 0x6D, 0x74, 0x7A, 0x06, 0x6B, 0x53, 0x53, 0x4A, 0x06, 0x50, 0x53,
0x48, 0x12, 0x06, 0x47, 0x4E, 0x49, 0x47, 0x4F, 0x06, 0x13, 0x56, 0x45,
0x4B, 0x49, 0x57, 0x13, 0x4E, 0x15, 0x4A, 0x4A, 0x17, 0x54, 0x58, 0x16,
0x59, 0x5A, 0x17, 0x5B, 0x15, 0x5A, 0x4E, 0x52, 0x16, 0x54, 0x4B, 0x57,
0x5A, 0x58, 0x15, 0x54, 0x4B, 0x57, 0x00
};
int main()
{
char data[55];
for (auto i = 0; i < 55; ++i)
data[i] = (char)((bEncryptedData[i] - 5) ^ 0x21);
data[54] = 0;
printf("%s", data);
return 0;
}
Output:
HINT Good job, check /pages/h1dd3nr0ut3w1thl0ngstr1ngs
Visiting the page we’re greeted with the following:
//Use the pw GET parameter for flag
char in_pw[20];
char check_pw[20];
for (char* mypointer = check_pw; mypointer < check_pw + 20; mypointer++)
*mypointer = (rand() % 255) + 1;
strcpy(in_pw, threadlocalhrq.req_body[i].value);
if(memcmp(check_pw, in_pw, 20) == 0)
response=sdscat(response,"<p>cd20{REDACTED}</p>");
else
response=sdscat(response,"<p>The pw is wrong!</p>");
This is a simple buffer overflow, a long enough in_pw
will overwrite check_pw
. After trying some lengths increasing from 40 I eventually got it right:
You try pw: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
cd20{F1nd1ng_0ut_p4ssw0rds_1s_s0_fun_R3v3rs3_lvl0_d0n3}