AlexCTF - PackedMovement
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 is 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 interestingly 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 intruction 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 decompress 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 a lea
instruction equivalent. 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 that has 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 fails I can always attempt to craft an assembler for it.
After I made this decission I started to do some dynamic analysis. At the beginning I felt like I was looking throuh a glass, My assumptions 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 assumption of how the binary validates each byte of the flag. It Would Load the particular byte of the flag into R2
virtual register and the input byte will be loaded into de R3
virtual register. Both of this registers will then be loaded into the ALU
module of the machine. it would held a set of operations and it will leave the result of them in the rax
register which would be pass to a test eax, eax
intruction, and if this instruction does not return 1, it would finish execution. Otherwise it will proceed and compare the next byte.
Something somewhat curious about this whole procedure is that the flag byte is always loaded into the R2
virtual register. If we can see all the instructions where some value is being stored into R2
we may be able to see all the bytes our input 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. That is address 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 bytes 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}