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 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!
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}