The-FLARE-On-Challenge-2015/Challenge-2
You are here | Challenge 2
|
File
Uncompress 599EA8F84AD975CFB07E0E5732C9BA14.zip (password is "flare") and you will get a file named very_success with following properties:
MD5 | d88dafdaefe27e7083ef16d241187d31 |
---|---|
SHA1 | 9dfe72f31fa8fa131e5f75fe99adcc1f9b3deca2 |
SHA256 | 9852afb172bc03a50d291c70faa724c69a10af9e6ee88457185ce5e0705216f0 |
File type | PE32 executable (console) Intel 80386, for MS Windows |
Code analysis
sub_401000
A cross references to the strings "You are success" and "You are failure" leads to the sub_401000 function where we can start the analysis.
The user buffer is read and saved to a buffer (unk_402159):
.text:00401038 lea eax, [ebp-4]
.text:0040103B push eax ; lpNumberOfBytesRead
.text:0040103C push 32h ; nNumberOfBytesToRead
.text:0040103E push offset unk_402159 ; lpBuffer
.text:00401043 push dword ptr [ebp-0Ch] ; hFile
.text:00401046 call ReadFile
The function sub_401084 is then called with the following 3 parameters:
.text:0040104C push 0 ; lpOverlapped
.text:0040104E lea eax, [ebp-4]
.text:00401051 push eax ; lpNumberOfBytesWritten
.text:00401052 push 11h ; nNumberOfBytesToWrite
.text:00401054 push dword ptr [ebp-4] ; length
.text:00401057 push offset unk_402159 ; userBuffer
.text:0040105C push dword ptr [ebp-10h] ; encryptedBuffer
.text:0040105F call sub_401084
sub_401084
This function is manipulating the user input with a variety of transformations applied to the characters and then compares the result to a buffer. You will be equally successful by reversing the encryption algorithm or by brute forcing it. My script is brute forcing the algorithm to solve this challenge. Refer to the comments in the script for more information.
Script and solution
Here is the script I wrote to solve this challenge:
#!/usr/bin/env python
def split_reg(reg):
#reg16 = reg & 0xFFFF
#reg8h = reg16 >> 8
#reg8l = reg16 & 0xFF
#return (reg16, reg8h, reg8l)
reg8h = reg >> 8
reg8l = reg & 0xFF
return (reg8h, reg8l)
def make_reg16_from_hl(reg8h, reg8l):
return reg8h*256 | reg8l
rol = lambda val, r_bits, max_bits=8: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
# .text:0040108C xor ebx, ebx ; EBX = 0
bx = 0
# .text:0040108E mov ecx, 25h ; ECX = 0x25 (37 chars)
cx = 0x25
# .text:00401093 cmp [ebp+length], ecx
# .text:00401096
# .text:00401096 loc_401096: ; if len(userBuffer)<37: fail
# .text:00401096 jl short loc_4010D7
# .text:00401098 mov esi, [ebp+userBuff] ; ESI = userBuffer
userBuffer = []
# .text:0040109B mov edi, [ebp+encryptedBuff] ; EDI = encryptedBuffer
# encrypted buffer is read in reverse order. It has already been reversed here
encryptedBuffer = [0xa8, 0x9a, 0x90, 0xb3, 0xb6, 0xbc, 0xb4, 0xab, 0x9d, 0xae, 0xf9, 0xb8, 0x9d, 0xb8, 0xaf, 0xba, 0xa5, 0xa5, 0xba, 0x9a, 0xbc, 0xb0, 0xa7, 0xc0, 0x8a, 0xaa, 0xae, 0xaf, 0xba, 0xa4, 0xec, 0xaa, 0xae, 0xeb, 0xad, 0xaa, 0xaf]
stack = []
# initial value for AX provided by OllyDbg
ax=0xff84
dx = 0
for c, i in enumerate(encryptedBuffer):
# backup registers
saved_ax = ax
saved_bx = bx
saved_cx = cx
saved_dx = dx
# Brute force
for probed_char in range(255):
# .text:004010A2 mov dx, bx ; DX = BX
dx = bx
(dh, dl) = split_reg(dx)
# .text:004010A5 and dx, 3 ; DX = DX & 0x3
dx = dx & 0x3
(dh, dl) = split_reg(dx)
# .text:004010A9 mov ax, 1C7h ; AX = 0x1CF
ax = 0x1C7
(ah, al) = split_reg(ax)
# .text:004010AD push eax ; push EAX to stack
stack.append(ax)
# .text:004010AE sahf ; set CF to 1 because AH = 1
cf = 1
# .text:004010AF lodsb ; EAX = userBuffer[i]
al = probed_char
ax = make_reg16_from_hl(ah, al)
# .text:004010B0 pushf ; Store flags register to stack
# .text:004010B1 xor al, [esp+4] ; al = userBuffer[i] ^ 0xC7
al = al ^ 0xC7
ax = make_reg16_from_hl(ah, al)
# .text:004010B5 xchg cl, dl ; (ECX <-> EDX)
saved_dx = dx
dx = cx
cx = saved_dx
(ch, cl) = split_reg(cx)
(dh, dl) = split_reg(dx)
# .text:004010B7 rol ah, cl ; EAX = ROL(EAX, ECX)
ah = rol(ah, cl)
ax = make_reg16_from_hl(ah, al)
# .text:004010B9 popf ; Retrieve flags register from stack
# .text:004010BA adc al, ah ; EAX = AL + AH + CF
al = al + ah + cf
ax = make_reg16_from_hl(ah, al)
# .text:004010BC xchg cl, dl ; ECX <-> EDX
saved_dx = dx
dx = cx
cx = saved_dx
(ch, cl) = split_reg(cx)
(dh, dl) = split_reg(dx)
# .text:004010BE xor edx, edx ; EDX = 0
dx = 0
(dh, dl) = split_reg(dx)
# .text:004010C0 and eax, 0FFh ; EAX = EAX & 0xFF
ax = ax & 0xFF
(ah, al) = split_reg(ax)
# .text:004010C5 add bx, ax ; BX = BX + AX
bx = bx + ax
(bh, bl) = split_reg(bx)
### Testing
if ax == encryptedBuffer[c]:
#print "found letter for index %s: %s" % (c, hex(probed_char))
userBuffer.append(probed_char)
# .text:004010C8 scasb ; ZF = AL == BYTE PTR[EDI]
# .text:004010C9 cmovnz cx, dx ; IF ZF == 0:DEST = SOURCE
# .text:004010CD pop eax ; EAX = value pushed to stack at 0x4010AD
ax = stack.pop()
# .text:004010CE jecxz short loc_4010D7 ; if ECX=0; goto fail
# .text:004010D0 sub edi, 2 ; EDI = EDI - 2
# .text:004010D3 loop loc_4010A2
cx -= 1
break
else:
#print "[KO], trying next letter..."
# Roll back registers
ax = saved_ax
bx = saved_bx
cx = saved_cx
dx = saved_dx
print ''.join([chr(i) for i in userBuffer])
Below is the output of my script:
$ ./script2-bf.py [email protected]
You will receive the following answer along with a 15M file attached:
Great job, you're really knocking these out! Here's the next binary for your goaty enjoyment. The password to the zip archive is "flare" again. Keep up the good work, and good luck! -FLARE
Comments
Keywords: reverse-engineering challenge flare fireeye