CSCG 2020: win_eXPerience 2


Memory dump is given.

The writeup for part 1 can be found here. There we already got the profile of the memorydump.

Extracting the Crackme

In the description we get the hint, that we are searching for a crackme. So we first list the processes and look for a running executable.

$ volatility -f memory.dmp --profile WinXPSP2x86 pslist

Offset(V)  Name                    PID   PPID   Thds     Hnds   Sess  Wow64 Start                          Exit                          
---------- -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------
[...]
0x8173ec08 CSCG_Delphi.exe        1920   1524      1       29      0      0 2020-03-22 18:27:45 UTC+0000                                 
[...]
        

Looks like we found the right process. So we use procdump to extract the binary file.

$ volatility -f memory.dmp --profile WinXPSP2x86 procdump --dump-dir procdump

Volatility Foundation Volatility Framework 2.6
Process(V) ImageBase  Name                 Result
---------- ---------- -------------------- ------
0x81bcca00 ---------- System               Error: PEB at 0x0 is unavailable (possibly due to paging)
0x81a04da0 0x48580000 smss.exe             OK: executable.340.exe
[...]
0x8173ec08 0x00400000 CSCG_Delphi.exe      OK: executable.1920.exe
[...]

But for some reasons the extracted file is not the actual binary, so we search in the filescan again.

$ volatility -f memory.dmp --profile WinXPSP2x86 filescan

(...)
0x0000000001a0c988      1      0 R--rwd \Device\HarddiskVolume1\Documents and Settings\CSCG\Desktop\CSCG\CSCG_Delphi.exe
(...)

From the filescan we can dump the binary with the discovered offset.

$ volatility -f memory.dmp --profile WinXPSP2x86 dumpfiles -Q 0x0000000001a0c988 -D procdump -u -n

Volatility Foundation Volatility Framework 2.6
ImageSectionObject 0x01a0c988   None   \Device\HarddiskVolume1\Documents and Settings\CSCG\Desktop\CSCG\CSCG_Delphi.exe
DataSectionObject 0x01a0c988   None   \Device\HarddiskVolume1\Documents and Settings\CSCG\Desktop\CSCG\CSCG_Delphi.exe

Analyzing the Binary

The hint in the file name obviously shows, that it is a Delphi binary. If we run the binary we can see a simple input field and a button to check the flag.

If we open the dumped binary in ghidra, it looks really complicated, because ghidra could not identify the default library. There is a ghidra script called dhrake to resolve the function names. We can load the script to ghidra and follow the steps in the readme.

We start with our search for the message, which pops up in a wrong guessing attempt. The string has only one cross reference so we take a look at the referenced function. If we open the generated pseudocode, it looks really good. I renamed some values and added some comments based on assumptions, to get the code more readable.

So let us analyze the function. In the beginning a md4 object is generated.

md4gen = (undefined **)
    TObject.Create((int *)&PTR_TIdHashMessageDigest4.HashValue_00453b34,'\x01',extraout_ECX);

After that some strings are loaded.

@LStrAsg(&md4_0,(undefined4 *)"1EFC99B6046A0F2C7E8C7EF9DC416323");
@LStrAsg((int *)&md4_1,(undefined4 *)"25DB3350B38953836C36DFB359DB4E27");
@LStrAsg((int *)&md4_2,(undefined4 *)"C129BD7796F23B97DF994576448CAA23");
@LStrAsg((int *)&md4_3,(undefined4 *)"40A00CA65772D7D102BB03C3A83B1F91");
@LStrAsg((int *)&md4_4,(undefined4 *)"017EFBC5B1D3FB2D4BE8A431FA6D6258");

They look like hashes, so we put them in a hash analycer. The result is that it could be a MD4 or MD5 hash. After that, there are some comparisons of chars of the flag format (CSCG{}). I assumed this as the input and the array index, which is compared to the closing bracket, as the flag length.

if (*FLAG_BUF == 'C') {
  if ((FLAG_BUF[2] == 'C') && (FLAG_BUF[3] == 'G')) {
    if (FLAG_BUF[1] == 'S') {
      if (FLAG_BUF[4] == '{') {
        if (FLAG_BUF[FLAG_LEN + -1] == '}') {

Then we can find a loop over the input characters. Within the loop the underscore characters are counted.

if (0 < FLAG_LEN) {
      /* loop flag chars */
  pos = (char *)0x1;
  i = FLAG_LEN;
  do {
      /* count "_" in flag */
    if (pos[(int)(FLAG_BUF + -1)] == '_') {
      _count = _count + 1;
    }
    pos = pos + 1;
    i = i + -1;
  } while (i != 0);
}

Another "if condition" checks, if the result of the underscore count equals 4.

if (_count == 4)

So we can assume, that the flag looks something like this CSCG{X_X_X_X_X}. At this point we can already guess, that the five parts between the underscores are the values for the hashes above, but we will still go through the code further.

There is a longer part in the code which divides the input into the parts between the underscores as guessed before. But before the input is hashed, the string is reversed. Then the hashed value is compared to a string and the check variable is updated.

ii = 1;
do {
  /* get flag part until "_" */
  pos = PosEx("_",FLAG_BUF,1);
  flaglen = @LStrLen((int)FLAG_BUF);
  if (pos == (char *)0x0) {
    pos = (char *)(flaglen + 1);
  }
  AnsiLeftStr(FLAG_BUF,pos + -1,&stack0xffffffec);
  @LStrAsg(&BUFF,flagpart);
  /* reverse order flagpart */
  AnsiReverseString(BUFF,(int *)&flagpartReversed);
  @LStrAsg(&BUFF,flagpartReversed);
  /* hash reversed flagpart */
  TIdHash128.HashValue(md4gen,BUFF,&stack0xffffffd4);
  TIdHash128.AsHex(*md4gen,(int)&stack0xffffffd4,(int *)&hexhash);
  @LStrAsg(&hexhash,hexhash);
  /* compare with correct hash */
  cmpRes = @LStrCmp(*(char **)(&DAT_00458df8 + ii * 4),hexhash);
  if (!cmpRes) {
    __checkFlag = 0;
  }
  /* cut off processed part from buffer */
  AnsiRightStr(FLAG_BUF,flaglen - (int)pos,&stack0xffffffd0);
  @LStrAsg(&FLAG_BUF,flagRight);
  ii = ii + 1;
  i = i + -1;
} while (i != 0);

So in order to get the flag we have to crack the hashes, reverse the strings and put the parts together with underscores. We can look them up in crackstation, where three hashes are already available. As the three hashes are md5 and not md4 hashes, I thought all might be md5 and that the call to md4 was recognized wrong in ghidra. We can try some other md5 cracker for the missing two hashes. md5online.org and onlinehashcrack.com solved the other two.

1EFC99B6046A0F2C7E8C7EF9DC416323 md5 dl0
25DB3350B38953836C36DFB359DB4E27 md5 kc4rc
C129BD7796F23B97DF994576448CAA23 md5 l00hcs
40A00CA65772D7D102BB03C3A83B1F91 md5 !3m
017EFBC5B1D3FB2D4BE8A431FA6D6258 md5 1hp13d

As I did not understand the order of the words in the static analysis, I looked at it in gdb. Putting the parts together, we get the flag CSCG{0ld_sch00l_d31ph1_cr4ck_m3!}.