The original console app from challenge #1 and #2 is back with a vengeance.
When you finish, you'll be surprised the code fits within this 5 k executable!
A hex dump of the first few bytes of the supplied file reveals that it is a Windows PE executable image.
Renaming to EXE and launching from the command line begins the challenge with a taunting password prompt. Typing the wrong
password results in the familiar "You are failure" message:
STATIC ANALYSIS? -- DON'T WASTE YOUR TIME:
Lets see what IDA can tell us about the executable. Right away, IDA gives us "The imports segment seems to be
destroyed..." warning. This has never stopped us before, so we continue to the "Exports" tab to follow
the entry point labeled "start". The entry point begins with a "JMP start_0" instruction. Following the target,
we are taken to a chunk of code with the "sp-analysis failed" error in red a few lines down. The code makes room for a DWORD on
the stack, and sets it to 401091 in a non-straightforward way, just before executing a RET instruction. Because RET is designed to pop
the value off at ESP and jump to it, we jump to address 401091. Right off the bat, IDA is confused, probably
because the only path into this block was a JMP, not a CALL, so the RET has confused it.
This is one of the many anti-disassembly tricks you'll see in this challenge.
If you scroll to the top of the disassembly window, you'll notice IDA has found a familiar-looking function that
invokes GetStdHandle() twice, then WriteFile(), and finally ReadFile().
Following the call to ReadFile() is a call to what would seem to be the validation function.
Upon return from the validation function are the branches to output either the success or failure messages. This entire function is a decoy.
One clue is the password prompt only displays "I have evolved since the first challenge." without the rest.
The function is not referenced by the program during an actual run and the program crashes after the validation
function is called if you force the debugger to run the code by manually setting EIP.
The validation function self-destructs the remainder of calling function by writing some invalid opcodes
where the return address is pointing that cause an an "Illegal Instruction" exception when the validation function returns.
While it may have been used during development to get the opcodes needed to plug into different parts of
the program (we'll get to that in a minute), the author cleverly left it in to send you down the wrong path.
DEBUGGING THE APP:
IDA isn't "the best tool for the job" for this challenge. You'll soon see why manually fixing up the program in
IDA each time an anti-disassembly technique is reached would be a lost cause. More mileage can be made in the
debugger. Fire up the program in OllyDbg and you should find yourself at the entry point 401C48:
Since this program intentionally avoids making proper use of CALL/RET pair as one of its anti-disassembly
techniques, you will almost exclusively be using the step-into (F7 key) feature to the step through
the code. Stepping-over a CALL that never returns is like running the app from its current position.
Step past the initial JMP and down to the RET in the next block of code. This unconventional use of
RET is anti-disassembly trick #1. It is only used to transfer control to 401091, but had the author used a "JMP
401091", static analysis tools like IDA would have marked the target address as the beginning of code and would
traverse it automatically. Because static disassemblers don't emulate code, but rather they focus
mostly on paths made through code via the branch control instructions like JMP, Jxx, matching CALL/RET, LOOP and the
like, they can't handle this construct. This causes the majority of the program to be marked as data and not
Since the CALL instruction is designed to push onto the stack the address of the next instruction prior to jumping to
the target address, the RET within the called block was designed to pop the return address pointed at by ESP into EIP
to resume execution just past the CALL. In this case, the code manually builds the
jump target address on the stack so it is the first to be pulled off and jumped to when the RET is executed, just like an
Stepping to 401091 appears to show us that we are executing raw data, rather than code instructions. The
processor is still executing instructions whether or not OllyDbg is confused as to whether we are in a code or
data block. OllyDbg thinks this is data because it scans the entry points automatically during module load
just like IDA does, and like IDA it didn't find a path into the code currently being executed. Because OllyDbg thinks this is data, it
uses a common assembler DB (Define Byte) syntax to display the raw hex bytes.
We just need to override the default analysis by forcing OllyDbg to "un-analyze" it. Start a multi-line
selection from 401091 down to the RETN instruction at 4010AA to contain all the "DB" byte lines. Right-click the selection and choose "Analysis"
-> "Remove analysis from selection" (or press CTRL-Bkspc). You will now see the data turn into assembly
instructions based from the selection's start. Always "un-analyze" code based from the current EIP to ensure
the disassembler's instruction interpretation is in sync. If you make a mistake, its not a huge deal as the processor will still execute the proper
instructions and OllyDbg automatically re-synchronizes its disassembler display at each step (more on this
below). As you step through instructions in this challenge, you will frequently need to inform
OllyDbg to interpret the bytes as code rather than data, so perform this step as often as necessary.
Considering that "XOR EAX,EAX" executing prior to the JE instruction essentially hardcodes the processor flags
necessary to ensure the jump is always taken and is thus semantically equivalent to a JMP instruction.
This is anti-disassembly trick #2.
When disassemblers encounter conditional jump instructions, they will analyze both the jump-taken
path and the jump-not-taken path (the next instruction). Code employing this technique cause the disassembler
to analyze code paths that have no chance in executing, such as the decoy function described above.
The reverse engineer can also waste time deciphering the purpose for the condition of the bogus branch, adding
to the overall complexity of the program. These
false branches often contain invalid instructions, or instructions that don't make any sense in the current
context such as privileges instructions in a user-mode program. A variation on this trick is a CALL that is never returned
to. Because the purpose of a CALL implies it will be returned-to, the code path that follows will be analyzed
automatically by the disassembler (and human) leading to the same problems as those caused by the unconditional
jumps made to look conditional.
Just when we've determined the JE instruction at 401093 is really an unconditional jump, this program wastes no
time in employing another trick. Notice that the target address of 401096 isn't listed anywhere in the address
column, as its value falls between those addresses shown. The program appears to be jumping in the middle of
another instruction based on how the disassembly window has aligned with the current flow of the opcode bytes,
but the processor only cares that the bytes at the target address represent a valid instruction. In a normal
program, jumping into the middle of opcode bytes meant for other instructions will likely lead to a crash
because the processor will misinterpret the instruction and every instructions that follows. For a specially
crafted program like this, the instructions being jumped into are actually valid, but are out of sync with the
disassembler display as the disassembler can only interpret any given opcode bytes as belonging to one
instruction at a time. If the disassembler has been tricked into displaying invalid instructions at a
particular address and then the program later jumps between them, those instructions will automatically turn
into completely different and valid instructions when the disassembler re-synchronizes with the current EIP.
You'll know OllyDbg re-synchronizes when the current disassembly instruction jumps to the top of the window for
seemingly no other reason. As a result, the previously executing instructions disappear and the new ones
appear. As long as the program doesn't generate an exception, the processor will happily execute any
instructions wherever you point it to. For obvious reasons, this adds to complexity and confusion of a program. This is anti-disassembly trick #3.
OllyDbg normally draws a nice line with a slight curve to show the target of a branching instruction, but this target's line looks like it was cut off
(see red arrow above). This is because a jump is targeting bytes currently occupied by other instructions.
Step past the JE instruction and you'll see the disassembly window automatically
re-synchronize to reflect the current EIP instruction at a "MOV EDX,EAX" which was not there before. The opcodes
have not changed, only the interpretation has. The CALL instruction at 401095 is made up of opcode bytes E8 89
C2 74 09. When we jump 1 byte into this address, the processor interprets instructions starting with
89 C2 74 09 and so on. 89 C2 translates to the "MOV EDX, EAX" leaving 74 09 to be interpreted as a
JE instruction. Since the program intended on the execution path containing valid instructions, we can
ascertain that the originally viewed CALL instruction at 401095 was a bogus instruction (FYI: it actually points to
invalid memory and would crash the program if executed).
There are even certain instruction combinations that can can be cleverly crafted such that the same byte can be
used in two valid and working instructions as EIP runs through them. One simple example where a "JMP -1"
jumps back inside of itself to the address occupied by the FF byte. This alters the interpretation for
successive instructions producing the "INC EAX" from the "FF C0" combination.
JMP -1 |
EB | FF | C0
| INC EAX
Tip: While a program is paused, you may need to scroll around, follow jump links or explicitly visit
other addresses. If you lose track of where the current instruction is or the current instruction is no longer
in sync based on the current view, you can return to the current instruction by
double-clicking the address shown to the right of the EIP register in the CPU registers window.
MAKING A MAP:
A module employing one or more anti-disassembly tricks is usually known as obfuscated
code. Other tricks that don't fall under the anti-disassembly category can still further obfuscate
the program. These tricks include "dead" code: useless instructions that
don't affect the algorithm or output of the program. Another trick is constant
unfolding: using more instructions than necessary to assign or adjust a value by a constant. An example
of this was shown near the entry point of this challenge where 3 instructions were used to assign a constant
value to a memory location, rather than a single MOV instruction.
When manually stepping through obfuscated code in the debugger, or even code that is difficult to understand due to an optimizing
compiler or any number of other reasons, I recommend making a "map" of the disassembly. This will serve as a
makeshift static analysis worksheet since traditional static analysis is probably not an option. If there is a specialized tool designed for the specific
obfuscation method in a module you are analyzing, and you have access to such a tool, use it by all means.
When I make a map, I open up a text editor like notepad. In the debugger, when I arrive at the beginning of the
code I'm trying to understand, I copy and paste the disassembly for the current code block. When a branch
instruction (JMP, Jxx, CALL, etc.) jumps to another code block I make a little separator line below the current block
(such as '---') and paste a copy of the disassembly for the new block below it. Continue doing this until you
have all of the blocks for the code being analyzed. If the branch is unconditional
but made to look conditional, I add a comment stating that fact. I also comment CALL's that never return or
RET's used only
to jump instead of return from a subroutine. When branching instructions jump "between" instructions as
described earlier, no matter how close the new block of code is to the one performing the jump, you still treat it as a
separate block the same as any other block. Also when jumping "between" instructions, since the debugger's disassembly of previous instructions will disappear as
the disassembler re-synchronizes, you can easily look backwards by referring to your map. When
making your first pass, I recommend focusing primarily on building the different blocks of code as a
result of the branching instructions. For code that jumps several times between high and low addresses as a
layer of obfuscation, your map automatically removes this layer as it naturally represents the order of execution.
Because the map will contain memory addresses, you can quickly navigate to any spot in
your code and easily refer to the address for setting debugger breakpoints. At this point you can make as many passes as necessary to add comments, manually fold constants,
turn conditional jumps that are in fact unconditional into plain JMPs, remove boundaries between adjacent
blocks that are related (such as when code jumps around multiple consecutive times effectively doing nothing but trying to
be confusing, you remove the series of jumps), and any
other algorithm distilling methods you deem necessary such as converting assembly into pseudocode. Keep refining until you
have a grasp for what is going on.
One final anti-disassembly trick employed by this challenge is building "other code" directly on the stack and
executing them to further obscure the "sensitive" key functionality. This is anti-disassembly trick #4. No static
analysis tool I know of supports displaying the dynamic code built on the stack, but the map you build will (I
like to annotate this fact). Unless the code is self-modifying, the map will help reduce the
complexity of understanding the dynamic stack-built code.
Following the basic principles described above, below is a basic map from the the program's entry point
up to the ReadFile() API call where user input is gathered. This map only connects one block of code to the
next and has very little distillation going on. You can think of it as only pass #1. We won't distill this
code further because it is only getting us from the entry point to the ReadFile() call, and whatever obfuscated
steps it does, has nothing to do with the password algorithm. It is included here for educational
purposes as a real example of a map.
00401C48E9 32F8FFFFJMP0040147F;entry point0040147F83C4 FCADDESP, -400401482C70424 010000MOVDWORD PTR SS:[ESP], 100401489C10424 16ROLDWORD PTR SS:[ESP], 160040148D812C24 6FEFFFSUBDWORD PTR SS:[ESP], -109100401494C3RETN;jumps to 4010910040109131C0XOREAX, EAX0040109374 01JESHORT 00401096;always taken0040109689C2MOVEDX, EAX0040109874 09JESHORT 004010A3;always004010A3E8 F9FFFFFFCALL004010A1;never returns004010A1EB 06JMPSHORT 004010A9004010A989C3MOVEBX, EAX004010AB31EBXOREBX, EBP004010AD891C04MOVDWORD PTR SS:[EAX+ESP], EBX004010B085E4TESTESP, ESP004010B275 01JNESHORT 004010B5;always taken004010B589C1MOVECX, EAX004010B787E5XCHGEBP, ESP;ESP originally has 12FFC0004010B989C4MOVESP, EAX;ESP blown away here004010BBF7D4NOTESP; and here004010BD0F44E9CMOVEEBP, ECX004010C00F45E1CMOVNEESP, ECX004010C309ECORESP, EBP;ESP gets back its original value of 12FFC0004010C575 02JNESHORT 004010C9;always taken004010C9BE 01000000MOVESI, 1004010CE31FFXOREDI, EDI004010D074 01JESHORT 004010D3;always taken004010D3F7D7NOTEDI004010D5B9 06000000MOVECX, 6004010DA52PUSHEDX004010DBEB FFJMPSHORT 004010DC004010DCFFC0INCEAX004010DE48DECEAX004010DF58POPEAX004010E085C0TESTEAX, EAX004010E275 03JNESHORT 004010E7;always jump004010E474 01JESHORT 004010E7; with one or the other004010E70F45C2CMOVNEEAX, EDX004010EA85FCTESTESP, EDI004010EC50PUSHEAX004010EDE2 09LOOPSHORT 004010F8;used as an unconditional jump, decrementing ecx from 6 to 5004010F891XCHGEAX, ECX004010F90F45CECMOVNEECX, ESI004010FCC1C1 16ROLECX, 16004010FF81C1 F2100000ADDECX, 10F2;ecx appears to be loaded with a code address0040110575 01JNESHORT 00401108;always taken0040110851PUSHECX;code address 4010F2 stored on stack0040110991XCHGEAX, ECX0040110AEB FFJMPSHORT 0040110B0040110BFFC0INCEAX0040110D8B0414MOVEAX, DWORD PTR SS:[EDX+ESP]00401110E8 BE0A0000CALL00401BD3;never returns
The last instruction above jumps into the repeating portion below (401BD3) that makes use of the
LOOP instruction. When dealing with obfuscated code, sometimes the LOOP instruction is used as an unconditional
jump, but this is not the case here.
The loop executes 6 times (the initial value of ECX). When you step through the code
and confirm a LOOP instruction is actually performing a loop (that you aren't interested in), set a breakpoint
on the loop exit so you don't waste your time. The map helps you recognize places you've already been to, such
as a giant loop that you might otherwise step through for hours.
00401BD350PUSHEAX00401BD466:B8 EB05MOVAX, 5EB00401BD831C0XOREAX, EAX00401BDA74 FAJESHORT 00401BD6;always taken00401BD6EB 05JMPSHORT 00401BDD00401BDD8B4424 08MOVEAX, DWORD PTR SS:[ESP+8]00401BE174 01JESHORT 00401BE4;always taken00401BE4894424 04MOVDWORD PTR SS:[ESP+4], EAX00401BE858POPEAX00401BE9C2 0400RETN4;jumps to 4010F2004010F2E8 E4FFFFFFCALL004010DB;never returns004010DBEB FFJMPSHORT 004010DC004010DCFFC0INCEAX004010DE48DECEAX004010DF58POPEAX004010E085C0TESTEAX, EAX004010E275 03JNESHORT 004010E7;always jump004010E474 01JESHORT 004010E7; with one or the other004010E70F45C2CMOVNEEAX, EDX004010EA85FCTESTESP, EDI004010EC50PUSHEAX004010EDE2 09LOOPSHORT 004010F8;loop control - we loop 6 times until ECX becomes zero!004010EFE3 33JECXZSHORT 00401124;always taken; loop exits here004010F891XCHGEAX, ECX004010F90F45CECMOVNEECX, ESI004010FCC1C1 16ROLECX, 16004010FF81C1 F2100000ADDECX, 10F20040110575 01JNESHORT 00401108;always taken0040110851PUSHECX0040110991XCHGEAX, ECX0040110AEB FFJMPSHORT 0040110B0040110BFFC0INCEAX0040110D8B0414MOVEAX, DWORD PTR SS:[EDX+ESP]00401110E8 BE0A0000CALL00401BD3;bottom of loop
Now that we're out of that loop, the remainder of the code bounces around until you make it to the eventual ReadFile() API call.
You'll notice in the map comments that the system API calls were manually-built with code as opposed to using a
traditional import to make it harder to spot where and which APIs are being used.
0040112466:B8 EB05MOVAX, 5EB0040112831C0XOREAX, EAX0040112A74 FAJESHORT 00401126;always taken00401126EB 05JMPSHORT 0040112D0040112D31D2XOREDX, EDX0040112F74 02JESHORT 004011330040113183C4 42ADDESP, 420040113481C4 E0FEFFFFADDESP, -1200040113A85E4TESTESP, ESP0040113C74 14JESHORT 00401152;never taken0040113E89D7MOVEDI, EDX00401140C1E7 16SHLEDI, 1600401143B9 28000000MOVECX, 280040114889C8MOVEAX, ECX;get pointer to0040114AC1E0 02SHLEAX, 2; next stack0040114D01E0ADDEAX, ESP; location0040114F83E8 A8SUBEAX, -58;004011528910MOVDWORD PTR DS:[EAX], EDX;init current00401154F710NOTDWORD PTR DS:[EAX]; stack location00401156FF00INCDWORD PTR DS:[EAX]; with module base (i.e. 0x400000)004011582138ANDDWORD PTR DS:[EAX], EDI; copied in roundabout way from EDI0040115A74 03JESHORT 0040115F;one or the other will0040115C75 01JNESHORT 0040115F; always jump0040115FE2 E7LOOPSHORT 00401148;loops 28 times back to a couple lines above0040116189BC24 D40000MOVDWORD PTR SS:[ESP+0D4], EDI0040116831C0XOREAX, EAX0040116A74 01JESHORT 0040116D;always taken0040116D898424 080100MOVDWORD PTR SS:[ARG.66], EAX004011748D45 F8LEAEAX,[EBP-8]004011778D5C24 F0LEAEBX,[LOCAL.3]0040117BC703 89842410MOVDWORD PTR DS:[EBX], 1024848900401181C74424 F4 010MOVDWORD PTR SS:[LOCAL.2], C3000001; ASCII "NDX("00401189895C24 F8MOVDWORD PTR SS:[LOCAL.1], EBX0040118DC74424 FC 9D1MOVDWORD PTR SS:[LOCAL.0], 119D00401195017C24 FCADDDWORD PTR SS:[LOCAL.0], EDI;adds PE base 400000 to 119D to get 40119D0040119983EC 08SUBESP, 80040119CC3RETN;jumps to code on stack at 12FE78, xfering control to next instruction0012FE78898424 10010000MOVDWORD PTR SS:[ESP+110], EAX;this code is on the stack0012FE7FC3RETN;jumps to 40119D0040119D81B424 080100XORDWORD PTR SS:[ESP+108], 00004000004011A8818424 C00000ADDDWORD PTR SS:[ESP+0C0], 1736004011B381AC24 B80000SUBDWORD PTR SS:[ESP+0B8], -1736004011BEEB FFJMPSHORT 004011BF004011BFFFC0INCEAX004011C189F8MOVEAX, EDI004011C30D 68200000OREAX, 00002068004011C88B00MOVEAX, DWORD PTR DS:[EAX];EAX contains the address of WriteFile()004011CA838C24 FC0000ORDWORD PTR SS:[ESP+0FC], FFFFFFFF004011D2C74424 FC 010MOVDWORD PTR SS:[ESP-4], 1004011DAC74424 F0 898MOVDWORD PTR SS:[ESP-10], 8248489004011E2C16424 FC 16SHLDWORD PTR SS:[ESP-4], 16004011E775 02JNESHORT 004011EB;always taken004011EBC74424 F4 010MOVDWORD PTR SS:[ESP-0C], C3000001; ASCII "NDX("004011F3818424 D40000ADDDWORD PTR SS:[ESP+0D4], 1736004011FE895C24 F8MOVDWORD PTR SS:[ESP-8], EBX00401202814424 FC 0E1ADDDWORD PTR SS:[ESP-4], 120E0040120A83C4 F8ADDESP, -80040120DC3RETN;jumps to stack at 12FE780012FE78898424 08010000MOVDWORD PTR SS:[ESP+108], EAX;this code is on the stack0012FE7FC3RETN;jumps to 40120E0040120E89BC24 000100MOVDWORD PTR SS:[ESP+100], EDI0040121521BC24 FC0000ANDDWORD PTR SS:[ESP+0FC], EDI0040121C66:B8 EB05MOVAX, 5EB0040122031C0XOREAX, EAX0040122274 FAJESHORT 0040121E;always taken0040121EEB 05JMPSHORT 0040122500401225C18424 080100ROLDWORD PTR SS:[ESP+108], 80040122DC74424 3C 000MOVDWORD PTR SS:[ESP+3C], 000401235818424 F80000ADDDWORD PTR SS:[ESP+0F8], 173600401240818C24 F40000ORDWORD PTR SS:[ESP+0F4], 000017360040124B31C0XOREAX, EAX0040124D75 C7JNESHORT 004012160040124F894424 30MOVDWORD PTR SS:[ESP+30], EAX00401253818424 EC0000ADDDWORD PTR SS:[ESP+0EC], 17360040125E81AC24 E80000SUBDWORD PTR SS:[ESP+0E8], -173600401269EB FFJMPSHORT 0040126A0040126AFFC0INCEAX0040126C48DECEAX0040126D814C24 6C 361ORDWORD PTR SS:[ESP+6C], 0000173600401275818424 E40000ADDDWORD PTR SS:[ESP+0E4], 173600401280C74424 4C 040MOVDWORD PTR SS:[ESP+4C], 40040128881AC24 DC0000SUBDWORD PTR SS:[ESP+0DC], -173600401293818C24 D80000ORDWORD PTR SS:[ESP+0D8], 000017360040129E818C24 FC0000ORDWORD PTR SS:[ESP+0FC], 00001C27004012A989F8MOVEAX, EDI004012AB74 03JESHORT 004012B0;one or the other always004012AD75 01JNESHORT 004012B0; results in a jump004012B00D 5C200000OREAX, 0000205C004012B58B00MOVEAX, DWORD PTR DS:[EAX];EAX now contains address to GetStdHandle()004012B7890424MOVDWORD PTR SS:[ESP], EAX004012BA81AC24 D00000SUBDWORD PTR SS:[ESP+0D0], -1736004012C5818424 C80000ADDDWORD PTR SS:[ESP+0C8], 1736004012D0818C24 B40000ORDWORD PTR SS:[ESP+0B4], 00001736004012DBFF4424 30INCDWORD PTR SS:[ESP+30]004012DF75 01JNESHORT 004012E2;always taken004012E2C14C24 30 0ARORDWORD PTR SS:[ESP+30], 0A004012E7818424 B00000ADDDWORD PTR SS:[ESP+0B0], 1736004012F2C78424 1C0100MOVDWORD PTR SS:[ESP+11C], 0004012FD81AC24 940000SUBDWORD PTR SS:[ESP+94], -173600401308818C24 AC0000ORDWORD PTR SS:[ESP+0AC], 000017360040131375 01JNESHORT 00401316;always taken00401316C14424 4C 14ROLDWORD PTR SS:[ESP+4C], 140040131B818424 A40000ADDDWORD PTR SS:[ESP+0A4], 17360040132681AC24 000100SUBDWORD PTR SS:[ESP+100], -1C0200401331EB FFJMPSHORT 0040133200401332FFC0INCEAX00401334EB FFJMPSHORT 0040133500401335FFC0INCEAX0040133748DECEAX00401338EB FFJMPSHORT 0040133900401339FFC0INCEAX0040133BEB FFJMPSHORT 0040133C0040133CFFC0INCEAX0040133E48DECEAX0040133F81B424 080100XORDWORD PTR SS:[ESP+108], 0000172D0040134A8D45 FCLEAEAX,[EBP-4]0040134D75 01JNESHORT 00401350;always taken004013508D5C24 F0LEAEBX,[LOCAL.3]00401354C703 8984243CMOVDWORD PTR DS:[EBX], 3C2484890040135AC74424 F4 000MOVDWORD PTR SS:[LOCAL.2], C3000000; ASCII "INDX("00401362895C24 F8MOVDWORD PTR SS:[LOCAL.1], EBX00401366897C24 FCMOVDWORD PTR SS:[LOCAL.0], EDI0040136A814C24 FC 761ORDWORD PTR SS:[LOCAL.0], 000013760040137283C4 F8ADDESP, -800401375C3RETN;jumps to code on stack at 12FE800012FE78898424 3C000000MOVDWORD PTR SS:[ESP+3C], EAX;this code is on the stack0012FE7FC3RETN;jumps to 40137600401376818424 980000ADDDWORD PTR SS:[ESP+98], 173600401381818C24 A80000ORDWORD PTR SS:[ESP+0A8], 000017360040138C75 01JNESHORT 0040138F;always taken0040138F81AC24 900000SUBDWORD PTR SS:[ESP+90], -17360040139AC78424 100100MOVDWORD PTR SS:[ESP+110], 0004013A581AC24 8C0000SUBDWORD PTR SS:[ESP+8C], -1736004013B0818424 880000ADDDWORD PTR SS:[ESP+88], 1736004013BB66:B8 EB06MOVAX, 6EB004013BF31C0XOREAX, EAX004013C174 FAJESHORT 004013BD004013BDEB 06JMPSHORT 004013C5004013C58D45 FCLEAEAX,[EBP-4]004013C8898424 180100MOVDWORD PTR SS:[ESP+118], EAX004013CF818C24 840000ORDWORD PTR SS:[ESP+84], 00001736004013DA818424 800000ADDDWORD PTR SS:[ESP+80], 1736004013E589F8MOVEAX, EDI004013E70D 5C200000OREAX, 0000205C004013EC8B00MOVEAX, DWORD PTR DS:[EAX]004013EE8D5C24 F0LEAEBX,[ESP-10]004013F2897C24 FCMOVDWORD PTR SS:[ESP-4], EDI004013F6C703 89842410MOVDWORD PTR DS:[EBX], 10248489004013FCC74424 F4 000MOVDWORD PTR SS:[ESP-0C], C3000000; ASCII "INDX("00401404895C24 F8MOVDWORD PTR SS:[ESP-8], EBX00401408814424 FC 141ADDDWORD PTR SS:[ESP-4], 14140040141083EC 08SUBESP, 800401413C3RETN;jumps to stack at 12FE780012FE78898424 10000000MOVDWORD PTR SS:[ESP+10], EAX;this code is on the stack0012FE7FC3RETN;jumps to 40141400401414897C24 44MOVDWORD PTR SS:[ESP+44], EDI00401418814C24 78 361ORDWORD PTR SS:[ESP+78], 000017360040142081AC24 F00000SUBDWORD PTR SS:[ESP+0F0], -17360040142B66:B8 EB05MOVAX, 5EB0040142F31C0XOREAX, EAX0040143174 FAJESHORT 0040142D;always taken0040142DEB 05JMPSHORT 0040143400401434814424 74 361ADDDWORD PTR SS:[ESP+74], 17360040143CC78424 140100MOVDWORD PTR SS:[ESP+114], 1100401447814424 30 0B2ADDDWORD PTR SS:[ESP+30], 210B;stack has pointer to 40210B string "I have evolved since..."0040144F814C24 70 361ORDWORD PTR SS:[ESP+70], 0000173600401457816C24 68 CAESUBDWORD PTR SS:[ESP+68], -17360040145F814424 64 361ADDDWORD PTR SS:[ESP+64], 173600401467814C24 5C 361ORDWORD PTR SS:[ESP+5C], 000017360040146F75 01JNESHORT 00401472;always taken00401472C74424 58 000MOVDWORD PTR SS:[ESP+58], 00040147AE9 F3000000JMP0040157200401572897C24 28MOVDWORD PTR SS:[ESP+28], EDI004015768D45 FCLEAEAX,[EBP-4]00401579894424 54MOVDWORD PTR SS:[ESP+54], EAX0040157D817424 4C 862XORDWORD PTR SS:[ESP+4C], 000021860040158581AC24 CC0000SUBDWORD PTR SS:[ESP+0CC], -173600401590897C24 04MOVDWORD PTR SS:[ESP+4], EDI00401594C74424 50 320MOVDWORD PTR SS:[ESP+50], 320040159C818C24 BC0000ORDWORD PTR SS:[ESP+0BC], 00001736004015A78D45 F4LEAEAX,[EBP-0C]004015AA8D5C24 F0LEAEBX,[ESP-10]004015AEC703 8984244CMOVDWORD PTR DS:[EBX], 4C248489004015B4897C24 FCMOVDWORD PTR SS:[ESP-4], EDI004015B875 04JNESHORT 004015BE;we will jump one004015BA74 02JESHORT 004015BE; way or the other004015BEC74424 F4 000MOVDWORD PTR SS:[ESP-0C], C3000000; ASCII "INDX("004015C6895C24 F8MOVDWORD PTR SS:[ESP-8], EBX004015CA816C24 FC 26ESUBDWORD PTR SS:[ESP-4], -15DA004015D275 02JNESHORT 004015D6;always taken004015D683EC 08SUBESP, 8004015D9C3RETN;jumps to stack at 12FE780012FE78898424 4C000000MOVDWORD PTR SS:[ESP+4C], EAX;this is code on the stack0012FE7FC3RETN;jumps to 4015DA004015DA816C24 60 CAESUBDWORD PTR SS:[ESP+60], -1736004015E2817424 44 361XORDWORD PTR SS:[ESP+44], 00001736004015EA75 01JNESHORT 004015ED;always taken004015ED818424 9C0000ADDDWORD PTR SS:[ESP+9C], 1736004015F889F8MOVEAX, EDI004015FA0D 6C200000OREAX, 0000206C004015FF8B00MOVEAX, DWORD PTR DS:[EAX];EAX now has address of ReadFile()00401601894424 40MOVDWORD PTR SS:[ESP+40], EAX00401605C74424 34 570MOVDWORD PTR SS:[ESP+34], 570040160D81AC24 C40000SUBDWORD PTR SS:[ESP+0C4], -173600401618EB FFJMPSHORT 0040161900401619FFC0INCEAX0040161B75 01JNESHORT 0040161E;always taken0040161E818C24 A00000ORDWORD PTR SS:[ESP+0A0], 0000173600401629897C24 14MOVDWORD PTR SS:[ESP+14], EDI0040162D66:B8 EB05MOVAX, 5EB0040163131C0XOREAX, EAX0040163374 FAJESHORT 0040162F;always taken0040162FEB 05JMPSHORT 00401636004016368D45 F8LEAEAX,[EBP-8]004016398D5C24 F0LEAEBX,[ESP-10]0040163D85DBTESTEBX, EBX0040163F75 02JNESHORT 0040164300401643C703 89842430MOVDWORD PTR DS:[EBX], 3024848900401649C74424 F4 000MOVDWORD PTR SS:[ESP-0C], C3000000; ASCII "INDX("00401651895C24 F8MOVDWORD PTR SS:[ESP-8], EBX00401655C74424 FC 020MOVDWORD PTR SS:[ESP-4], 20040165DC16424 FC 15SHLDWORD PTR SS:[ESP-4], 150040166275 01JNESHORT 00401665;always taken00401665814C24 FC 711ORDWORD PTR SS:[ESP-4], 000016710040166D83EC 08SUBESP, 800401670C3RETN/jumps on stack at 12FE780012FE78898424 30000000MOVDWORD PTR SS:[ESP+30], EAX;this is code on stack0012FE7FC3RETN;jumps to 49167100401671817424 28 021XORDWORD PTR SS:[ESP+28], 00001C0200401679EB FFJMPSHORT 0040167A0040167AFFC0INCEAX0040167C89F8MOVEAX, EDI0040167E0D 68200000OREAX, 00002068004016838B00MOVEAX, DWORD PTR DS:[EAX];EAX now points to WriteFile()00401685894424 24MOVDWORD PTR SS:[ESP+24], EAX0040168966:B8 EB06MOVAX, 6EB0040168D31C0XOREAX, EAX0040168F74 FAJESHORT 0040168B;always taken0040168BEB 06JMPSHORT 00401693004016938D45 F8LEAEAX,[EBP-8]00401696894424 20MOVDWORD PTR SS:[ESP+20], EAX0040169AC74424 18 F5FMOVDWORD PTR SS:[ESP+18], -0B004016A2818424 E00000ADDDWORD PTR SS:[ESP+0E0], 1736004016AD816C24 14 14ESUBDWORD PTR SS:[ESP+14], -1BEC004016B566:B8 EB05MOVAX, 5EB004016B931C0XOREAX, EAX004016BB74 FAJESHORT 004016B7;always taken004016B7EB 05JMPSHORT 004016BE004016BE74 02JESHORT 004016C2;always taken004016C274 03JESHORT 004016C7004016C78D45 F4LEAEAX,[EBP-0C]004016CA8D5C24 F0LEAEBX,[ESP-10]004016CE74 04JESHORT 004016D4;we will always jump004016D075 02JNESHORT 004016D4; to 4016D4004016D4C703 89442414MOVDWORD PTR DS:[EBX], 14244489004016DAC74424 F4 C30MOVDWORD PTR SS:[ESP-0C], 0C3004016E2895C24 F8MOVDWORD PTR SS:[ESP-8], EBX004016E6895424 FCMOVDWORD PTR SS:[ESP-4], EDX004016EAC14424 FC 16ROLDWORD PTR SS:[ESP-4], 16004016EF75 03JNESHORT 004016F4;we will always jump004016F174 01JESHORT 004016F4; to 4016F4004016F4814424 FC 001ADDDWORD PTR SS:[ESP-4], 1700004016FC83C4 F8ADDESP, -8004016FFC3RETN;jumps to stack at 12FE780012FE78894424 14MOVDWORD PTR SS:[ESP+14], EAX;this is code on stack0012FE7CC3RETN;jumps to 40170000401700C74424 08 F6FMOVDWORD PTR SS:[ESP+8], -0A00401708895424 1CMOVDWORD PTR SS:[ESP+1C], EDX0040170CC16424 1C 16SHLDWORD PTR SS:[ESP+1C], 160040171175 01JNESHORT 00401714;always taken00401714814424 1C 021ADDDWORD PTR SS:[ESP+1C], 1C020040171C814C24 04 EC1ORDWORD PTR SS:[ESP+4], 00001BEC00401724816C24 7C CAESUBDWORD PTR SS:[ESP+7C], -17360040172CC3RETN;jumps to GetStdHandle(), and that API returns to 401BEC00401BEC53PUSHEBX00401BED75 04JNESHORT 00401BF3;nomatter what we00401BEF74 02JESHORT 00401BF3; jump to 401BF300401BF38B5C24 08MOVEBX, DWORD PTR SS:[ESP+8]00401BF78903MOVDWORD PTR DS:[EBX], EAX00401BF95BPOPEBX00401BFA74 03JESHORT 00401BFF;nomatter what00401BFC75 01JNESHORT 00401BFF; we jump to 401BFF00401BFFC2 0400RETN4;jumps to GetStdHandle, the API returns to 401BEC00401BEC53PUSHEBX00401BED75 04JNESHORT 00401BF3;nomatter what00401BEF74 02JESHORT 00401BF3; we jump to 401BF300401BF38B5C24 08MOVEBX, DWORD PTR SS:[ESP+8]00401BF78903MOVDWORD PTR DS:[EBX], EAX00401BF95BPOPEBX00401BFA74 03JESHORT 00401BFF;nomatter what00401BFC75 01JNESHORT 00401BFF; we jump 401BFF00401BFFC2 0400RETN4;jumps to 401C02, which drops to next line00401C0253PUSHEBX00401C0374 03JESHORT 00401C08;nomatter what00401C0575 01JNESHORT 00401C08; we jump to 401C0800401C088B5C24 0CMOVEBX, DWORD PTR SS:[ESP+0C]00401C0C8B1BMOVEBX, DWORD PTR DS:[EBX]00401C0E895C24 0CMOVDWORD PTR SS:[ESP+0C], EBX00401C1275 03JNESHORT 00401C17;nomatter what00401C1474 01JESHORT 00401C17; we jump to 401C1700401C175BPOPEBX00401C18C3RETN;jumps to WriteFile which returns to 401C02 (first output to console is "I have evolved...")00401C0253PUSHEBX00401C0374 03JESHORT 00401C08;nomatter what00401C0575 01JNESHORT 00401C08; we jump to 401C0800401C088B5C24 0CMOVEBX, DWORD PTR SS:[ESP+0C]00401C0C8B1BMOVEBX, DWORD PTR DS:[EBX]00401C0E895C24 0CMOVDWORD PTR SS:[ESP+0C], EBX00401C1275 03JNESHORT 00401C17;nomatter what00401C1474 01JESHORT 00401C17; we jump to 401C1700401C175BPOPEBX00401C18C3RETN;jumps to ReadFile which returns to 401736
The code above doesn't need to be analyzed too much because we were simply using it to understand making a
map of obfuscated code. To skip directly to the ReadFile() API call, as this is usually "nearer" to the
validation code we need to understand, we need to set an API breakpoint. In OllyDbg, go to the "View" menu
-> "Executable modules" (or ALT-e). In the modules window, right click on the DLL that contains the desired
API call. In this case, ReadFile() is in kernel32, so right-click kernel32 -> "Show names" (or CTRL+n). Sort
the column by name (click the header button) and/or begin typing "readfile". When the ReadFile export is
selected, double-click it or hit <ENTER> to go to the function's prologue. Set your breakpoint here and
run the program. Once hit, step-over instructions until you reach ReadConsoleA, where you must switch back to
the debugged-console and enter the password before the debugger regains control. Continue stepping-over until
you exit ReadFile() via the "RETN 14".
Now we want to set a breakpoint on whatever code starts reading your input (to skip past whatever bouncing
around may happen between here and there).
At this point you'll be back in the challenge module at 401736. Locate your "user input" data by scrolling the stack
up a bit until you see you it (you did type something that will stand out, right?) Right click on this stack
location and "follow" the pointer into the memory dump window. Right
click on the first byte of your input and set a read breakpoint. Now run the program to the breakpoint. You
should find yourself near the top of the algorithm loop at 401A9C. As explained above, you'll have to have
OllyDbg unanalyze this code so you can view the instructions rather than the data representation.
Without including a larger map of the obfuscated code that comprises the password validation routine, I'll leave
debugging that section of code as an exercise to the reader. Be patient with this one. I made 4 passes through
the algorithm loop in the debugger before I had a clear picture of what was going on, taking and refining my
notes with each pass. The code is intentionally complex so as to hide the
relatively simple algorithm. The code employs all obfuscated techniques described thus far.
It is a doable task if you just have some patience and expect that you might not understand all that is
going on with the first few loop iterations. If you prefer not to make a map and solely use the debugger, use OllyDbg's
"comment" feature to annotate instructions by pressing the ";" key. The comments will be kept across multiple
debugging sessions, so when you see your comments re-appear, you'll know you are in a familiar part.
Early on in the loop, it is apparent a successful password is 41 characters. You'll soon find the
"key/password" is divided amongst 4 different tables. The first table is what I called the offset table,
indexed by the current character position by processing the user input from left to right. Once this offset
retrieved for the current input character position, it is used as an index into each of the other 3 tables. The
2nd table contains XOR bytes, the 3rd shift bytes, and the 4th a "magic" DWORD value whose least-significant
byte was the expected encrypted password character. The most difficult part for me was locating where the
expected password characters were located, as the code performing this comparison was built on the fly and
executed on the stack in a different position with each iteration of the loop. Making matters worse, the
comparison avoided the use of CMP which would have stood out and instead used a "CMPXCHG BL, DL" instruction for
an *implied* comparison between the destination operand and the AL register (where the expected password character was)!
I missed this on multiple passes because the AL register wasn't listed as an
operand and therefore wasn't on my "radar". This is why it is handy to have your Intel x86 assembly reference
handy. On top of that, the result of the comparison that sent you to the "failure" branch was delayed until the
loop body executed the number of times of the length of the expected password (regardless of the user input
size)! If you didn't type in a full 41 characters, 41 bytes of whatever artifacts were in the user input buffer
were compared regardless.
As previously mentioned, the important parts dealing with the key were executed by code built on the stack by
other code. Setting breakpoints on the stack aren't very useful in general because the stack is constantly
growing and shrinking causing those breakpoints to be constantly triggered by other code. This is why making a map
is useful; the location of the currently executing code on the stack isn't important so much as the
instructions, because they are mostly the same, just accessing different indexes into the same buffers.
In the map snippet below, the stack addresses reflect those of the first loop iteration for the encrypted
password comparison code. An understanding
of the algorithm isn't usually affected by changing stack addresses for stack code unless the generated code is
drastically different between iterations. In this case, the code was always a "CMPXCHG BL,DL" followed by a RETN.
After you do multiple passes through the loop you'll be able to identify the pattern with the code being placed on the
stack to distill the important algorithm pieces. Here's the snippet from my map where the encrypted character comparison was
built and executed on the stack:
00401B4688C4MOVAH, AL;AH = AL - a copy of the encrypted (XORed and SHIFTED) input char00401B4896XCHGEAX, ESI;ESI=0040C3C3 EAX=100401B4975 04JNESHORT 00401B4F00401B4B74 02JESHORT 00401B4F00401B4FC74424 F4 0FBMOVDWORD PTR SS:[ESP-0C], C3D3B00F;place "0F B0 D3 C3" opcodes on stack (i.e. cmpxchg bl, dl; ret)00401B5768 7115845CPUSH5C841571;portion of unfolded constant (see below)00401B5C54PUSHESP;push current ESP; will later point at opcodes00401B5D816C24 04 07FSUBDWORD PTR SS:[ESP+4], 5C43FA07;unfolded constant now 401B6A (jump addres after opcodes execute)00401B65832C24 08SUBDWORD PTR SS:[ESP], 8;adjust previous ESP to point at opcodes00401B69C3RETN;execute opcodes on stack0012FDF80FB0D3CMPXCHGBL, DL;ENCRYPTED CHAR COMPARISON running on stack; compare AL to BL; if equal, assign BL = DL0012FDFBC3RETN;jump to 401B6A00401B6A0F44C2CMOVEEAX, EDX;idx=0 EAX=0 EDX=100401B6D85C0TESTEAX, EAX
NOTE: Despite the limited usefulness of stack breakpoints, that doesn't mean there aren't instances where they
are useful. In this challenge I did in fact set a memory write breakpoint at a specific stack location to figure out which
routine was responsible for placing the "You are failure" message to be picked up by WriteFile().
Once I had an understanding of the algorithm, I used breakpoints to manually gather the values
from the XOR, SHIFT and MAGIC tables for each position of the password. I hardcoded these tables into a small
C++ program to run the algorithm in reverse on the encrypted password (stored in the low byte of the "magic" table).
The program source is shown below:
byte arXor =
byte arRot =
dword arMagic =
char* p = szTemp;
const char* pEnd = p + MAX_ARRAY_ITEMS(szTemp);
//loop throught the number of entries in the array
for (uint i=0; i<MAX_ARRAY_ITEMS(arMagic); ++i)
//current encrypted key is low byte of magic number
byte chCur = *(byte*)(arMagic+i);
byte uRot = arRot[i];
byte uXor = arXor[i];
mov al,chCur //load encrypted char
mov cl,uRot //load rotate value
mov dl,uXor //load xor value
ror al,cl //perform rotate
xor al,dl //perform xor
mov chCur,al //load result into variable
//store decrypted character
if (p>=pEnd-1) break;
*p++ = *((char*)&chCur);
*p = 0;
printf("result is \"%s\"\n",szTemp);
CRACKING THE PASSWORD:
After building the program, we run it to produce the decrypted password. The result is then double-checked
in the challenge program.
The official solution confirmed my findings about the decoy
function at 401000 (the default entry point used by Microsoft linkers) . I suspect the decoy resembles the original unobfuscated
version of the program. I didn't catch the use of the
NtGlobalFlag "debugger present" check that was added to throw off the results when the program was running
in a debugger. This didn't affect me because I wasn't using the debugger to test password characters, such as with a brute
force method. The author ultimately developed a Python script using the same algorithm as in
my C++ decoder.
Response from Is_th1s_3v3n_mai_finul_foarm@flare-on.com:
Subject: FLARE-On Challenge #9 Completed!
Date: Fri, 21 Aug 2015 03:15:52 -0400
When you inevitably take over the world, just remember that I was one of the nice ones and I absolutely did not try to break you with difficult reversing challenges. Don't give up, you are so close to the prize!
The password is once again "flare"