

a....
                       __    _                         __    _ 
 _      ______  ____  / /_  (_)  _      ______  ____  / /_  (_)
| | /| / / __ \/ __ \/ __ \/ /  | | /| / / __ \/ __ \/ __ \/ / 
| |/ |/ / /_/ / /_/ / /_/ / /   | |/ |/ / /_/ / /_/ / /_/ / /  
|__/|__/\____/\____/_.___/_/    |__/|__/\____/\____/_.___/_/   
                                                               
                           __          
    ____  ____ _____  ____/ /___ ______
   / __ \/ __ `/ __ \/ __  / __ `/ ___/
  / /_/ / /_/ / / / / /_/ / /_/ (__  ) 
 / .___/\__,_/_/ /_/\__,_/\__,_/____/  
/_/                                    

                                            Production
                                            


===[ Reversing the win.exe Windows challenge
by [pandas] Tora


===[ Introduction

In this challenge, we have a windows executabe and a bmp image. If we
execute win.exe, we can see that the program asks for a bmp file and
then for a "hidden string". It's easy to see that this one is an stego
challenge, so maybe the best approach will be to take a plain black bmp
(so all bytes are 0's) and encode some string. If we encode "A" we get
something like that:

00 00 00 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 
01 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 00 00 00 01 01 01 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 .......

So now we can see that values are paired on groups of three bytes and,
with a bit of logic, the full stego algorithm can be extracted but that
won't let us do some cool reversing tricks! ;D


===[ Reversing details

If we disassemble the binary, we see that main() consists of two
function calls, one to require data to the user and encode it and the
second to save the new data to a new bmp file. Skipping all data-related
code and going straight to the point, we land at 0040116B so let's rely
on the in-line comments to explain the code:

-----------------------------------------------------------------------
0040116B next_byte:                              
0040116B                 mov     edi, 80h        ; EDI = bitmask
00401170                 jmp     short loc_401176
00401172
00401172 next_bit:                               
00401172                 mov     edx, [esp+1Ch+HiddenString]
00401176
00401176 loc_401176:                             
00401176                 mov     eax, [esp+1Ch+HiddenStrIndex]
0040117A                 movsx   eax, byte ptr [edx+eax] ; EAX = Byte from HiddenString
0040117E                 and     eax, edi        ; Bitmask applied
00401180                 xor     edx, edx
00401182                 div     edi             ; right-shift >> so EAX = 0 or EAX = 1
00401184                 xor     esi, esi
00401186
00401186 tri_byte_loop:                          
00401186                 cmp     eax, 1
00401189                 jnz     short encode_0
0040118B                 mov     ecx, BMPData    ; encoding '1'
00401191                 add     ecx, ebx        ; EBX = Dest BMP index
00401193                 lea     edx, [ecx+esi]
00401196                 mov     cl, [ecx+esi]   ; Get byte from BMP
00401199                 mov     ebp, ecx
0040119B                 and     ebp, 0FFh       ; cast to byte
004011A1                 and     ebp, 80000001h  ; check for special case (EBP is negative)
004011A7                 jns     short no_special_case
004011A9                 dec     ebp
004011AA                 or      ebp, 0FFFFFFFEh
004011AD                 inc     ebp
004011AE
004011AE no_special_case:                        
004011AE                 jnz     short next_tri_byte_loop ; if EBP was odd, no encoding is needed
004011B0                 test    cl, cl
004011B2                 jnz     short adjust_ecx_sub_1 ; do encoding by dec original value
004011B4                 mov     byte ptr [edx], 1 ; if original was 0, set to 1
004011B7                 jmp     short next_tri_byte_loop
004011B9
004011B9 encode_0:                               
004011B9                 test    eax, eax
004011BB                 jnz     short next_tri_byte_loop
004011BD                 mov     edx, BMPData    ; encoding '0'
004011C3                 add     edx, ebx
004011C5                 add     edx, esi
004011C7                 mov     cl, [edx]       ; Get byte from BMP
004011C9                 mov     ebp, ecx
004011CB                 and     ebp, 0FFh       ; cast to byte
004011D1                 and     ebp, 80000001h  ; check for special case (EBP is negative)
004011D7                 jns     short no_special_case2
004011D9                 dec     ebp
004011DA                 or      ebp, 0FFFFFFFEh
004011DD                 inc     ebp
004011DE
004011DE no_special_case2:                       
004011DE                 cmp     ebp, 1
004011E1                 jnz     short next_tri_byte_loop ; if EBP was even, no encoding
004011E3                 test    cl, cl
004011E5                 jz      short next_tri_byte_loop ; if original was 0, no encoding
004011E7
004011E7 adjust_ecx_sub_1:                       
004011E7                 dec     cl
004011E9                 mov     [edx], cl
004011EB
004011EB next_tri_byte_loop:                     
004011EB                 inc     esi             ; ESI = index of 3 bytes group
004011EC                 cmp     esi, 3
004011EF                 jl      short tri_byte_loop
004011F1                 add     ebx, 3
004011F4                 shr     edi, 1          ; EDI = bitmask
004011F6                 jnz     next_bit
004011FC                 mov     edx, [esp+1Ch+HiddenString]
00401200                 mov     esi, [esp+1Ch+HiddenStrIndex]
00401204                 mov     edi, edx
00401206                 or      ecx, 0FFFFFFFFh
00401209                 xor     eax, eax
0040120B                 inc     esi
0040120C                 repne scasb
0040120E                 not     ecx
00401210                 dec     ecx             ; Get Hidden String len
00401211                 mov     [esp+1Ch+HiddenStrIndex], esi
00401215                 cmp     esi, ecx
00401217                 jb      next_byte       ; still data to encode
0040121D                 mov     esi, [esp+1Ch+var_4]
00401221                 pop     ebp
00401222
00401222 loc_401222:                             
00401222                 mov     eax, Size
00401227                 pop     edi
00401228                 cmp     ebx, eax
0040122A                 jnb     short no_padding
0040122C
0040122C clean_noise_rest_file:                  
0040122C                 mov     ecx, BMPData    ; This loop cleans the rest of the file,
0040122C                                         ; so we don't get trash when extracting
00401232                 mov     al, [ecx+ebx]
00401235                 mov     edx, eax
00401237                 and     edx, 0FFh
0040123D                 and     edx, 80000001h
00401243                 jns     short loc_40124A
00401245                 dec     edx
00401246                 or      edx, 0FFFFFFFEh
00401249                 inc     edx
0040124A
0040124A loc_40124A:                             
0040124A                 cmp     edx, 1
0040124D                 jnz     short loc_401254
0040124F                 dec     al
00401251                 mov     [ecx+ebx], al
00401254
00401254 loc_401254:                             
00401254                 mov     eax, Size
00401259                 inc     ebx
0040125A                 cmp     ebx, eax
0040125C                 jb      short clean_noise_rest_file
0040125E
0040125E no_padding:                             
-----------------------------------------------------------------------


Basically for each byte of the "Hidden String", the program iterates
through every bit of information (using the EDI bitmask). For every bit
that is '0', the program transforms the next 3 bytes of the BMP so they
become all even and if the bit is '1', the 3 bytes must become odd.

And that's it, we can code a simple python script to extract strings
hidden using this method:

-----------------------------------------------------------------------
f = open("beistlab.bmp", "rb")
f.seek(0x36) # Skip bmp header
data = f.read()
text = ""

for i in range(1, len(data), 3*8):
	b = 0
	for j in range(1, 3*8, 3):
		b = b | (ord(data[i+j]) & 1) << 7 - ((j-1)/3)
	text = text + chr(b)
	if b == 0:
		break

print "Hidden String found:"
print text
-----------------------------------------------------------------------

Using this simple script against the bmp supplied with the challenge
shows the following message:

-----------------------------------------------------------------------
Hidden String found:
hi, guys! congratulation! next code is "this is not cipher, this is
stego". i wanna see you again in Korea. last of all, i love eazy !!!
he is cool guy and genius.
-----------------------------------------------------------------------


===[ Closing words

It seems that there's no Hacking Festival without some stego, huh? ;D
This one was a funny and nice challenge, and also the final message
was quite funny because it seems directed to non-korean teams, hehe.
Be sure that pandas will be back in Korea, we still have some 'things'
in our to-do... "Ay Carmiņa, tu en Cambados y yo en el Pais Vasco"

Woobi woobi pandas'09

