Tuesday, March 4, 2014

DEFKTHON CTF 2014 - Reversing 300 WriteUp

Hi Again,
The CTF just ended (less than 15 min) and here's the write-up , actually I prepared it yesterday.
Yesterday was a hard day at university , but I was happy to see that a CTF was taking place that night and there were some reversing tasks.
The CTF was DEFKTHON 2014 CTF , I was able to solve Reverse300 , and the challenge was quite cool.
They offered 'me' a 32-bit executable (300.exe) which will display "Password!!" when opening it
(I figured later that you can provide a command line argument and hope to get the flag if you have the right key : D which is really hard to predict btw).


The first step I took was to figure out where exactly the process will write to the console and I figured that it will create a child process that will do the job. Before that , and while stepping I saw that the parent will create a temporary folder under %temp% in which multiple "dll"s and "pyd" files will be stored (python27.dll for example) that folder name looks like _MEIxxxx where "x"s are random numbers . This gave me a clear idea about what this challenge is for, the executable was actually generated from a Py2Exe software and sooner or later the python source code will be in memory or in disk.

Now that I knew that the child process is the one that will execute the interpreted code, I tried to debug that child process. It is actually possible to do this under WinDbg (if you know a way to do it under Olly/Immunity please leave a comment below).
To switch the debugger to the Child Process whenever it's created all you have to do is type this command " .childdbg 1 " this will enable child process debugging for the current session.

eax=00000000 ebx=00000000 ecx=a1240000 edx=0009e138 esi=fffffffe edi=00000000
eip=776e103b esp=0036f9f8 ebp=0036fa24 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!LdrVerifyImageMatchesChecksum+0x96c:
776e103b cc              int     3
1:001> |
   0    id: 2fa8    create    name: image01340000
   1    id: 1e30    child    name: image01340000
(After Calling CreateProcess by the parent here we are breaking before executing the child proc)


Now all I had to do is determine what API is used to write to the console , so I've put a breakpoint on kernel32!WriteFile and kernel32!WriteConsole (there are high odds that would be one of these two), And It was WriteFile which writes the message to the console.
Now I'm at WriteFile and I need to go to the call from the child process right ? So I have kept returning from a function to its caller until reaching the original call from the ChildProcess.

Stack Trace :
============
0036d89c 729699ad kernel32!WriteFile
0036f3a8 72969d37 MSVCR90!lseeki64+0x56b
0036f3ec 7292f4c6 MSVCR90!write+0x9f
0036f410 729313ff MSVCR90!flsbuf+0x143
0036f438 729314ac MSVCR90!fwrite_nolock+0x11b
0036f47c 1e0b1ad8 MSVCR90!fwrite+0x61
0036f4a8 1e0abd64 python27!PyString_InsertThousandsGrouping+0x2c8
0036f4c8 1e0abdb6 python27!PyTrash_thread_destroy_chain+0x144
0036f4dc 1e094a55 python27!PyObject_Print+0x16
[...]
[...]
0036f638 0134352c python27!PyRun_SimpleString+0xc
00000000 00000000 image01340000+0x352c


And here's the call to the function from python27.dll (in the folder under %temp%)

01343525 53              push    ebx
01343526 ff1564c23501    call    dword ptr [image01340000+0x1c264 (0135c264)]


The function is supplied one argument in EBX. And You can see from the way the function is called (the function pointer is accessed from a memory location) that this call could be used to call different functions from python27.dll depending on the task that the file needs to do.
Let's rerun the crackme switch to the child proc and put a breakpoint at (push ebx) , you'll notice that we'll hit the breakpoint many times.
Taking a further look to this function I found that it's always copying each character of the interpreted script filename to the stack, leaving the target file to be executed visible for us. So each time after hitting the breakpoint at <push ebx> , we can display the stack and look for our file which is obviously (challenge1.py). There's a manifest file at the folder in the %temp% giving us a little hint about the filename.
Here's a snippet showing the stack just before starting to execute the interpreted code of challenge1.py :
Now after recognizing the file, we'll need the source to it so maybe it is stored on disk and maybe in memory . I tried to look at the folder stored at %temp% but there was no trace , hopefully the argument passed to the function in EBX is a pointer to a string (in other words pointer to the python source code) so if you dump EBX you'll see the source code right there :)


So the python source is :
==============================================
#AJIN ABRAHAM | OPENSECURITY.IN
from passlib.hash import cisco_pix as pix
import sys,base64
user=""
xx=""
if(len(sys.argv)>1):
     x=sys.argv[1]
     hashus = pix.encrypt("DEFCON14-CTF-IS", user=x)
     z=[89,86,69,121,100,82,69,103,98,47,48,103,80,71,77,121]
     for zz in z:
          xx+= chr(zz+(275*100001-275*1000-27225274))
     hashgen = pix.encrypt("DEFCON14-CTF-IS", user=base64.decodestring(xx))
     if(hashgen==hashus):
          print "Oh Man You got it! But :( ===>    " + str(base64.encodestring(base64.decodestring(xx)))
     else:
          print "Naaaaaaa !! You are screweD"
else:
     print "Password !!"
==============================================
As it seems to be "Password!!" is written to the console because no command line arguments were given when executing the file.
I made simple changes to the code to be able to see the flag directly :) as it will be generated only based on the array "z" (so no need for the passlib.hash) :==============================================
import sys,base64
xx=""
z=[89,86,69,121,100,82,69,103,98,47,48,103,80,71,77,121]
for zz in z:
    xx+= chr(zz+(275*100001-275*1000-27225274))
print "The Flag : " + str((base64.decodestring(xx)))
==============================================
This will print the flag to validate on the platform.
And there it is :  300 points + fun.
See you soon,
Souhail Hammou.