PackedMovement was the last Reverse Engineering challenge on
AlexCTF 2017. The puntuation of this challnege was of
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
If we run the
strings move | grep UPX command we will confirm this binary has been packed with
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!
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
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
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
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 :