PackedMovement was the last Reverse Engineering challenge on AlexCTF 2017. The puntuation of this challnege was of 350 points.

The only hint given to this challenge was its name. You will see why later on in this writeup.

The retrieved binary is called move. if we run the file command over it we find the following:

move: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped

Even more interesting is when we see the binaries segments with readelf -l move

Elf file type is EXEC (Executable file)
Entry point 0xd906d0
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00c01000 0x00c01000 0x18fea1 0x18fea1 R E 0x1000
  LOAD           0x000300 0x0880b300 0x0880b300 0x00000 0x00000 RW  0x1000

The fact that the binary only contains two LOAD segments suggest us that the binary is possibly packed.

Lets check the entry point of the binary and see how the first couple of instructions look:

Several packers use the instruction pusha in order to save the registry contents before running the decompression routine. One famous packer that uses this instruction is the UPX packer.

If we run the strings move | grep UPX command we will confirm this binary has been packed with UPX:

9vUPX!
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.91 Copyright (C) 1996-2013 the UPX Team. All Rights Reserved. $
UPX!u
UPX!
UPX!

Fortunately, UPX also contains a flag for decompression. If we run the upx -d move -o dem command it will decompress the move binary and save a copy of the decompressed binary into disk with the name dem

If we run readelf -l dem command to see dem's segments we see the following:

Elf file type is EXEC (Executable file)
Entry point 0x804829c
There are 6 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
  INTERP         0x0000f4 0x080480f4 0x080480f4 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x18b3c 0x18b3c R E 0x1000
  LOAD           0x018f58 0x08061f58 0x08061f58 0x5a93a4 0x7a93a8 RW  0x1000
  DYNAMIC        0x018f58 0x08061f58 0x08061f58 0x000a8 0x000a8 RW  0x4
  GNU_RELRO      0x018f58 0x08061f58 0x08061f58 0x000a8 0x000a8 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.plt .plt .text 
   03     .dynamic .got.plt .data .bss 
   04     .dynamic 
   05     .dynamic 

This looks more like what we are looking for. Now that we have a decompressed file is a good chance to see what the binary on execution looks like. When we execute the file we see the following:

Guess a flag: AAAAAAA
Wrong Flag!

Easy enough. Lets see how the binary looks like. When we open the dem binary in IDA we see the following:

The binary looks like its obfuscated with movfuscator

There is a tool called demovfuscator however this tool still under development. All I could achieve with it was to replace a chunk of mov instructions with an equivalent lea instruction. However demovfuscator can geneate flow-graphs of the actuall execution flow of the executable.

This is an example of one of them:

As we can see, the binary seems to have an if else behavior.

Addiditonally, the binary itself seem to be a stack based virtual machine. One can assure this is true just by looking the name of some of its global variables:

This being said I had to make a choice on the stategy I was going to follow to solve this challenge. I had two different options

  • 1 - Attempt to make an assembler of the actual machine opcodes in order to be able to identify clearly the verification of the flag

  • 2 - Attempt to find the verification mechanism without crafting an assembler of the actual machine.

I choose the 2nd approach so that if it failed, I could always attempt to craft an assembler.

After I made this decission I started to do some dynamic analysis. At the beginning I felt like I was looking throuh glass. My assumptions about how the binary actually work would change every 5 intructions. However, at one point I saw the light.

This point was at address 0x080493DB

At that instruction I saw what it could be an initial idea of how the binary validates each byte of the flag. It will store the particular byte of the flag into R2 virtual register and our input byte will be stored into the R3 virtual register. Both of these registers will then be loaded into the ALU module of the machine. A series of operations will be done and the result of them will be stored into the rax register which will then be checked wether this result is 0 by a test eax, eax instruction. If Z flag not set when this instruction is executed, the binary will be redirected so that it finish execution. Otherwise it will proceed and compare the next byte of the flag with our input string.

Something somewhat curious about this whole procedure is that the flag byte is always loaded into the R2 virtual register. If we could see all the instructions in which some value is being loaded into R2, we may be able to see all the bytes our input string is being compared against.

Running the following IDA python sript will help us find exactly that:

from idc import *
from idaapi import *

main = None

for func in Functions():
    if GetFunctionName(func) == "main":
        main = func
    
code = list(FuncItems(main))

for line in code:
        if GetOpnd(line,0) == 'R2':
            print hex(line), "\t", GetDisasm(line)

Make sure to extend the end of the main function down to the end of the _text section. This is because IDA fails on analyzing the entire main function. That address is 0x08060B38.

The result of this script is the following:

0x80493dbL 	mov     R2, 41h
0x8049ddeL 	mov     R2, 4Ch
0x804a7e1L 	mov     R2, 45h
0x804b1e4L 	mov     R2, 58h
0x804bb9cL 	mov     R2, 43h
0x804c59fL 	mov     R2, 54h
0x804cfa2L 	mov     R2, 46h
0x804d9a5L 	mov     R2, 7Bh
0x804e357L 	mov     R2, 4Dh
0x804ed5aL 	mov     R2, 30h
0x804f75dL 	mov     R2, 56h
0x8050160L 	mov     R2, 66h
0x8050b0cL 	mov     R2, 75h
0x805150fL 	mov     R2, 73h
0x8051f12L 	mov     R2, 63h
0x8052915L 	mov     R2, 34h
0x80532bbL 	mov     R2, 74h
0x8053cbeL 	mov     R2, 30h
0x80546c1L 	mov     R2, 72h
0x80550c4L 	mov     R2, 5Fh
0x8055a64L 	mov     R2, 77h
0x8056467L 	mov     R2, 30h
0x8056e6aL 	mov     R2, 72h
0x805786dL 	mov     R2, 6Bh
0x8058207L 	mov     R2, 35h
0x8058c0aL 	mov     R2, 5Fh
0x805960dL 	mov     R2, 6Ch
0x805a010L 	mov     R2, 31h
0x805a9a4L 	mov     R2, 6Bh
0x805b3a7L 	mov     R2, 65h
0x805bdaaL 	mov     R2, 5Fh
0x805c7adL 	mov     R2, 6Dh
0x805d13bL 	mov     R2, 34h
0x805db3eL 	mov     R2, 67h
0x805e541L 	mov     R2, 31h
0x805ef44L 	mov     R2, 63h
0x805f8ccL 	mov     R2, 7Dh

If we modify the script above to print the characters that are been stored into R2 we should get the flag.

final script is:

from idc import *
from idaapi import *

flag = ''
main = None

for func in Functions():
    if GetFunctionName(func) == "main":
        main = func
    
code = list(FuncItems(main))

for line in code:
        if GetOpnd(line,0) == 'R2':
            flag += chr(int(GetOpnd(line, 1)[:-1], 16))
            
print "\n\n", flag

By Executing the previous script we will get the flag : ALEXCTF{M0Vfusc4t0r_w0rk5_l1ke_m4g1c}