|
filename: |
|
you_are_very_good_at_this |
|
DOWNLOAD |
md5 | | 51105503d77689facbf2b8a8b4fd040b |
size | | 4 k (4,608 bytes) |
type | | Win32 Console App |
Original FLARE Author | | Nick Harbour |
|
tool: |
|
OllyDbg 2.01 / Debugger |
|
Visit Website |
tool: |
|
Your favorite C/C++ compiler |
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:
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.
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:
00401C48 E9 32F8FFFF JMP 0040147F |
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
code.
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
unconditional jump.
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.
__
v \
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.
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.
00401C48 E9 32F8FFFF JMP 0040147F ;entry point
0040147F 83C4 FC ADD ESP, -4
00401482 C70424 010000 MOV DWORD PTR SS:[ESP], 1
00401489 C10424 16 ROL DWORD PTR SS:[ESP], 16
0040148D 812C24 6FEFFF SUB DWORD PTR SS:[ESP], -1091
00401494 C3 RETN ;jumps to 401091
00401091 31C0 XOR EAX, EAX
00401093 74 01 JE SHORT 00401096 ;always taken
00401096 89C2 MOV EDX, EAX
00401098 74 09 JE SHORT 004010A3 ;always
004010A3 E8 F9FFFFFF CALL 004010A1 ;never returns
004010A1 EB 06 JMP SHORT 004010A9
004010A9 89C3 MOV EBX, EAX
004010AB 31EB XOR EBX, EBP
004010AD 891C04 MOV DWORD PTR SS:[EAX+ESP], EBX
004010B0 85E4 TEST ESP, ESP
004010B2 75 01 JNE SHORT 004010B5 ;always taken
004010B5 89C1 MOV ECX, EAX
004010B7 87E5 XCHG EBP, ESP ;ESP originally has 12FFC0
004010B9 89C4 MOV ESP, EAX ;ESP blown away here
004010BB F7D4 NOT ESP ; and here
004010BD 0F44E9 CMOVE EBP, ECX
004010C0 0F45E1 CMOVNE ESP, ECX
004010C3 09EC OR ESP, EBP ;ESP gets back its original value of 12FFC0
004010C5 75 02 JNE SHORT 004010C9 ;always taken
004010C9 BE 01000000 MOV ESI, 1
004010CE 31FF XOR EDI, EDI
004010D0 74 01 JE SHORT 004010D3 ;always taken
004010D3 F7D7 NOT EDI
004010D5 B9 06000000 MOV ECX, 6
004010DA 52 PUSH EDX
004010DB EB FF JMP SHORT 004010DC
004010DC FFC0 INC EAX
004010DE 48 DEC EAX
004010DF 58 POP EAX
004010E0 85C0 TEST EAX, EAX
004010E2 75 03 JNE SHORT 004010E7 ;always jump
004010E4 74 01 JE SHORT 004010E7 ; with one or the other
004010E7 0F45C2 CMOVNE EAX, EDX
004010EA 85FC TEST ESP, EDI
004010EC 50 PUSH EAX
004010ED E2 09 LOOP SHORT 004010F8 ;used as an unconditional jump, decrementing ecx from 6 to 5
004010F8 91 XCHG EAX, ECX
004010F9 0F45CE CMOVNE ECX, ESI
004010FC C1C1 16 ROL ECX, 16
004010FF 81C1 F2100000 ADD ECX, 10F2 ;ecx appears to be loaded with a code address
00401105 75 01 JNE SHORT 00401108 ;always taken
00401108 51 PUSH ECX ;code address 4010F2 stored on stack
00401109 91 XCHG EAX, ECX
0040110A EB FF JMP SHORT 0040110B
0040110B FFC0 INC EAX
0040110D 8B0414 MOV EAX, DWORD PTR SS:[EDX+ESP]
00401110 E8 BE0A0000 CALL 00401BD3 ;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.
00401BD3 50 PUSH EAX
00401BD4 66:B8 EB05 MOV AX, 5EB
00401BD8 31C0 XOR EAX, EAX
00401BDA 74 FA JE SHORT 00401BD6 ;always taken
00401BD6 EB 05 JMP SHORT 00401BDD
00401BDD 8B4424 08 MOV EAX, DWORD PTR SS:[ESP+8]
00401BE1 74 01 JE SHORT 00401BE4 ;always taken
00401BE4 894424 04 MOV DWORD PTR SS:[ESP+4], EAX
00401BE8 58 POP EAX
00401BE9 C2 0400 RETN 4 ;jumps to 4010F2
004010F2 E8 E4FFFFFF CALL 004010DB ;never returns
004010DB EB FF JMP SHORT 004010DC
004010DC FFC0 INC EAX
004010DE 48 DEC EAX
004010DF 58 POP EAX
004010E0 85C0 TEST EAX, EAX
004010E2 75 03 JNE SHORT 004010E7 ;always jump
004010E4 74 01 JE SHORT 004010E7 ; with one or the other
004010E7 0F45C2 CMOVNE EAX, EDX
004010EA 85FC TEST ESP, EDI
004010EC 50 PUSH EAX
004010ED E2 09 LOOP SHORT 004010F8 ;loop control - we loop 6 times until ECX becomes zero!
004010EF E3 33 JECXZ SHORT 00401124 ;always taken; loop exits here
004010F8 91 XCHG EAX, ECX
004010F9 0F45CE CMOVNE ECX, ESI
004010FC C1C1 16 ROL ECX, 16
004010FF 81C1 F2100000 ADD ECX, 10F2
00401105 75 01 JNE SHORT 00401108 ;always taken
00401108 51 PUSH ECX
00401109 91 XCHG EAX, ECX
0040110A EB FF JMP SHORT 0040110B
0040110B FFC0 INC EAX
0040110D 8B0414 MOV EAX, DWORD PTR SS:[EDX+ESP]
00401110 E8 BE0A0000 CALL 00401BD3 ;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.
00401124 66:B8 EB05 MOV AX, 5EB
00401128 31C0 XOR EAX, EAX
0040112A 74 FA JE SHORT 00401126 ;always taken
00401126 EB 05 JMP SHORT 0040112D
0040112D 31D2 XOR EDX, EDX
0040112F 74 02 JE SHORT 00401133
00401131 83C4 42 ADD ESP, 42
00401134 81C4 E0FEFFFF ADD ESP, -120
0040113A 85E4 TEST ESP, ESP
0040113C 74 14 JE SHORT 00401152 ;never taken
0040113E 89D7 MOV EDI, EDX
00401140 C1E7 16 SHL EDI, 16
00401143 B9 28000000 MOV ECX, 28
00401148 89C8 MOV EAX, ECX ;get pointer to
0040114A C1E0 02 SHL EAX, 2 ; next stack
0040114D 01E0 ADD EAX, ESP ; location
0040114F 83E8 A8 SUB EAX, -58 ;
00401152 8910 MOV DWORD PTR DS:[EAX], EDX ;init current
00401154 F710 NOT DWORD PTR DS:[EAX] ; stack location
00401156 FF00 INC DWORD PTR DS:[EAX] ; with module base (i.e. 0x400000)
00401158 2138 AND DWORD PTR DS:[EAX], EDI ; copied in roundabout way from EDI
0040115A 74 03 JE SHORT 0040115F ;one or the other will
0040115C 75 01 JNE SHORT 0040115F ; always jump
0040115F E2 E7 LOOP SHORT 00401148 ;loops 28 times back to a couple lines above
00401161 89BC24 D40000 MOV DWORD PTR SS:[ESP+0D4], EDI
00401168 31C0 XOR EAX, EAX
0040116A 74 01 JE SHORT 0040116D ;always taken
0040116D 898424 080100 MOV DWORD PTR SS:[ARG.66], EAX
00401174 8D45 F8 LEA EAX, [EBP-8]
00401177 8D5C24 F0 LEA EBX, [LOCAL.3]
0040117B C703 89842410 MOV DWORD PTR DS:[EBX], 10248489
00401181 C74424 F4 010 MOV DWORD PTR SS:[LOCAL.2], C3000001 ; ASCII "NDX("
00401189 895C24 F8 MOV DWORD PTR SS:[LOCAL.1], EBX
0040118D C74424 FC 9D1 MOV DWORD PTR SS:[LOCAL.0], 119D
00401195 017C24 FC ADD DWORD PTR SS:[LOCAL.0], EDI ;adds PE base 400000 to 119D to get 40119D
00401199 83EC 08 SUB ESP, 8
0040119C C3 RETN ;jumps to code on stack at 12FE78, xfering control to next instruction
0012FE78 898424 10010000 MOV DWORD PTR SS:[ESP+110], EAX ;this code is on the stack
0012FE7F C3 RETN ;jumps to 40119D
0040119D 81B424 080100 XOR DWORD PTR SS:[ESP+108], 00004000
004011A8 818424 C00000 ADD DWORD PTR SS:[ESP+0C0], 1736
004011B3 81AC24 B80000 SUB DWORD PTR SS:[ESP+0B8], -1736
004011BE EB FF JMP SHORT 004011BF
004011BF FFC0 INC EAX
004011C1 89F8 MOV EAX, EDI
004011C3 0D 68200000 OR EAX, 00002068
004011C8 8B00 MOV EAX, DWORD PTR DS:[EAX] ;EAX contains the address of WriteFile()
004011CA 838C24 FC0000 OR DWORD PTR SS:[ESP+0FC], FFFFFFFF
004011D2 C74424 FC 010 MOV DWORD PTR SS:[ESP-4], 1
004011DA C74424 F0 898 MOV DWORD PTR SS:[ESP-10], 8248489
004011E2 C16424 FC 16 SHL DWORD PTR SS:[ESP-4], 16
004011E7 75 02 JNE SHORT 004011EB ;always taken
004011EB C74424 F4 010 MOV DWORD PTR SS:[ESP-0C], C3000001 ; ASCII "NDX("
004011F3 818424 D40000 ADD DWORD PTR SS:[ESP+0D4], 1736
004011FE 895C24 F8 MOV DWORD PTR SS:[ESP-8], EBX
00401202 814424 FC 0E1 ADD DWORD PTR SS:[ESP-4], 120E
0040120A 83C4 F8 ADD ESP, -8
0040120D C3 RETN ;jumps to stack at 12FE78
0012FE78 898424 08010000 MOV DWORD PTR SS:[ESP+108], EAX ;this code is on the stack
0012FE7F C3 RETN ;jumps to 40120E
0040120E 89BC24 000100 MOV DWORD PTR SS:[ESP+100], EDI
00401215 21BC24 FC0000 AND DWORD PTR SS:[ESP+0FC], EDI
0040121C 66:B8 EB05 MOV AX, 5EB
00401220 31C0 XOR EAX, EAX
00401222 74 FA JE SHORT 0040121E ;always taken
0040121E EB 05 JMP SHORT 00401225
00401225 C18424 080100 ROL DWORD PTR SS:[ESP+108], 8
0040122D C74424 3C 000 MOV DWORD PTR SS:[ESP+3C], 0
00401235 818424 F80000 ADD DWORD PTR SS:[ESP+0F8], 1736
00401240 818C24 F40000 OR DWORD PTR SS:[ESP+0F4], 00001736
0040124B 31C0 XOR EAX, EAX
0040124D 75 C7 JNE SHORT 00401216
0040124F 894424 30 MOV DWORD PTR SS:[ESP+30], EAX
00401253 818424 EC0000 ADD DWORD PTR SS:[ESP+0EC], 1736
0040125E 81AC24 E80000 SUB DWORD PTR SS:[ESP+0E8], -1736
00401269 EB FF JMP SHORT 0040126A
0040126A FFC0 INC EAX
0040126C 48 DEC EAX
0040126D 814C24 6C 361 OR DWORD PTR SS:[ESP+6C], 00001736
00401275 818424 E40000 ADD DWORD PTR SS:[ESP+0E4], 1736
00401280 C74424 4C 040 MOV DWORD PTR SS:[ESP+4C], 4
00401288 81AC24 DC0000 SUB DWORD PTR SS:[ESP+0DC], -1736
00401293 818C24 D80000 OR DWORD PTR SS:[ESP+0D8], 00001736
0040129E 818C24 FC0000 OR DWORD PTR SS:[ESP+0FC], 00001C27
004012A9 89F8 MOV EAX, EDI
004012AB 74 03 JE SHORT 004012B0 ;one or the other always
004012AD 75 01 JNE SHORT 004012B0 ; results in a jump
004012B0 0D 5C200000 OR EAX, 0000205C
004012B5 8B00 MOV EAX, DWORD PTR DS:[EAX] ;EAX now contains address to GetStdHandle()
004012B7 890424 MOV DWORD PTR SS:[ESP], EAX
004012BA 81AC24 D00000 SUB DWORD PTR SS:[ESP+0D0], -1736
004012C5 818424 C80000 ADD DWORD PTR SS:[ESP+0C8], 1736
004012D0 818C24 B40000 OR DWORD PTR SS:[ESP+0B4], 00001736
004012DB FF4424 30 INC DWORD PTR SS:[ESP+30]
004012DF 75 01 JNE SHORT 004012E2 ;always taken
004012E2 C14C24 30 0A ROR DWORD PTR SS:[ESP+30], 0A
004012E7 818424 B00000 ADD DWORD PTR SS:[ESP+0B0], 1736
004012F2 C78424 1C0100 MOV DWORD PTR SS:[ESP+11C], 0
004012FD 81AC24 940000 SUB DWORD PTR SS:[ESP+94], -1736
00401308 818C24 AC0000 OR DWORD PTR SS:[ESP+0AC], 00001736
00401313 75 01 JNE SHORT 00401316 ;always taken
00401316 C14424 4C 14 ROL DWORD PTR SS:[ESP+4C], 14
0040131B 818424 A40000 ADD DWORD PTR SS:[ESP+0A4], 1736
00401326 81AC24 000100 SUB DWORD PTR SS:[ESP+100], -1C02
00401331 EB FF JMP SHORT 00401332
00401332 FFC0 INC EAX
00401334 EB FF JMP SHORT 00401335
00401335 FFC0 INC EAX
00401337 48 DEC EAX
00401338 EB FF JMP SHORT 00401339
00401339 FFC0 INC EAX
0040133B EB FF JMP SHORT 0040133C
0040133C FFC0 INC EAX
0040133E 48 DEC EAX
0040133F 81B424 080100 XOR DWORD PTR SS:[ESP+108], 0000172D
0040134A 8D45 FC LEA EAX, [EBP-4]
0040134D 75 01 JNE SHORT 00401350 ;always taken
00401350 8D5C24 F0 LEA EBX, [LOCAL.3]
00401354 C703 8984243C MOV DWORD PTR DS:[EBX], 3C248489
0040135A C74424 F4 000 MOV DWORD PTR SS:[LOCAL.2], C3000000 ; ASCII "INDX("
00401362 895C24 F8 MOV DWORD PTR SS:[LOCAL.1], EBX
00401366 897C24 FC MOV DWORD PTR SS:[LOCAL.0], EDI
0040136A 814C24 FC 761 OR DWORD PTR SS:[LOCAL.0], 00001376
00401372 83C4 F8 ADD ESP, -8
00401375 C3 RETN ;jumps to code on stack at 12FE80
0012FE78 898424 3C000000 MOV DWORD PTR SS:[ESP+3C], EAX ;this code is on the stack
0012FE7F C3 RETN ;jumps to 401376
00401376 818424 980000 ADD DWORD PTR SS:[ESP+98], 1736
00401381 818C24 A80000 OR DWORD PTR SS:[ESP+0A8], 00001736
0040138C 75 01 JNE SHORT 0040138F ;always taken
0040138F 81AC24 900000 SUB DWORD PTR SS:[ESP+90], -1736
0040139A C78424 100100 MOV DWORD PTR SS:[ESP+110], 0
004013A5 81AC24 8C0000 SUB DWORD PTR SS:[ESP+8C], -1736
004013B0 818424 880000 ADD DWORD PTR SS:[ESP+88], 1736
004013BB 66:B8 EB06 MOV AX, 6EB
004013BF 31C0 XOR EAX, EAX
004013C1 74 FA JE SHORT 004013BD
004013BD EB 06 JMP SHORT 004013C5
004013C5 8D45 FC LEA EAX, [EBP-4]
004013C8 898424 180100 MOV DWORD PTR SS:[ESP+118], EAX
004013CF 818C24 840000 OR DWORD PTR SS:[ESP+84], 00001736
004013DA 818424 800000 ADD DWORD PTR SS:[ESP+80], 1736
004013E5 89F8 MOV EAX, EDI
004013E7 0D 5C200000 OR EAX, 0000205C
004013EC 8B00 MOV EAX, DWORD PTR DS:[EAX]
004013EE 8D5C24 F0 LEA EBX, [ESP-10]
004013F2 897C24 FC MOV DWORD PTR SS:[ESP-4], EDI
004013F6 C703 89842410 MOV DWORD PTR DS:[EBX], 10248489
004013FC C74424 F4 000 MOV DWORD PTR SS:[ESP-0C], C3000000 ; ASCII "INDX("
00401404 895C24 F8 MOV DWORD PTR SS:[ESP-8], EBX
00401408 814424 FC 141 ADD DWORD PTR SS:[ESP-4], 1414
00401410 83EC 08 SUB ESP, 8
00401413 C3 RETN ;jumps to stack at 12FE78
0012FE78 898424 10000000 MOV DWORD PTR SS:[ESP+10], EAX ;this code is on the stack
0012FE7F C3 RETN ;jumps to 401414
00401414 897C24 44 MOV DWORD PTR SS:[ESP+44], EDI
00401418 814C24 78 361 OR DWORD PTR SS:[ESP+78], 00001736
00401420 81AC24 F00000 SUB DWORD PTR SS:[ESP+0F0], -1736
0040142B 66:B8 EB05 MOV AX, 5EB
0040142F 31C0 XOR EAX, EAX
00401431 74 FA JE SHORT 0040142D ;always taken
0040142D EB 05 JMP SHORT 00401434
00401434 814424 74 361 ADD DWORD PTR SS:[ESP+74], 1736
0040143C C78424 140100 MOV DWORD PTR SS:[ESP+114], 11
00401447 814424 30 0B2 ADD DWORD PTR SS:[ESP+30], 210B ;stack has pointer to 40210B string "I have evolved since..."
0040144F 814C24 70 361 OR DWORD PTR SS:[ESP+70], 00001736
00401457 816C24 68 CAE SUB DWORD PTR SS:[ESP+68], -1736
0040145F 814424 64 361 ADD DWORD PTR SS:[ESP+64], 1736
00401467 814C24 5C 361 OR DWORD PTR SS:[ESP+5C], 00001736
0040146F 75 01 JNE SHORT 00401472 ;always taken
00401472 C74424 58 000 MOV DWORD PTR SS:[ESP+58], 0
0040147A E9 F3000000 JMP 00401572
00401572 897C24 28 MOV DWORD PTR SS:[ESP+28], EDI
00401576 8D45 FC LEA EAX, [EBP-4]
00401579 894424 54 MOV DWORD PTR SS:[ESP+54], EAX
0040157D 817424 4C 862 XOR DWORD PTR SS:[ESP+4C], 00002186
00401585 81AC24 CC0000 SUB DWORD PTR SS:[ESP+0CC], -1736
00401590 897C24 04 MOV DWORD PTR SS:[ESP+4], EDI
00401594 C74424 50 320 MOV DWORD PTR SS:[ESP+50], 32
0040159C 818C24 BC0000 OR DWORD PTR SS:[ESP+0BC], 00001736
004015A7 8D45 F4 LEA EAX, [EBP-0C]
004015AA 8D5C24 F0 LEA EBX, [ESP-10]
004015AE C703 8984244C MOV DWORD PTR DS:[EBX], 4C248489
004015B4 897C24 FC MOV DWORD PTR SS:[ESP-4], EDI
004015B8 75 04 JNE SHORT 004015BE ;we will jump one
004015BA 74 02 JE SHORT 004015BE ; way or the other
004015BE C74424 F4 000 MOV DWORD PTR SS:[ESP-0C], C3000000 ; ASCII "INDX("
004015C6 895C24 F8 MOV DWORD PTR SS:[ESP-8], EBX
004015CA 816C24 FC 26E SUB DWORD PTR SS:[ESP-4], -15DA
004015D2 75 02 JNE SHORT 004015D6 ;always taken
004015D6 83EC 08 SUB ESP, 8
004015D9 C3 RETN ;jumps to stack at 12FE78
0012FE78 898424 4C000000 MOV DWORD PTR SS:[ESP+4C], EAX ;this is code on the stack
0012FE7F C3 RETN ;jumps to 4015DA
004015DA 816C24 60 CAE SUB DWORD PTR SS:[ESP+60], -1736
004015E2 817424 44 361 XOR DWORD PTR SS:[ESP+44], 00001736
004015EA 75 01 JNE SHORT 004015ED ;always taken
004015ED 818424 9C0000 ADD DWORD PTR SS:[ESP+9C], 1736
004015F8 89F8 MOV EAX, EDI
004015FA 0D 6C200000 OR EAX, 0000206C
004015FF 8B00 MOV EAX, DWORD PTR DS:[EAX] ;EAX now has address of ReadFile()
00401601 894424 40 MOV DWORD PTR SS:[ESP+40], EAX
00401605 C74424 34 570 MOV DWORD PTR SS:[ESP+34], 57
0040160D 81AC24 C40000 SUB DWORD PTR SS:[ESP+0C4], -1736
00401618 EB FF JMP SHORT 00401619
00401619 FFC0 INC EAX
0040161B 75 01 JNE SHORT 0040161E ;always taken
0040161E 818C24 A00000 OR DWORD PTR SS:[ESP+0A0], 00001736
00401629 897C24 14 MOV DWORD PTR SS:[ESP+14], EDI
0040162D 66:B8 EB05 MOV AX, 5EB
00401631 31C0 XOR EAX, EAX
00401633 74 FA JE SHORT 0040162F ;always taken
0040162F EB 05 JMP SHORT 00401636
00401636 8D45 F8 LEA EAX, [EBP-8]
00401639 8D5C24 F0 LEA EBX, [ESP-10]
0040163D 85DB TEST EBX, EBX
0040163F 75 02 JNE SHORT 00401643
00401643 C703 89842430 MOV DWORD PTR DS:[EBX], 30248489
00401649 C74424 F4 000 MOV DWORD PTR SS:[ESP-0C], C3000000 ; ASCII "INDX("
00401651 895C24 F8 MOV DWORD PTR SS:[ESP-8], EBX
00401655 C74424 FC 020 MOV DWORD PTR SS:[ESP-4], 2
0040165D C16424 FC 15 SHL DWORD PTR SS:[ESP-4], 15
00401662 75 01 JNE SHORT 00401665 ;always taken
00401665 814C24 FC 711 OR DWORD PTR SS:[ESP-4], 00001671
0040166D 83EC 08 SUB ESP, 8
00401670 C3 RETN /jumps on stack at 12FE78
0012FE78 898424 30000000 MOV DWORD PTR SS:[ESP+30], EAX ;this is code on stack
0012FE7F C3 RETN ;jumps to 491671
00401671 817424 28 021 XOR DWORD PTR SS:[ESP+28], 00001C02
00401679 EB FF JMP SHORT 0040167A
0040167A FFC0 INC EAX
0040167C 89F8 MOV EAX, EDI
0040167E 0D 68200000 OR EAX, 00002068
00401683 8B00 MOV EAX, DWORD PTR DS:[EAX] ;EAX now points to WriteFile()
00401685 894424 24 MOV DWORD PTR SS:[ESP+24], EAX
00401689 66:B8 EB06 MOV AX, 6EB
0040168D 31C0 XOR EAX, EAX
0040168F 74 FA JE SHORT 0040168B ;always taken
0040168B EB 06 JMP SHORT 00401693
00401693 8D45 F8 LEA EAX, [EBP-8]
00401696 894424 20 MOV DWORD PTR SS:[ESP+20], EAX
0040169A C74424 18 F5F MOV DWORD PTR SS:[ESP+18], -0B
004016A2 818424 E00000 ADD DWORD PTR SS:[ESP+0E0], 1736
004016AD 816C24 14 14E SUB DWORD PTR SS:[ESP+14], -1BEC
004016B5 66:B8 EB05 MOV AX, 5EB
004016B9 31C0 XOR EAX, EAX
004016BB 74 FA JE SHORT 004016B7 ;always taken
004016B7 EB 05 JMP SHORT 004016BE
004016BE 74 02 JE SHORT 004016C2 ;always taken
004016C2 74 03 JE SHORT 004016C7
004016C7 8D45 F4 LEA EAX, [EBP-0C]
004016CA 8D5C24 F0 LEA EBX, [ESP-10]
004016CE 74 04 JE SHORT 004016D4 ;we will always jump
004016D0 75 02 JNE SHORT 004016D4 ; to 4016D4
004016D4 C703 89442414 MOV DWORD PTR DS:[EBX], 14244489
004016DA C74424 F4 C30 MOV DWORD PTR SS:[ESP-0C], 0C3
004016E2 895C24 F8 MOV DWORD PTR SS:[ESP-8], EBX
004016E6 895424 FC MOV DWORD PTR SS:[ESP-4], EDX
004016EA C14424 FC 16 ROL DWORD PTR SS:[ESP-4], 16
004016EF 75 03 JNE SHORT 004016F4 ;we will always jump
004016F1 74 01 JE SHORT 004016F4 ; to 4016F4
004016F4 814424 FC 001 ADD DWORD PTR SS:[ESP-4], 1700
004016FC 83C4 F8 ADD ESP, -8
004016FF C3 RETN ;jumps to stack at 12FE78
0012FE78 894424 14 MOV DWORD PTR SS:[ESP+14], EAX ;this is code on stack
0012FE7C C3 RETN ;jumps to 401700
00401700 C74424 08 F6F MOV DWORD PTR SS:[ESP+8], -0A
00401708 895424 1C MOV DWORD PTR SS:[ESP+1C], EDX
0040170C C16424 1C 16 SHL DWORD PTR SS:[ESP+1C], 16
00401711 75 01 JNE SHORT 00401714 ;always taken
00401714 814424 1C 021 ADD DWORD PTR SS:[ESP+1C], 1C02
0040171C 814C24 04 EC1 OR DWORD PTR SS:[ESP+4], 00001BEC
00401724 816C24 7C CAE SUB DWORD PTR SS:[ESP+7C], -1736
0040172C C3 RETN ;jumps to GetStdHandle(), and that API returns to 401BEC
00401BEC 53 PUSH EBX
00401BED 75 04 JNE SHORT 00401BF3 ;nomatter what we
00401BEF 74 02 JE SHORT 00401BF3 ; jump to 401BF3
00401BF3 8B5C24 08 MOV EBX, DWORD PTR SS:[ESP+8]
00401BF7 8903 MOV DWORD PTR DS:[EBX], EAX
00401BF9 5B POP EBX
00401BFA 74 03 JE SHORT 00401BFF ;nomatter what
00401BFC 75 01 JNE SHORT 00401BFF ; we jump to 401BFF
00401BFF C2 0400 RETN 4 ;jumps to GetStdHandle, the API returns to 401BEC
00401BEC 53 PUSH EBX
00401BED 75 04 JNE SHORT 00401BF3 ;nomatter what
00401BEF 74 02 JE SHORT 00401BF3 ; we jump to 401BF3
00401BF3 8B5C24 08 MOV EBX, DWORD PTR SS:[ESP+8]
00401BF7 8903 MOV DWORD PTR DS:[EBX], EAX
00401BF9 5B POP EBX
00401BFA 74 03 JE SHORT 00401BFF ;nomatter what
00401BFC 75 01 JNE SHORT 00401BFF ; we jump 401BFF
00401BFF C2 0400 RETN 4 ;jumps to 401C02, which drops to next line
00401C02 53 PUSH EBX
00401C03 74 03 JE SHORT 00401C08 ;nomatter what
00401C05 75 01 JNE SHORT 00401C08 ; we jump to 401C08
00401C08 8B5C24 0C MOV EBX, DWORD PTR SS:[ESP+0C]
00401C0C 8B1B MOV EBX, DWORD PTR DS:[EBX]
00401C0E 895C24 0C MOV DWORD PTR SS:[ESP+0C], EBX
00401C12 75 03 JNE SHORT 00401C17 ;nomatter what
00401C14 74 01 JE SHORT 00401C17 ; we jump to 401C17
00401C17 5B POP EBX
00401C18 C3 RETN ;jumps to WriteFile which returns to 401C02 (first output to console is "I have evolved...")
00401C02 53 PUSH EBX
00401C03 74 03 JE SHORT 00401C08 ;nomatter what
00401C05 75 01 JNE SHORT 00401C08 ; we jump to 401C08
00401C08 8B5C24 0C MOV EBX, DWORD PTR SS:[ESP+0C]
00401C0C 8B1B MOV EBX, DWORD PTR DS:[EBX]
00401C0E 895C24 0C MOV DWORD PTR SS:[ESP+0C], EBX
00401C12 75 03 JNE SHORT 00401C17 ;nomatter what
00401C14 74 01 JE SHORT 00401C17 ; we jump to 401C17
00401C17 5B POP EBX
00401C18 C3 RETN ;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:
00401B46 88C4 MOV AH, AL ;AH = AL - a copy of the encrypted (XORed and SHIFTED) input char
00401B48 96 XCHG EAX, ESI ;ESI=0040C3C3 EAX=1
00401B49 75 04 JNE SHORT 00401B4F
00401B4B 74 02 JE SHORT 00401B4F
00401B4F C74424 F4 0FB MOV DWORD PTR SS:[ESP-0C], C3D3B00F ;place "0F B0 D3 C3" opcodes on stack (i.e. cmpxchg bl, dl; ret)
00401B57 68 7115845C PUSH 5C841571 ;portion of unfolded constant (see below)
00401B5C 54 PUSH ESP ;push current ESP; will later point at opcodes
00401B5D 816C24 04 07F SUB DWORD PTR SS:[ESP+4], 5C43FA07 ;unfolded constant now 401B6A (jump addres after opcodes execute)
00401B65 832C24 08 SUB DWORD PTR SS:[ESP], 8 ;adjust previous ESP to point at opcodes
00401B69 C3 RETN ;execute opcodes on stack
0012FDF8 0FB0D3 CMPXCHG BL, DL ;ENCRYPTED CHAR COMPARISON running on stack; compare AL to BL; if equal, assign BL = DL
0012FDFB C3 RETN ;jump to 401B6A
00401B6A 0F44C2 CMOVE EAX, EDX ;idx=0 EAX=0 EDX=1
00401B6D 85C0 TEST EAX, 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:
//
// flare2015crackme9_algo()
//
void flare2015crackme9_algo(void)
{
byte arXor[] =
{
0x46,0x15,0xF4,0xBD,0xFF,0x4C,0xEF,0x46,0xEB,0xE6,0xB2,0xEB,0xF1,0xC4,0x34,0x67,
0x39,0xb5,0x8e,0xef,0x40,0x1b,0x74,0x0d,0x60,0x26,0x45,0xa8,0x4a,0x96,0xc9,0x65,
0xe2,0x32,0x60,0x64,0x8c,0x65,0xe3,0x8e,0x9f
};
byte arRot[] =
{
0x56,0xF5,0xAC,0x1B,0xB5,0x93,0x7E,0xB8,0x23,0xDA,0x0A,0xF2,0x01,0x61,0x5C,0xC8,
0x4C,0xD6,0x16,0x55,0x67,0xB8,0xC1,0xF8,0xBC,0x11,0xFA,0x9B,0x6B,0xF9,0xD4,0x75,
0x87,0xCA,0xCE,0xBE,0x4E,0x6E,0xF1,0xB9,0x6E
};
dword arMagic[] =
{
0x064EF4C3,0xF080B8CC,0xB8CCC2BA,0xBC42064E,0x30565DF2,0x6B5A16EB,0xF4C35D27,0x66535319,0x8E0EC3C6,
0x5DF2BC42,0xF2BC4206,0xC66B5A16,0x2E30565D,0xD2665353,0xE101DC55,0x5D278E0E,0x03F9D266,0x42064EF4,
0x55509AF9,0x53192E30,0xDC55509A,0x509AF977,0x192E3056,0x0EC3C66B,0xF97748F0,0xC35D278E,0xEBE101DC,
0x5353192E,0x01DC5550,0x5A16EBE1,0xC3C66B5A,0x7748F080,0x9AF97748,0x4EF4C35D,0xF9D26653,0x80B8CCC2,
0x48F080B8,0xCF03F9D2,0x16EBE101,0x278E0EC3,0x565DF2BC
};
char szTemp[100];
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];
__asm
{
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;
else
{
*p++ = *((char*)&chCur);
}
} //for
*p = 0;
printf("result is \"%s\"\n",szTemp);
} //flare2015crackme9_algo()
After building the program, we run it to produce the decrypted password. The result is then double-checked
in the challenge program.
Success!
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!
From: Is_th1s_3v3n_mai_finul_foarm@flare-on.com
To: <HIDDEN>
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"
-FLARE
attachment_filename="DC682778F53E853B3188AC63EB376D8B.zip"
<< Flare-On 2015 Index -- Go on to Challenge #10 >>
|
| |
|
|
|