I played the EMC defenders CTF with a few of my friends a while back. We sadly couldn't complete all the challenges. All the same it was quite a lot of fun. One of the challenges in Week 3 was reverse engineering a 32 bit Windows executable.
While the challenge finished a while ago, I was poking at it a bit even after the challenge finished and finally managed to get a flag. In this post, I wanted to talk a little bit about the various obstacles that were in the way and how I circumvented them.
The first thing that I noticed was that directly running the executable caused it to terminate. So I started digging into it a bit more and found that there were a couple of checks that the exe performed.
- Check if it was being executed in a 64 bit environment
- Check if it was being run inside a debugger
A quick patch of the JZ to a JNZ bypassed both these protections and we were able to proceed without a problem.
After a while at 4010CF VirtualAlloc was called and a section of memory allocated to write "something into" - we don't know what at this time. The next problem was when a CALL was made at 40110B - which called into this section.
Going to that place in memory showed a lot of weird weird code which did not look right at all. Have a look at the screenshot.
Trying to run this code at this point caused an Access Violation straightaway. So I started single stepping the code, and immediately found that code a little lower down started changing. This meant that this was some kind of self modifying executable - a lot of packers use this trick.
I single stepped up to a point and ran again but it crashed again. Single step again - the next section made sense now. I repeated this for quite a while until the entire section started to make sense. What was happening, was that a small section of code would decrypt the next section. The next section would then run and decrypt the next bit. And so on ... until the entire bit unpacked itself.
Here is a screenshot of a packed section.
..And the same section after it's unpacked.
Single stepping the code until 3D0194 eventually decrypted every single bit of the code.
The unpacked code is at 3D0791 (red highlight in screen-shot above). This is how most packers behave - unpack the code to a different location and run it from there. Notice also all those junk ADD instructions after the CALL - more signs that the unpacking ends here. Lets hop over to 3D0791 now and see what's there.
Look at the last instruction and the red highlighted bit below. There is a CMP with 5A4D there and something happening after that. Hmm. 5A4D = MZ in Ascii. And MZ is the start of an EXE file. So it looks like it's searching for the start of an EXE file in memory. That too probably is what the unpacker has done - unpacked the real EXE file somewhere into memory. Where? We don't know as of now.
The red highlighted bits do have MZ and PE - things which lead us to think that the file's somewhere near.. but the other bits like "This program cannot be run in DOS mode.." .. are nowhere near. So maybe... it's not this bit which is the EXE but somewhere else. Where though?
Lets run the code after this and see what happens. We hit F9 and the code starts looping backward .. each time decrementing the place it searches by 1 (ECX register). Hmm. Meaning.. it's searching for the PE header backward. At some point it is going to find this header. Maybe :)
So I started searching for all occurrences of 4D 5A in memory. We got a hit at 3D0181 and with more text which looks like an EXE.
So I set a conditional break-point just after the CMP to break when ECX = 3D0181.
As expected it breaks. Now EDX is compared to 5A 4D. I'd expected that it would find a match and move on. Strangely the program never found a match and kept crashing. So I looked at what EDX was getting set to when the program broke.
Interestingly it got set to 5A CC and not 5A 4D. In other words the instruction CMP EDX,5A4D was failing.. coz EDX was getting set to 5A CC instead and hence never finding a match. Why?
Well CC in assembly is a software breakpoint .. or INT 3 as is often known. I'd set a conditional breakpoint..right? So the 4D at that point was temporarily overwritten by CC and thus the match failed and the app felt that there was no PE file there at all... when in reality there was. Here's a screenshot of what EDX actually contained.
But we know for sure... that there IS a PE file here. Right? So I edited EDX at run-time (and cheated a bit :)) and made it 5A 4D so that the match would succeed.
Suddenly all the code after all started to make sense...and all the right branches started getting selected, which meant I was on the right track. The biggest hint was that the next CMP which compared against 45 50 (start of PE header) succeeded and I exited the "search for PE header" loop. Which means that the header was found. Nice.
There was more memory allocated at 3D09E4 where the entire EXE was copied into 3D0000. Load Library was then called a couple of times at 3D0A9A and the addresses for a ton of functions in kernel32.dll and user32.dll were obtained.
Then I got bored and tried running it after this point to see if it'd give me a flag.. but nope.. program exited again. Aargh :(. More single stepping. I eventually came up to a call at 3D0D46. This call suddenly called to some code which was quite far from 3D0246... it called to 320A0B. Hmm. Interesting..
Eventually I managed to isolate which function was causing the code to exit. I followed the path 3D0915 - 3D097C - 3D0180 - 3D01130. And then I saw this...
So there's 3 CMP instructions ..comparing 3 different locations on the stack to 16,2 and 7E6 in hex and if they "fail" jumping to the end of the code which is 3D0171 (Screenshot shows 261171 because I wrote this blog over a couple of days and the addresses changed :D... just replace 261 with 3D0 and continue reading).
What is it looking at? Lets convert all those 3 to decimal - and it comes out to 22, 2 and 2022. Hmm. 22-2-2022. 22nd February 2022. And look at the call just before that - GetSystemTime. What'll happen if we change our system date to 22nd Feb 2022 and proceed? Let's try.
No.. that didn't work and the program still exited. So there's something else which is calculating those numbers so there is an exact match. We could sit and play around and possibly find the right match..but maybe..we do not need to and can just patch the 3 jumps. I just toggled the ZF thrice... and passed all the conditions so the program exited normally.
No more changes....and I eventually made my way over to 3D009A where there seemed to be some kind of comparison happening with all the sections of the executable..and the right path chosen when one landed on .bss.. one of the sections.
Then there seemed to be a bunch of junk copied over to 18F5CC. But I looked to be coming closer.
And then finally, there seemed to be an XOR with 5E and a MessageBox popping up with..a FLAG?
Maybe..maybe. Yessss.. Finally :)
Unfortunately I couldn't submit the flag since the contest was long long over. But still... it was nice to finish the challenge :)
While the challenge finished a while ago, I was poking at it a bit even after the challenge finished and finally managed to get a flag. In this post, I wanted to talk a little bit about the various obstacles that were in the way and how I circumvented them.
The first thing that I noticed was that directly running the executable caused it to terminate. So I started digging into it a bit more and found that there were a couple of checks that the exe performed.
- Check if it was being executed in a 64 bit environment
- Check if it was being run inside a debugger
A quick patch of the JZ to a JNZ bypassed both these protections and we were able to proceed without a problem.
After a while at 4010CF VirtualAlloc was called and a section of memory allocated to write "something into" - we don't know what at this time. The next problem was when a CALL was made at 40110B - which called into this section.
Going to that place in memory showed a lot of weird weird code which did not look right at all. Have a look at the screenshot.
Trying to run this code at this point caused an Access Violation straightaway. So I started single stepping the code, and immediately found that code a little lower down started changing. This meant that this was some kind of self modifying executable - a lot of packers use this trick.
I single stepped up to a point and ran again but it crashed again. Single step again - the next section made sense now. I repeated this for quite a while until the entire section started to make sense. What was happening, was that a small section of code would decrypt the next section. The next section would then run and decrypt the next bit. And so on ... until the entire bit unpacked itself.
Here is a screenshot of a packed section.
..And the same section after it's unpacked.
Single stepping the code until 3D0194 eventually decrypted every single bit of the code.
The unpacked code is at 3D0791 (red highlight in screen-shot above). This is how most packers behave - unpack the code to a different location and run it from there. Notice also all those junk ADD instructions after the CALL - more signs that the unpacking ends here. Lets hop over to 3D0791 now and see what's there.
Look at the last instruction and the red highlighted bit below. There is a CMP with 5A4D there and something happening after that. Hmm. 5A4D = MZ in Ascii. And MZ is the start of an EXE file. So it looks like it's searching for the start of an EXE file in memory. That too probably is what the unpacker has done - unpacked the real EXE file somewhere into memory. Where? We don't know as of now.
The red highlighted bits do have MZ and PE - things which lead us to think that the file's somewhere near.. but the other bits like "This program cannot be run in DOS mode.." .. are nowhere near. So maybe... it's not this bit which is the EXE but somewhere else. Where though?
Lets run the code after this and see what happens. We hit F9 and the code starts looping backward .. each time decrementing the place it searches by 1 (ECX register). Hmm. Meaning.. it's searching for the PE header backward. At some point it is going to find this header. Maybe :)
So I started searching for all occurrences of 4D 5A in memory. We got a hit at 3D0181 and with more text which looks like an EXE.
So I set a conditional break-point just after the CMP to break when ECX = 3D0181.
As expected it breaks. Now EDX is compared to 5A 4D. I'd expected that it would find a match and move on. Strangely the program never found a match and kept crashing. So I looked at what EDX was getting set to when the program broke.
Interestingly it got set to 5A CC and not 5A 4D. In other words the instruction CMP EDX,5A4D was failing.. coz EDX was getting set to 5A CC instead and hence never finding a match. Why?
Well CC in assembly is a software breakpoint .. or INT 3 as is often known. I'd set a conditional breakpoint..right? So the 4D at that point was temporarily overwritten by CC and thus the match failed and the app felt that there was no PE file there at all... when in reality there was. Here's a screenshot of what EDX actually contained.
But we know for sure... that there IS a PE file here. Right? So I edited EDX at run-time (and cheated a bit :)) and made it 5A 4D so that the match would succeed.
Suddenly all the code after all started to make sense...and all the right branches started getting selected, which meant I was on the right track. The biggest hint was that the next CMP which compared against 45 50 (start of PE header) succeeded and I exited the "search for PE header" loop. Which means that the header was found. Nice.
There was more memory allocated at 3D09E4 where the entire EXE was copied into 3D0000. Load Library was then called a couple of times at 3D0A9A and the addresses for a ton of functions in kernel32.dll and user32.dll were obtained.
Then I got bored and tried running it after this point to see if it'd give me a flag.. but nope.. program exited again. Aargh :(. More single stepping. I eventually came up to a call at 3D0D46. This call suddenly called to some code which was quite far from 3D0246... it called to 320A0B. Hmm. Interesting..
Eventually I managed to isolate which function was causing the code to exit. I followed the path 3D0915 - 3D097C - 3D0180 - 3D01130. And then I saw this...
So there's 3 CMP instructions ..comparing 3 different locations on the stack to 16,2 and 7E6 in hex and if they "fail" jumping to the end of the code which is 3D0171 (Screenshot shows 261171 because I wrote this blog over a couple of days and the addresses changed :D... just replace 261 with 3D0 and continue reading).
What is it looking at? Lets convert all those 3 to decimal - and it comes out to 22, 2 and 2022. Hmm. 22-2-2022. 22nd February 2022. And look at the call just before that - GetSystemTime. What'll happen if we change our system date to 22nd Feb 2022 and proceed? Let's try.
No.. that didn't work and the program still exited. So there's something else which is calculating those numbers so there is an exact match. We could sit and play around and possibly find the right match..but maybe..we do not need to and can just patch the 3 jumps. I just toggled the ZF thrice... and passed all the conditions so the program exited normally.
No more changes....and I eventually made my way over to 3D009A where there seemed to be some kind of comparison happening with all the sections of the executable..and the right path chosen when one landed on .bss.. one of the sections.
Then there seemed to be a bunch of junk copied over to 18F5CC. But I looked to be coming closer.
And then finally, there seemed to be an XOR with 5E and a MessageBox popping up with..a FLAG?
Maybe..maybe. Yessss.. Finally :)
Unfortunately I couldn't submit the flag since the contest was long long over. But still... it was nice to finish the challenge :)
3 comments:
What debugger is that you're using?
That's just OllyDbg with the colours changed.
Thanks a lot Arvind.
Post a Comment