Every year the Flare team puts on a reverse engineering CTF called the Flare-On Challenge. These challenges are loosely based off malware and techniques that are seen in malware. The nice thing about these challenges is that it helps folks like me to keep my skills sharp. Majority of these challenges are heavily windows based, so unless you have a Windows VM, debugging can be tough. Usually most can be solved with just using IDA pro or Ghidra without needing to debug anything.
All flags are suffixed with "@flare-on.com" which is very helpful in finding flags are encrypted.
In this walk-through I will assume that the first 4 challenges are for new reverse engineers and will be over-explaining a bit. The rest should be more high level for keeping the length down.
This is a simple .NET GUI program that uses a simple byte XOR based on the modulo length of the string created by the name of the weapons you choose.
Memecat Battlestation [Shareware Demo Edition]
Welcome to the Sixth Flare-On Challenge!
The 7zip password is: flare
This is a simple game. Reverse engineer it to figure out what "weapon codes" you need to enter to defeat each of the two enemies and the victory screen will reveal the flag. Enter the flag here on this site to score and move on to the next level.
This challenge is written in .NET. If you don't already have a favorite .NET reverse engineering tool I recommend dnSpy
With any new binary you receive be sure triage and handle with care. While these CTFs are generally good natured, never trust any binary and examine in a Virtual Machine. Use a hex editor, binwalk, or the file command on your *nix shell to determine the file type. Use PE Bear, CFF explorer, or PE studio to help you identify what kind of PE binary it could be.
After opening in Dnspy you can see that there are 3 forms to actually care about. If you haven't made a GUI .NET form program before, I highly recommend coding up one yourself. To put it in plain words, each form class acts like a scene in the GUI which means each class has several visual properties that reference objects in the resources (MemcatBattelstation.Properties). When working with malware, the resources of a .NET program is a great place to start looking for clues.
In the main program, there are references to the form classes: stage1, stage2, and VictoryForm. The constructor for the victory form is taking in a string concatenated from strings returned from stage1 and stage2. When the VictoryForm is constructed it loads the encoded flag as a byte array of 31 integers. The constructor string was used to decode the encoded byte array using a simple XOR routine.
Application.Run(new VictoryForm
{
Arsenal = string.Join(",", new string[]
{
stage2Form.WeaponCode,
stage1Form.WeaponCode
}),
Location = stage2Form.Location
});
Since this is a game that requires you to enter in a weapon name, the next step is finding out what those weapon names are. The form class should have an event function associated with the button click. This takes in a string which becomes very obvious with the IF statement.
So at this point, the Stage 1 form is looking for a string RAINBOW as the weapon name. There is a dank animation afterwards.
The second stage becomes a little bit tricky, but like I said the clues are in the resource section. You can use deductive reasoning to guess which image objects haven't been referenced yet:
Knowing that the answer is using both stage1 and stage2 weapon code combined we already know part of the key. Can also assume that the end of the 31 byte array ends with "@flare-on.com". There is also a function call isValidWeaponCode that verifies if the string matches the hard coded sequence shown below. The isValidWeaponCode function pretty much takes out the guesswork.
private bool isValidWeaponCode(string s)
{
char[] array = s.ToCharArray();
int length = s.Length;
for (int i = 0; i < length; i++)
{
char[] array2 = array;
int num = i;
array2[num] ^= 'A';
}
return array.SequenceEqual(new char[]
{
'\u0003',
' ',
'&',
'$',
'-',
'\u001e',
'\u0002',
' ',
'/',
'/',
'.',
'/'
});
}
In the end you are left with:
RAINBOWBagel_Cannon
It's all downhill from that point. The following function is used to decode the integer array.
byte[] bytes = Encoding.UTF8.GetBytes(this.Arsenal);
for (int i = 0; i < array.Length; i++)
{
byte[] array2 = array;
int num = i;
array2[num] ^= bytes[i % bytes.Length];
}
this.flagLabel.Text = Encoding.UTF8.GetString(array);
And then you get another dank animation.
The flag will be revealed after that.
This is a simple Windows x32 PE program that decrypts the flag within a loop that never triggers. You will need to either patch the program or modify the branch flags in the debugger to advance.
Overlong
1
The secret of this next challenge is cleverly hidden. However, with the right approach, finding the solution will not take an overlong amount of time.
*The 7zip password is: flare
In a disassembler, offset 0x117E is where the function decides to loop through the encoded text. Using the graph mode in your disassembler will help you see the loop designated by the blue arrow lines. The branch statement is where you will want to modify in the debugger.
In x32dbg, you can patch instructions. In this case, I did not want to take the branch jump so it was easier just to "nop" out the instruction altogether. Be sure to fill the rest of the bytes with nops.
Typically in byte arrays that are being processed, the byte needs to be moved from a register to a memory address. It's easier to find a mov [address], register
instruction (intel syntax) as a quicker way to find the destination.
In x32dbg, if you right click on the destination address within the instruction and you can view that address in the dump window as seen below. After you have patched the instructions, you can continue to run the program and it will reveal the flag.
This is a Tamagotchi themed android apk that uses simple math to decrypt the flag. The math product is calculated by the inputs of playing, feeding, and cleaning poop.
Flarebear
1
We at Flare have created our own Tamagotchi pet, the flarebear. He is very fussy. Keep him alive and happy and he will give you the flag.
7Zip password: flare
The apktool does a good job at getting you the file you need the most which is the classes.dex file which is a Dalvik executable. The command below should do the trick.
apktool d flarebear.apk
Next you will need to convert the dex file into a jar to that you can decompile with a java decompiler tool. Using dex2jar should help with that.
d2j-dex2jar.sh -f ~/path/to/classes.dex
Once you have the jar file in my case classes-dex2jar.jar, you can use the tool jd-gui to decompile the jar.
java -j jd-gui.jar
Finally download an emulator from Android studio, using Tools->SDK Manager->Android SDK->SDK Tools Tab->Android Emulator. Under Tools->AVD Manager is where you can manage all the android virtual machines for debugging the APK. You can open the original APK in Android studio and debug with the emulator/AVD build you downloaded. At this point you have the java classes loaded in jd-gui and the flarebear.apk running in the emulator from android studio.
Android malware can sometimes hide binary data in the APK "raw" resources. In the raw folder from the apktool dump, files named "ecstatic" and "estatic2" are highly suspicious. It will be useful to trace these filenames in the code: ecstatic -> openRawResource ->danceWithFlag->isEcstatic->setMood.
In debugging in android studio has a window you can view the values of the password for the decrypt function. You'll notice that the password is built from the string created from the combination of inputs.
The next step is to determine how the input relates to the functions. In FlarebearActivity.java, there are isHappy and IsEstatic functions that precede the decryption of final flag image in function danceWithFlag.
public final void setMood() {
if (isHappy()) {
((ImageView)_$_findCachedViewById(R.id.flareBearImageView)).setTag("happy");
if (isEcstatic()) {
danceWithFlag();
return;
}
} else {
((ImageView)_$_findCachedViewById(R.id.flareBearImageView)).setTag("sad");
}
}
The actions play, feed, and poop accumulate mass, happy, and clean values. In the function isEcstatic
these values tested against hard coded integers. Figuring out the combination becomes a simple math problem that you can do by hand.
public final boolean isEcstatic() {
byte b = 0;
int i = getState("mass", 0);
int j = getState("happy", 0);
int k = getState("clean", 0);
int m = b;
if (i == 72) {
m = b;
if (j == 30) {
m = b;
if (k == 0)
m = 1;
}
}
return m;
}
Using the emulator you can plug in 8 feeds, 4 plays, 2 cleans and it will decrypt an image from the resources to reveal the flag.
This challenge has 2 x64 ELF binaries that utilizes the DNS requests as inputs for determining the next move on a chess board.
Dnschess
1
Some suspicious network traffic led us to this unauthorized chess program running on an Ubuntu desktop. This appears to be the work of cyberspace computer hackers. You'll need to make the right moves to solve this one. Good luck!
7Zip password: flare
Some malware and RAT kits can use DNS requests as a form of communication to the command and control node. ChessUI binary serves just as the UI for the chessboard while ChessAI.so is the library that calculates the next chess moves based off of the DNS responses. The Flare team likes to take you down rabbit holes when the solution is very simple.
In ChessAI.so the function getNextMove
takes the values from the DNS requests and converts them for positions on the chessboard. The if statement if ( *v10 != 127 || v10[3] & 1 || a1 != (v10[2] & 0xF))
was the most obvious clue in that it is looking for 127 like in 127.0.0.1. So I just need to make sure when I replay packets that the IP addresses contain these values. Easy peasy.
I was too lazy to stand up a DNS tool to replay the pcap so instead I just changed my /etc/hosts file on my ubuntu vm to match. I used tshark to grep all the IP addresses and domain names.
tshark -r capture.pcap -T fields -e dns.a -e dns.qry.name -2 -R "dns.flags.response eq 1"
My /etc/hosts file:
127.150.96.223 rook-c3-c6.game-of-thrones.flare-on.com
127.252.212.90 knight-g1-f3.game-of-thrones.flare-on.com
127.215.177.38 pawn-c2-c4.game-of-thrones.flare-on.com
127.118.118.207 knight-c7-d5.game-of-thrones.flare-on.com
127.89.38.84 bishop-f1-e2.game-of-thrones.flare-on.com
127.109.155.97 rook-a1-g1.game-of-thrones.flare-on.com
127.217.37.102 bishop-c1-f4.game-of-thrones.flare-on.com
127.49.59.14 bishop-c6-a8.game-of-thrones.flare-on.com
127.182.147.24 pawn-e2-e4.game-of-thrones.flare-on.com
127.0.143.11 king-g1-h1.game-of-thrones.flare-on.com
127.227.42.139 knight-g1-h3.game-of-thrones.flare-on.com
127.101.64.243 king-e5-f5.game-of-thrones.flare-on.com
127.201.85.103 queen-d1-f3.game-of-thrones.flare-on.com
127.200.76.108 pawn-e5-e6.game-of-thrones.flare-on.com
127.50.67.23 king-c4-b3.game-of-thrones.flare-on.com
127.157.96.119 king-c1-b1.game-of-thrones.flare-on.com
127.99.253.122 queen-d1-h5.game-of-thrones.flare-on.com
127.25.74.92 bishop-f3-c6.game-of-thrones.flare-on.com
127.168.171.31 knight-d2-c4.game-of-thrones.flare-on.com
127.148.37.223 pawn-c6-c7.game-of-thrones.flare-on.com
127.108.24.10 bishop-f4-g3.game-of-thrones.flare-on.com
127.37.251.13 rook-d3-e3.game-of-thrones.flare-on.com
127.34.217.88 pawn-e4-e5.game-of-thrones.flare-on.com
127.57.238.51 queen-a8-g2.game-of-thrones.flare-on.com
127.196.103.147 queen-a3-b4.game-of-thrones.flare-on.com
127.141.14.174 queen-h5-f7.game-of-thrones.flare-on.com
127.238.7.163 pawn-h4-h5.game-of-thrones.flare-on.com
127.230.231.104 bishop-e2-f3.game-of-thrones.flare-on.com
127.55.220.79 pawn-g2-g3.game-of-thrones.flare-on.com
127.184.171.45 knight-h8-g6.game-of-thrones.flare-on.com
127.196.146.199 bishop-b3-f7.game-of-thrones.flare-on.com
127.191.78.251 queen-d1-d6.game-of-thrones.flare-on.com
127.159.162.42 knight-b1-c3.game-of-thrones.flare-on.com
127.184.48.79 bishop-f1-d3.game-of-thrones.flare-on.com
127.127.29.123 rook-b4-h4.game-of-thrones.flare-on.com
127.191.34.35 bishop-c1-a3.game-of-thrones.flare-on.com
127.5.22.189 bishop-e8-b5.game-of-thrones.flare-on.com
127.233.141.55 rook-f2-f3.game-of-thrones.flare-on.com
127.55.250.81 pawn-a2-a4.game-of-thrones.flare-on.com
127.53.176.56 pawn-d2-d4.game-of-thrones.flare-on.com
The next easy step was to just take all the values from the DNS request and plug them into the gameboard. As an example the DNS request rook-c3-c6.game-of-thrones.flare-on.com
is to move rook from position c3 to c6 on the chess board.
Simple right? But wait there's more!
Turns out that the order of the DNS request are not sequential. The other parts of the IP address dedupes and numbers the order of the requests: v10[3] & 1
and a1 != (v10[2] & 0xF).
4 0 252.212.90
1 0 215.177.38
6 0 89.38.84
5 0 217.37.102
b 0 49.59.14
3 0 182.147.24
c 0 200.76.108
d 0 99.253.122
a 0 25.74.92
8 0 108.24.10
9 0 34.217.88
e 0 141.14.174
7 0 230.231.104
2 0 159.162.42
0 0 53.176.56
Just go through all the positions after that and it will reveal the flag once you have won the game.
This is an x32 PE executable with a shrunken header that uses the DirectX 9 library to handle 3D sprites and their translations on a plane.
demo
1
Someone on the Flare team tried to impress us with their demoscene skills. It seems blank. See if you can figure it out or maybe we will have to fire them. No pressure.
7Zip password: flare ** You will need DirectX 9
Always use a PE header parser like CFF explorer or PE Bear to verify the that the header looks correct. I had found that this header seemed to be smaller than normal and lacked a section table. Here is a link that I found useful: https://www.bigmessowires.com/2015/10/08/a-handmade-executable-file/
DOS Header PE Header Optional Header Overlap Optional Header Code EntryPoint
00000000: 4d5a 3231 5045 0000 4c01 0000 01db 617f MZ21PE..L.....a.
00000010: 10d0 1773 7547 ebf9 0800 0200 0b01 11c9 ...suG..........
00000020: 4585 c079 1f01 d350 f7e2 903d 5c00 0000 E..y...P...=\...
00000030: f7f3 39c1 19db eb48 0000 4000 0400 0000 ..9....H..@.....
00000040: 0400 0000 0fa3 2d6a 0140 008d 0400 ebce ......-j.@......
00000050: 0000 0000 ebb6 431f 4000 0000 5331 edbb ......C.@...S1..
00000060: 0200 0000 90be 4401 4000 6a01 58bf 0000 ......D.@.j.X...
00000070: 4200 b100 9057 eb12 0000 0000 0000 0000 B....W..........
00000080: 5a72 0792 29d1 0400 29d0 60ad 01f8 742c Zr..)...).`...t,
00000090: 6a0a 5a89 1454 8954 2410 ad31 ed4d 4501 j.Z..T.T$..1.ME.
I think I spent majority of this challenge reading up on DirectX 3D programs. I didn't even know what a "demo" was until I had to find the term in gaming pop culture articles. The best way I can describe it is like a graphic designer showing off their portfolio pieces. The best resource I could find on was on Game Hacking from Nick Cano. This resource helped explained how to manipulate 3D meshes and 3D Matrix translations.
I found that there were 2 3D meshes that were being placed on the plane. The first mesh being the Flare logo and the unknown second mesh. D3DXMatrixTranslation
function controlled how those meshes were displayed. If you ever worked with Blender or Autodesk Maya before you know that there are 3 positions: X, Y, and Z.
D3DXMATRIX* D3DXMatrixTranslation(
_Inout_ D3DXMATRIX *pOut,
_In_ FLOAT x,
_In_ FLOAT y,
_In_ FLOAT z
);
Remember that arguments are pushed onto the stack backwards starting with Z, Y, X. The Z position was grossly out of proportion at 0x3FAAAAAB. So this position is way off the camera view. If you patched the instruction or changed the value on the stack to 0, it will render the second mesh will be displayed in front of the Flare logo.
Here is the image of the second mesh with a new Z offset:
This is a .NET executable that uses the most significant bit steganography to hide an image within another image. This challenge also uses IL code modification to edit values of function offsets and immediate values.
bmphide
1
Tyler Dean hiked up Mt. Elbert (Colorado's tallest mountain) at 2am to capture this picture at the perfect time. Never skip leg day. We found this picture and executable on a thumb drive he left at the trail head. Can he be trusted?
7Zip password: flare
Like any other higher level language, .NET is built on something called IL code or Intermediate Language code. The code that you write is converted into unique identifiers that used to find functions, strings, etc in a giant look up table. You can find this table in the .NET header, it's called the Metadata Table. This is good to know if you decide to one day do .NET JIT function hooking or IL code modification, but for this challenge you'll need to identify when something is changed. Luckily Dnspy as a nice feature to help with this.
The init function is making sure that functions a, b, c, d
haven't been tampered with. Instead of using the names of the function it uses the Method Identifier to determine which function bodies to verify. It will also swap function a
with function b
and function c
with function d
.
The same init function also calls a function to CalculateStack
. If you follow this function down to IdentifyLocals->IncrementStack
functions, you'll find that the function h body had been modified to swap function f with function g.
If you right click on the h function in Dnspy you can view the method body in a hex editor. This will help determine what bytes are actually being modified. Luckily Dnspy already converts function identifiers in to hex, which is easy to spot in the hex editor.
It will covert function 6000013 (f) -> 6000014 (g)
It will change the integer values in function g
The final product is then ...
public static byte[] h(byte[] data)
{
byte[] array = new byte[data.Length];
int num = 0;
for (int i = 0; i < data.Length; i++)
{
int num2 = (int)Program.g(num++);
int num3 = (int)data[i];
num3 = (int)Program.e((byte)num3, (byte)num2);
num3 = (int)Program.b((byte)num3, 7);
int num4 = (int)Program.g(num++);
num3 = (int)Program.e((byte)num3, (byte)num4);
num3 = (int)Program.d((byte)num3, 3);
array[i] = (byte)num3;
}
return array;
}
The Flare team really likes you to go down rabbit holes. It may look like it's using some kind of RC4 but it's doing some simple math functions. These functions you can easily create inverses for. So functions b,d,e, h and i will need to be written as inverse functions meaning f(x) = y and g(y) = x. Excuse my shitty c# code.
Inverse h
public static byte[] inverseh(byte[] data)
{
byte[] array = new byte[data.Length];
int A = 2* data.Length;
int len = data.Length - 1;
for (int i = data.Length-1; i > -1; i--)
{
int B = (int)data[i];
int Ap = A = A - 1;
int App = A = A - 1;
int X = (int)Program.g(Ap);
int Y = (int)Program.g(App);
B = (int)Program.inversed((byte)B, 3);
int C = (int)Program.inversee((byte)B, (byte)X);
int D = (int)Program.inverseb((byte)C, 7);
int E = (int)Program.inversee((byte)D, (byte)Y);
array[i] = (byte)E;
}
return array;
}
Inverse B
public static byte inverseb(byte b, int r)
{
for (int i = 0; i < r; i++)
{
bool flag = (int)b % 2 == 0; // even
if (flag)
{
b = (byte)((int)b / 2);
}
else
{
b = (byte)((int)b / 2 + 128);
}
}
return b;
}
Inverse D
public static byte id(byte b, int r)
{
for (int i = 0; i < r; i++)
{
byte ob = b;
if (b < 128)
{
b = (byte)((b * 2));
}
else
{
b = (byte)(((b - 128) * 2) + 1);
}
}
return b;
}
Inverse E
public static byte inversee(byte b, byte k)
{
return (byte)(b ^ k);
}
Inverse I
public static byte[] inversei(Bitmap bm, byte[] data)
{
byte[] array = new byte[data.Length];
int num = Program.j(103);
int k = 0;
for (int i = Program.j(103); i < bm.Width; i++)
{
for (int j = Program.j(103); j < bm.Height; j++)
{
bool flag = num > data.Length - Program.j(231);
if (flag)
{
break;
}
Color pixel = bm.GetPixel(i, j);
int red = (int)pixel.R;
int green = (int)pixel.G;
int blue = (int)pixel.B;
int out = red & 7;
out = out | ((green & 7) << 3);
out = out | ((blue & 3) << 6);
num += Program.j(231);
array[k] = (byte)test;
k++;
}
}
return array;
}
Besure to unit test your inverse functions. Finally plug in those images on the command line and watch them extract.
Debug>ConsoleApp1.exe image.bmp image.bmp out.bmp
You get this image:
Now let's do it again!
Debug>ConsoleApp1.exe out.bmp out.bmp final.bmp
Nice.
This is a x32 Windows PE created by Pyinstaller exe that uses an annoying math problem to get launch codes.
Wopr
1
We used our own computer hacking skills to "find" this AI on a military supercomputer. It does strongly resemble the classic 1983 movie WarGames. Perhaps life imitates art? If you can find the launch codes for us, we'll let you pass to the next challenge. We promise not to start a thermonuclear war.
7Zip password: flare
I hadn't watched War Games in a long time, so I decided to look up some clues and dialog. I saw that the launch code are all upper case letters and numbers: DLG2209TVX.
My next assumption is that maybe these codes will look similar (all uppercase).
Looking at the disassembly there is a reference to the string "_MEIPASS2". From my experience looking at py2exe and pyinstaller malware I was able to quickly identify that this string is related to pyinstaller. This string represents the offset of code that contains the python bytecode.
Luckily there is a tool out there to extract the pyc or python compiled bytecode. I used PyInstaller Extractor.
C:\Users\redteam\Desktop\7 - wopr>C:\Python37\python.exe pyinstxtractor.py w
opr.exe
pyinstxtractor.py:86: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's document
ation for alternative uses
import imp
[*] Processing wopr.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 37
[*] Length of package: 5068358 bytes
[*] Found 64 files in CArchive
[*] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap
[+] Possible entry point: pyiboot02_cleanup
[*] Found 135 files in PYZ archive
[*] Successfully extracted pyinstaller archive: wopr.exe
Once you have the extracted folder, you'll notice that it is using Python37.dll. Going forward you will want to install this version to decompile. Now there are 2 entrypoints dumped by the extractor. The file pyiboot02_cleanup
is the file to decompile.
The header is stripped in pyiboot02_cleanup
. Uncompyle6 requires that the correct header is present. Using python37 in Windows, compile a random python file so that you can copy it's pyc header. I noticed that Macos and Windows pyc headers are different. So whichever you used to compile Uncompyle6, and you should use the same OS and version of python37. The following command should to the trick.
python37 -m compileall .
This was my header:
420D0D0A 00000000 4D5B635D 7B310000
The final product of pyiboot02_cleanup.pyc
00000000: 420d 0d0a 0000 0000 4d5b 635d 7b31 0000 B.......M[c]{1..
00000010: e300 0000 0000 0000 0000 0000 0009 0000 ................
00000020: 0040 0000 0073 3c01 0000 6400 5a00 6401 .@...s<...d.Z.d.
00000030: 6402 6c01 5a01 6401 6402 6c02 5a02 6401 d.l.Z.d.d.l.Z.d.
00000040: 6402 6c03 5a03 6401 6402 6c04 5a04 6401 d.l.Z.d.d.l.Z.d.
00000050: 6402 6c05 5a05 6401 6402 6c06 5a06 6401 d.l.Z.d.d.l.Z.d.
00000060: 6402 6c07 5a07 6401 6402 6c08 5a08 6401 d.l.Z.d.d.l.Z.d.
00000070: 6403 6c09 5400 650a 6404 8301 0100 6504 d.l.T.e.d.....e.
00000080: a00b 6405 6406 a102 5a0c 6900 6601 6407 ..d.d...Z.i.f.d.
00000090: 6408 8401 5a0d 6409 5a0e 640a 5a0f 650d d...Z.d.Z.d.Z.e.
Now to run uncompyle6
uncompyle6 pyiboot02_cleanup.pyc
Now that you have the decompiled python code, there is another layer of python code that needs to be decrypted. The "_doc_" which is the Shakespeare text at the beginning of the file contains the compressed and encrypted python code. You will need to extract out the _doc_ bytes from the original pyc file because the bytes existing between lines are actually the bytes used to RC4 decrypt (fire() function).
Next a key file is read from the folder that's obviously named "key". A for loop that ranges from 0 to 256 while add 1 byte (bytes([74])
) to the beginning of the key. It uses a try catch to handle any failures so it's hard to identify which one actually worked.
for i in range(256):
try:
exec(lzma.decompress(fire(eye(__doc__.encode()), bytes([i]) + BOUNCE)))
except Exception as e:
pass
The Flare team does a sneaky function switch by swapping print
and exec
functions. So when there is a successful decompression, it will continue to execute the decompressed python code without throwing an error.
You will still need x32dbg for this next part. In the function wrong, it acquires the handle to its own process in memory.
trust = windll.kernel32.GetModuleHandleW(None)
This means you will need to work with the process memory offsets as they exist post ntdll loading.
You can dump and modify the python code so that it will take a bin file instead of actually getting a handle to the process memory.
with open("wopr_01000000.bin", "rb") as bin:
wopr_addr = 0x1000000
binbytes = bin.read()
pbinbytes = c_char_p(binbytes)
computer = string_at(pbinbytes, 1024)
It will generate a md5 hash from processing this memory. This hash is xored with an integer array.
xor = [212, 162, 242, 218, 101, 109, 50, 31, 125, 112, 249, 83, 55, 187, 131, 206]
h = [hash[i] ^ xor[i] for i in range(16)]
//output:
h = [115, 29, 32, 68, 106, 108, 89, 76, 21, 71, 78, 51, 75, 1, 55, 102]
The xored hash is product is used to verify the launch codes (b == h
) with the following:
b[0] = x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[11] ^ x[14]
b[1] = x[0] ^ x[1] ^ x[8] ^ x[11] ^ x[13] ^ x[14]
b[2] = x[0] ^ x[1] ^ x[2] ^ x[4] ^ x[5] ^ x[8] ^ x[9] ^ x[10] ^ x[13] ^ x[14] ^ x[15]
b[3] = x[5] ^ x[6] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[15]
b[4] = x[1] ^ x[6] ^ x[7] ^ x[8] ^ x[12] ^ x[13] ^ x[14] ^ x[15]
b[5] = x[0] ^ x[4] ^ x[7] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[13] ^ x[14] ^ x[15]
b[6] = x[1] ^ x[3] ^ x[7] ^ x[9] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[15]
b[7] = x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[10] ^ x[11] ^ x[14]
b[8] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[12]
b[9] = x[6] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[15]
b[10] = x[0] ^ x[3] ^ x[4] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[14] ^ x[15]
b[11] = x[0] ^ x[2] ^ x[4] ^ x[6] ^ x[13]
b[12] = x[0] ^ x[3] ^ x[6] ^ x[7] ^ x[10] ^ x[12] ^ x[15]
b[13] = x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7] ^ x[11] ^ x[12] ^ x[13] ^ x[14]
b[14] = x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[7] ^ x[11] ^ x[13] ^ x[14] ^ x[15]
b[15] = x[1] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[13] ^ x[15]
I was too lazy to do solve the rest of this by hand, so my work CTF buddy suggested I used Z3. If you decide to install Z3 on a Mac you will need to compile the source code. Remember the launch codes are all uppercase letters and numbers. Here is my simple python z3 script that solved the puzzle.
from z3 import *
solver = Solver()
x = [BitVec('input_%d' % i, 8) for i in range(16)]
b = [115, 29, 32, 68, 106, 108, 89, 76, 21, 71, 78, 51, 75, 1, 55, 102]
#for i in range(0, len(e)):
# solver.add(b[i] == e[i])
solver.add(b[0] == x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[11] ^ x[14])
solver.add(b[1] == x[0] ^ x[1] ^ x[8] ^ x[11] ^ x[13] ^ x[14])
solver.add(b[2] == x[0] ^ x[1] ^ x[2] ^ x[4] ^ x[5] ^ x[8] ^ x[9] ^ x[10] ^ x[13] ^ x[14] ^ x[15])
solver.add(b[3] == x[5] ^ x[6] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[15])
solver.add(b[4] == x[1] ^ x[6] ^ x[7] ^ x[8] ^ x[12] ^ x[13] ^ x[14] ^ x[15])
solver.add(b[5] == x[0] ^ x[4] ^ x[7] ^ x[8] ^ x[9] ^ x[10] ^ x[12] ^ x[13] ^ x[14] ^ x[15])
solver.add(b[6] == x[1] ^ x[3] ^ x[7] ^ x[9] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[15])
solver.add(b[7] == x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[8] ^ x[10] ^ x[11] ^ x[14])
solver.add(b[8] == x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[12])
solver.add(b[9] == x[6] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[15])
solver.add(b[10] == x[0] ^ x[3] ^ x[4] ^ x[7] ^ x[8] ^ x[10] ^ x[11] ^ x[12] ^ x[13] ^ x[14] ^ x[15])
solver.add(b[11] == x[0] ^ x[2] ^ x[4] ^ x[6] ^ x[13])
solver.add(b[12] == x[0] ^ x[3] ^ x[6] ^ x[7] ^ x[10] ^ x[12] ^ x[15])
solver.add(b[13] == x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7] ^ x[11] ^ x[12] ^ x[13] ^ x[14])
solver.add(b[14] == x[1] ^ x[2] ^ x[3] ^ x[5] ^ x[7] ^ x[11] ^ x[13] ^ x[14] ^ x[15])
solver.add(b[15] == x[1] ^ x[3] ^ x[5] ^ x[9] ^ x[10] ^ x[11] ^ x[13] ^ x[15])
print solver.check()
modl = solver.model()
print(modl)
And finally the launch code:
5C0G7TY2LWI2YXMB
An old NES game that has a simple patch to get to the flag scene.
snake
1
The Flare team is attempting to pivot to full-time twitch streaming video games instead of reverse engineering computer software all day. We wrote our own classic NES game to stream content that nobody else has seen and watch those subscribers flow in. It turned out to be too hard for us to beat so we gave up. See if you can beat it and capture the internet points that we failed to collect.
Now I anticipated that there was going to be an NES challenge based on one of Nick Harbour's tweets. Luckily fceux emulator is all that you really needed for this challenge:
I didn't know anything about reversing NES games prior to this challenge. But any assembly language is just like any other assembly language and is easy to pick up. Looking up past NES CTF writeups and NESdev wiki was what I found most useful:
http://wiki.nesdev.com/w/index.php/Tools
https://wiki.nesdev.com/w/index.php/PPU_nametables
So reading other CTF challenges gave me a clue to look at the PPU tables for any interesting information. I see that the tiled images of characters would probably be the flag. The next step is to find out how they are mapped and hardcoded in the NES code.
I probably spent way too much time learning about NES reversing than actually solving the challenge. IDA mac doesn't really have a great NES plugin precompiled but as long as you use M6502 microprocessor when you load the bin file it should be fine. Here is what I used as an instruction resource http://obelisk.me.uk/6502/reference.html
In your disassembler be sure to rebase the image to 0xC000 and change the entrypoint to 0x10. At the end of the NES binary is where all the data is stored to display the tiles from the PPU tables.
When the snake is changing position, the assembly compares (CMP) the contents of the accumulator register at offset 0xC300. If you patch the branch instruction at offset 0xC302 to a JMP it will force the control flow to continue on to display the flag.
A broken header x32 Windows PE that has some Rick-Rolling and Anti-Debugging fun.
reloadered
1
This is a simple challenge, enter the password, receive the key. I hear that it caused problems when trying to analyze it with ghidra. Remember that valid flare-on flags will always end with @flare-on.com
7zip password: flare
Luckily the challenge text gave a really good hint as to what was broken about it. Typically disassemblers fail when there is an unexpected value being processed such as addresses being processed as an integer rather than an unsigned integer. I had guessed right off the bat that Ghidra was having a header parser issue and I was right.
The ghidra log was throwing this error.
I went to go check the source code at the link below. And it was improperly handling the header base addressing. https://github.com/NationalSecurityAgency/ghidra/blob/5e593cb4ae18903b053ac4c84664ee3c632bd95f/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java#L313
AddressRange brddRange =
new AddressRangeImpl(space.getAddress(originalImageBase + brdd.getVirtualAddress()),
space.getAddress(originalImageBase + brdd.getVirtualAddress() + brdd.getSize()));
Luckily IDA doesn't have this same problem but you can always change the base image to a normal address like 0x400000.
When you use a debugger it takes you down a path that seems way too simple to be the solution. A simple if statement that you can easily do by hand and the key.
if ((((((iVar3 == 0xb) && (pcVar2[1] == 'o')) && (((int)pcVar2[2] & 0xfffffffU) == 0x54)) &&
((pcVar2[10] == 'G' && ((byte)~pcVar2[7] == 0xad)))) &&
(((byte)(pcVar2[4] ^ pcVar2[3]) == 0x41 &&
(((int)*pcVar2 * 3 - 0xf6U < 2 && ((int)pcVar2[8] * 0xc800 - 0x520800U < 3)))))) &&
((pcVar2[6] == 'e' && (((pcVar2[5] & 1U) == 0 && (pcVar2[9] == 'n')))))) {
uVar4 = 0;
Obviously it presents you with a fake flag.
If you throw the binary in a Hex editor there is a dead give away of where executable code might be placed. All the 0x90 nops are highly suspicious.
Before main is even called, there is bootstrap code that sets up crt libs and arguments. At offset 0x1A4A is a call to ___scrt_is_nonwritable_in_current_image
. You will want to stepover this function or modify the branch at 0x1A52 to continue to the call to esi at 0x1A62 and step into. This is where the 0x90 nops are filled in with new code.
Add another breakpoint at offset 0x15AD to get past the performance time checks.
I was able to extract out the bytes that were placed in that section and just patched the binary at offset 0x6D0 so that I could view it casually in IDA and Ghidra.
The following code is actually what is decrypting the key:
_Buf = (char *)calloc(0xe,1);
/* Call print */
FUN_004016a0("Enter key: ",uVar15);
_File = (FILE *)__acrt_iob_func(0);
fgets(_Buf,0xe,_File);
_Buf[0xd] = 0;
cVar2 = *_Buf;
iVar8 = 0;
cVar3 = cVar2;
while (cVar3 != 0) {
iVar8 = iVar8 + 1;
cVar3 = _Buf[iVar8];
}
if (_Buf[iVar8 + -1] == '\n') {
iVar8 = 0;
while (cVar2 != 0) {
iVar8 = iVar8 + 1;
cVar2 = _Buf[iVar8];
}
_Buf[iVar8 + -1] = 0;
}
uVar11 = 0;
cVar2 = *_Buf;
while (cVar2 != 0) {
uVar11 = uVar11 + 1;
cVar2 = _Buf[uVar11];
}
uVar6 = 0;
do {
uVar9 = uVar6;
abStack280[uVar9] = _Buf[uVar9 % uVar11] ^ *(byte *)((int)&local_160 + uVar9);
uVar6 = uVar9 + 1;
} while (uVar9 + 1 < 0x35);
abStack280[uVar9] = 0;
if (uVar9 + 1 < 0x100) {
abStack280[uVar9 + 1] = 0;
_Buf = strstr((char *)abStack280,"@flare-on.com");
if (_Buf == (char *)0x0) {
FUN_004016a0("\nERROR: Wrong key!\n",uVar16);
(*_DAT_000130a4)(0);
}
FUN_004016a0("Here is your prize:\n\n\t%s\n",0xe8);
(*_DAT_000130a4)(0);
}
The hint here is the 0x35 which gives away the length of a hardcoded value. Apart of the code that was placed in the 0x90 nop section contains the byte array that is being used in the sequence:
The final value is:
001DFDF0 7A 17 08 34 17 31 3B 25 5B 18 2E 3A 15 56 0E 11 z..4.1;%[..:.V..
001DFE00 3E 0D 11 3B 24 21 31 06 3C 26 7C 3C 0D 24 16 3A >..;$!1.<&|<.$.:
001DFE10 14 79 01 3A 18 5A 58 73 2E 09 00 16 00 49 22 01 .y.:.ZXs.....I".
001DFE20 40 08 0A 14 00
It's then easy to determine the decoder key with the simple Xor:
33 48 65 61 64 65 64 4d 6f 6e 6B 65 79 -> 3HeadedMonkey
Here is the solution:
A Windows exe based on ransomware based on the Movie Zoolander. So hot right now. I actually used Ghidra the most on this challenge.
Mugatu
1
Hello,
I'm working an incident response case for Derek Zoolander. He clicked a link and was infected with MugatuWare! As a result, his new headshot compilation GIF was encrypted. To secure an upcoming runway show, Derek needs this GIF decrypted; however, he refuses to pay the ransom. We received an additional encrypted GIF from an anonymous informant. The informant told us the GIF should help in our decryption efforts, but we were unable to figure it out. We're reaching out to you, our best malware analyst, in hopes that you can reverse engineer this malware and decrypt Derek's GIF.
I've included a directory full of files containing:
MugatuWare malware
Ransom note (GIFtToDerek.txt)
Encrypted headshot GIF (best.gif.Mugatu)
Encrypted informant GIF (the_key_to_success_0000.gif.Mugatu)
Thanks, Roy
7zip password: flare
Typically ransomware does a few tricks for anti-analysis and layered execution (hide shit in memory). Ransomware will also will create a ransom note, display a ransom note image to the user, and rename encrypted files.
One of the anti-analysis techniques is jumbling the IAT. The correct Windows API will resolve dynamically. You can choose to fix up the IAT or just continue to debug and fix up along the way. Luckily x32dbg will populate the right Windows API during runtime.
I've summarized the functions that I chose to focus on:
1) 0x1cda WinMain
2) 0x1dad HTTP request to handle some twitter data, which you can patch to skip over. Nop out the branch at 0x1DB6:
3) 0x166e Load Images from Resources
4) 0x1491 Call Virtual Protect and Run DLL entrypoint function
5) 0x1447 Get function offset from DLL exported function
6) @0x1e29 CreateThread pointing to the DLL exported function 0x1724
In function 0x1491, a dll is loaded in memory but is fairly easy to carve out. Like the parent exe, the IAT of this dll is also jumbled so it's helpful to write down all the Windows API called dynamically.
Function 0x1229 recursively traverses the filesystem looking for folder really, ridiculously good looking gifs
.
It also dumps the ransom note in that same folder.
A few functions deep, the encrypt function is being called dynamically. It's highly suspicious when malware uses CreateFileMapping to do file manipulation in memory. And the first 4 bytes of this string is used as a part of the key to encrypt the files.
00000000 54 4f 44 44 44 44 44 44 44 44 44 44 44 44 44 44 |TODDDDDDDDDDDDDD|
00000010 44 44 44 44 72 65 61 6c 6c 79 2c 20 72 65 61 6c |DDDDreally, real|
00000020 6c 79 2c 20 72 65 61 6c 6c 79 2c 20 72 69 64 69 |ly, really, ridi|
00000030 63 75 6c 6f 75 73 6c 79 20 67 6f 6f 64 20 6c 6f |culously good lo|
00000040 6f 6b 69 6e 67 20 67 69 66 73 |oking gifs|
Finally the encrypt function at 0x16b9:
void ENCRYPT(int twentyhex,uint *chari,int TODD)
{
uint keyi;
uint chararray;
uint ivar;
uint charrout;
charrout = *chari;
chararray = chari[1];
ivar = 0;
do {
keyi = (uint)*(byte *)((ivar & 3) + TODD) + ivar;
ivar = ivar + 0x9e3779b9;
charrout = charrout + ((chararray << 4 ^ chararray >> 5) + chararray ^ keyi);
chararray = chararray +
((charrout * 0x10 ^ charrout >> 5) + charrout ^
(uint)*(byte *)((ivar >> 0xb & 3) + TODD) + ivar);
twentyhex = twentyhex + -1;
} while (twentyhex != 0);
*chari = charrout;
chari[1] = chararray;
return;
}
I used this cute little image as a unit test. Just place a gif in the really, ridiculously good looking gifs
folder so that you can verify your encrypt algorithm. It helps verify your decryption function.
Here is my decrypt function in python:
def decrypt(data, filename, key):
output = []
looplen = len(data) / 8
i = 0
for n in range(looplen):
num = data[i:i+4]
num2 = data[i+4:i+8]
intA = unpack("I", num)[0]
intB = unpack("I", num2)[0]
storeA = intA
storeB = intB
ivarloop = 0x20
for x in range(0x20):
ivar = 0
for x in range(ivarloop):
ivar = (ivar - 0x61C88647) & 0xFFFFFFFF
keyi = key[ivar & 3]
keyi = keyi + ivar
temp = storeA
temp2 = temp
temp2 = (temp >> 5) & 0xFFFFFFFF
temp3 = (temp << 4) & 0xFFFFFFFF
temp2 = temp2 ^ temp3
ivar2 = ivar >> 0xB
temp = (temp2 + temp) & 0xFFFFFFFF
keyi2 = key[ivar2 & 3]
keyi2 = (keyi2 + ivar) & 0xFFFFFFFF
temp3 = temp ^ keyi2
storeB = (storeB - temp3) & 0xFFFFFFFF
ivar = 0
for x in range(ivarloop-1):
ivar = (ivar - 0x61C88647) & 0xFFFFFFFF
keyi = key[ivar & 3]
intBlow = (storeB << 4) & 0xFFFFFFFF
intBhigh = (storeB >> 5) & 0xFFFFFFFF
keyi = keyi + ivar
temp = intBlow ^ intBhigh
ivar = (ivar + 0x61C88647) & 0xFFFFFFFF
temp = (storeB + temp) & 0xFFFFFFFF
temp = (temp ^ keyi) & 0xFFFFFFFF
temp = (storeA - temp) & 0xFFFFFFFF
storeA = temp
ivarloop -=1
finalA = pack("I", storeA)
finalB = pack("I", storeB)
for a in finalA:
output.append(a)
for a in finalB:
output.append(a)
i+=8
if i < len(data):
for a in data[i:]:
output.append(a)
with open(filename + ".dec.gif", "wb") as test:
for o in output:
test.write(o)
Now the biggest hint that the TODD
string gave is that the key is only 4 bytes long. Sometimes real ransomware will put hints into the encrypted filename. So the encrypted test image has this 0000
in the filename. Turns out this was the key in as in bytes: 0x00,0x00,0x00,0x00
.
the_key_to_success_0000.gif.Mugatu
The Flare team was nice enough to give you a hint for the final key in the best.gif image. Where 0x31
is the first byte in 4 byte array. From here on out you simple just need to bruteforce the last 3 bytes.
SOLUTION = [49, 115, 53, 177]
best.gif.Mugatu
Another esoteric language Windows x64 exe that uses AVX2 registers and instructions. For this challenge I used rust lang to help handle large integers.
vv_max
1
Hey, at least its not subleq.
7zip password: flare
This was actually one of my favorite challenges this year. I remember doing the subleq challenge from flareon4 so I knew what to expect for this challenge. It's a simple x64 windows PE program that uses a blob of bytes as the intermediate language for the instructions. The only issue I had was finding the right language to code this up. Rust lang works really well with handling register-like math operations and it also had a nice library called bigint which made it easy to work with large integers.
The program sets up the memory space storing the instruction blob and generating a vtable that handles all the instruction AVX2 instructions.
Simple vtable:
000000000021EF40 00 00 00 00 00 00 00 00 B0 17 E2 3F 01 00 00 00 ........°.â?....
000000000021EF50 00 23 E2 3F 01 00 00 00 E0 21 E2 3F 01 00 00 00 .#â?....à!â?....
000000000021EF60 30 30 E2 3F 01 00 00 00 40 27 E2 3F 01 00 00 00 00â?....@'â?....
000000000021EF70 D0 1D E2 3F 01 00 00 00 30 26 E2 3F 01 00 00 00 Ð.â?....0&â?....
000000000021EF80 B0 1C E2 3F 01 00 00 00 10 2F E2 3F 01 00 00 00 °.â?...../â?....
000000000021EF90 50 19 E2 3F 01 00 00 00 B0 2B E2 3F 01 00 00 00 P.â?....°+â?....
000000000021EFA0 70 1A E2 3F 01 00 00 00 D0 2C E2 3F 01 00 00 00 p.â?....Ð,â?....
000000000021EFB0 90 1B E2 3F 01 00 00 00 F0 2D E2 3F 01 00 00 00 ..â?....ð-â?....
000000000021EFC0 E0 24 E2 3F 01 00 00 00 20 24 E2 3F 01 00 00 00 à$â?.... $â?....
000000000021EFD0 10 20 E2 3F 01 00 00 00 80 29 E2 3F 01 00 00 00 . â?.....)â?....
000000000021EFE0 D0 20 E2 3F 01 00 00 00 90 2A E2 3F 01 00 00 00 Ð â?.....*â?....
000000000021EFF0 60 28 E2 3F 01 00 00 00 F0 1E E2 3F 01 00 00 00 `(â?....ð.â?....
000000000021F000 00 26 E2 3F 01 00 00 00 00 00 00 00 00 00 00 00 .&â?............
These instructions use a set of 32 byte arrays as ways to store values. I used colors to help me visualize the locations :) :
I was too lazy to code up every line so I just carved out the blob instructions to be processed as bytes in rust. Keep in mind is that all math operations have to convert the bytes for little endian.
Program.bin:
00000000: 0011 0041 4243 4441 4243 4441 0000 0000 ...ABCDABCDA....
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000020: 0000 0011 012f 2f2f 2f2f 2f2f 2f2f 2f2f .....///////////
00000030: 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f ////////////////
00000040: 2f2f 2f2f 2f11 0315 1111 1111 1111 1111 /////...........
00000050: 1113 1a1b 1b1b 1a15 1111 1111 1111 1111 ................
00000060: 1113 1a1b 1b1b 1a11 0410 1001 0204 0804 ................
00000070: 0810 1010 1010 1010 1010 1001 0204 0804 ................
00000080: 0810 1010 1010 1010 1011 0500 1013 04bf ................
00000090: bfb9 b900 0000 0000 0000 0000 1013 04bf ................
000000a0: bfb9 b900 0000 0000 0000 0011 062f 2f2f .............///
000000b0: 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f ////////////////
000000c0: 2f2f 2f2f 2f2f 2f2f 2f2f 2f2f 2f11 0a40 /////////////..@
000000d0: 0140 0140 0140 0140 0140 0140 0140 0140 .@.@.@.@.@.@.@.@
000000e0: 0140 0140 0140 0140 0140 0140 0140 0111 .@.@.@.@.@.@.@..
000000f0: 0b00 1001 0000 1001 0000 1001 0000 1001 ................
00000100: 0000 1001 0000 1001 0000 1001 0000 1001 ................
00000110: 0011 0c02 0100 0605 040a 0908 0e0d 0cff ................
00000120: ffff ff02 0100 0605 040a 0908 0e0d 0cff ................
00000130: ffff ff11 0d00 0000 0001 0000 0002 0000 ................
00000140: 0004 0000 0005 0000 0006 0000 00ff ffff ................
00000150: ffff ffff ff11 10ff ffff ffff ffff ffff ................
00000160: ffff ffff ffff ffff ffff ffff ffff ffff ................
00000170: ffff ffff ffff ff11 1119 cde0 5bab d983 ............[...
00000180: 1f8c 6805 9b7f 520e 513a f54f a572 f36e ..h...R.Q:.O.r.n
00000190: 3c85 ae67 bb67 e609 6a11 12d5 5e1c aba4 <..g.g..j...^...
000001a0: 823f 92f1 11f1 595b c256 39a5 dbb5 e9cf .?....Y[.V9.....
000001b0: fbc0 b591 4437 7198 2f8a 4211 1304 0000 ....D7q./.B.....
000001c0: 0005 0000 0006 0000 0007 0000 0000 0000 ................
000001d0: 0001 0000 0002 0000 0003 0000 0011 1400 ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 0011 ................
00000200: 1501 0000 0001 0000 0001 0000 0001 0000 ................
00000210: 0001 0000 0001 0000 0001 0000 0001 0000 ................
00000220: 0011 1602 0000 0002 0000 0002 0000 0002 ................
00000230: 0000 0002 0000 0002 0000 0002 0000 0002 ................
00000240: 0000 0011 1703 0000 0003 0000 0003 0000 ................
00000250: 0003 0000 0003 0000 0003 0000 0003 0000 ................
00000260: 0003 0000 0011 1804 0000 0004 0000 0004 ................
00000270: 0000 0004 0000 0004 0000 0004 0000 0004 ................
00000280: 0000 0004 0000 0011 1905 0000 0005 0000 ................
00000290: 0005 0000 0005 0000 0005 0000 0005 0000 ................
000002a0: 0005 0000 0005 0000 0011 1a06 0000 0006 ................
000002b0: 0000 0006 0000 0006 0000 0006 0000 0006 ................
000002c0: 0000 0006 0000 0006 0000 0011 1b07 0000 ................
000002d0: 0007 0000 0007 0000 0007 0000 0007 0000 ................
000002e0: 0007 0000 0007 0000 0007 0000 0015 1400 ................
000002f0: 1415 1500 1515 1600 1615 1700 1715 1800 ................
00000300: 1815 1900 1915 1a00 1a15 1b00 1b12 0701 ................
00000310: 0403 1c14 1503 1c1c 1603 1c1c 1703 1c1c ................
00000320: 1803 1c1c 1903 1c1c 1a03 1c1c 1b05 0707 ................
00000330: 0613 1d11 0712 1e11 1904 0f1d 1e16 0801 ................
00000340: 0613 1d11 1512 1e11 0b04 1d1d 1e03 0f0f ................
00000350: 1d16 0801 0613 1d11 1a12 1e11 0604 1d1d ................
00000360: 1e03 0f0f 1d03 1d14 1005 1e14 1203 1d1d ................
00000370: 1e0b 0f1d 0f0b 140f 0007 0708 0703 1d14 ................
00000380: 1c15 111d 1314 0705 0713 1d11 0712 1e11 ................
00000390: 1904 0f1d 1e13 1d11 1512 1e11 0b04 1d1d ................
000003a0: 1e03 0f0f 1d13 1d11 1a12 1e11 0604 1d1d ................
000003b0: 1e03 0f0f 1d07 0201 0703 1d15 1005 1e15 ................
000003c0: 1203 1d1d 1e0b 0f1d 0f0b 150f 0003 1d15 ................
000003d0: 1c15 111d 1303 1414 1513 1d11 0712 1e11 ................
000003e0: 1904 0f1d 1e13 1d11 1512 1e11 0b04 1d1d ................
000003f0: 1e03 0f0f 1d13 1d11 1a12 1e11 0604 1d1d ................
00000400: 1e03 0f0f 1d01 0702 0a03 1d16 1005 1e16 ................
00000410: 1203 1d1d 1e0b 0f1d 0f0b 160f 0003 1d16 ................
00000420: 1c15 111d 1303 1414 1613 1d11 0712 1e11 ................
00000430: 1904 0f1d 1e13 1d11 1512 1e11 0b04 1d1d ................
00000440: 1e03 0f0f 1d13 1d11 1a12 1e11 0604 1d1d ................
00000450: 1e03 0f0f 1d02 0207 0b03 1d17 1005 1e17 ................
00000460: 1203 1d1d 1e0b 0f1d 0f0b 170f 0003 1d17 ................
00000470: 1c15 111d 1303 1414 1713 1d11 0712 1e11 ................
00000480: 1904 0f1d 1e13 1d11 1512 1e11 0b04 1d1d ................
00000490: 1e03 0f0f 1d13 1d11 1a12 1e11 0604 1d1d ................
000004a0: 1e03 0f0f 1d03 1d18 1005 1e18 1203 1d1d ................
000004b0: 1e0b 0f1d 0f0b 180f 0003 1d18 1c15 111d ................
000004c0: 1303 1414 1813 1d11 0712 1e11 1904 0f1d ................
000004d0: 1e13 1d11 1512 1e11 0b04 1d1d 1e03 0f0f ................
000004e0: 1d13 1d11 1a12 1e11 0604 1d1d 1e03 0f0f ................
000004f0: 1d03 1d19 1005 1e19 1203 1d1d 1e0b 0f1d ................
00000500: 0f0b 190f 0003 1d19 1c15 111d 1303 1414 ................
00000510: 1914 0202 0c13 1d11 0712 1e11 1904 0f1d ................
00000520: 1e13 1d11 1512 1e11 0b04 1d1d 1e03 0f0f ................
00000530: 1d13 1d11 1a12 1e11 0604 1d1d 1e03 0f0f ................
00000540: 1d03 1d1a 1005 1e1a 1203 1d1d 1e0b 0f1d ................
00000550: 0f0b 1a0f 0003 1d1a 1c15 111d 1303 1414 ................
00000560: 1a13 1d11 0712 1e11 1904 0f1d 1e13 1d11 ................
00000570: 1512 1e11 0b04 1d1d 1e03 0f0f 1d13 1d11 ................
00000580: 1a12 1e11 0604 1d1d 1e03 0f0f 1d15 0202 ................
00000590: 0d03 1d1b 1005 1e1b 1203 1d1d 1e0b 0f1d ................
000005a0: 0f0b 1b0f 0003 1d1b 1c15 111d 1303 1414 ................
000005b0: 1b11 13ff ffff ffff ffff ffff ffff ffff ................
000005c0: ffff ffff ffff ffff ffff ff00 0000 0000 ................
000005d0: 0000 0005 1414 1311 1f22 1e1b 4b2d 1705 ........."..K-..
000005e0: 0c15 590e 7823 2633 2e10 074f 7318 3658 ..Y.x#&3...Os.6X
000005f0: 0b29 0f5c 3a0c 6276 21ff .).\:.bv!.
The next function 0x1830 will process the program binary data be looping through the bytes. Every instruction has a set size for immediate values and math operations. One of the final instructions will compare one of the command line arguments post processed to 0x1ea1f3b229c845e81a861c08a82aa70a615ed201acb27070
. The other command line argument is actually given to you in the final function: FLARE2019
.
Here is some quick rust code for processing the program:
extern crate bigint;
extern crate byteorder;
use bigint::U256;
use byteorder::{BigEndian, ReadBytesExt, LittleEndian};
use std::mem::transmute;
use std::fs::File;
use std::io::Read;
fn shrd_2980(val: U256, count : u32) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut out_vec: Vec<u8> = vec![];
for mut chunk in valvec.chunks(4) {
let mut result: u32 = chunk.read_u32::<BigEndian>().unwrap();
result = result >> count;
let dst: [u8; 4] = unsafe { transmute(result.to_be()) };
out_vec.extend(dst.to_vec().iter().cloned())
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn shld_20d0(val: U256, count : u32) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut out_vec: Vec<u8> = vec![];
for mut chunk in valvec.chunks(4) {
let mut result: u32 = chunk.read_u32::<BigEndian>().unwrap();
result = result << count;
let dst: [u8; 4] = unsafe { transmute(result.to_be()) };
out_vec.extend(dst.to_vec().iter().cloned());
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn addb_1cb0(val: U256, val2: U256) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut valvec2: [u8; 32] = [0; 32];
val2.to_big_endian(&mut valvec2);
let mut out_vec: Vec<u8> = vec![0; 32];
let mut i = 0;
while i < valvec.len(){
let int1 = valvec[i] as i8;
let int2 = valvec2[i] as i8;
let (r, _) = int1.overflowing_add(int2);
let result = r as u8;
out_vec[i] = result;
i+=1;
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn addd_1a70(val: U256, val2: U256) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut valvec2: [u8; 32] = [0; 32];
val2.to_big_endian(&mut valvec2);
let mut out_vec: Vec<u8> = vec![];
let mut i = 0;
while i < valvec.len(){
let mut chunk = &valvec[i..i+4];
let mut chunk2 = &valvec2[i..i+4];
let int1: i32 = chunk.read_i32::<BigEndian>().unwrap();
let int2: i32 = chunk2.read_i32::<BigEndian>().unwrap();
let (r, _) = int1.overflowing_add(int2);
let result = r as u32;
let dst: [u8; 4] = unsafe { transmute(result.to_be()) };
out_vec.extend(dst.to_vec().iter().cloned());
i+=4;
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn vpmaddubsw_2300(val: U256, val2: U256) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut valvec2: [u8; 32] = [0; 32];
val2.to_big_endian(&mut valvec2);
let mut out_vec: Vec<u8> = vec![];
let mut i = 0;
while i < valvec.len(){
let int1 = valvec[i] as u16;
let int2 = valvec2[i] as u16;
let int3 = valvec[i+1] as u16;
let int4 = valvec2[i+1] as u16;
let (r1, _) = int1.overflowing_mul(int2);
let (r2, _) = int3.overflowing_mul(int4);
let (result,_) = r1.overflowing_add(r2);
let dst: [u8; 2] = unsafe { transmute(result.to_be()) };
out_vec.extend(dst.to_vec().iter().cloned());
i+=2;
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn vpmaddwd_21e0(val: U256, val2: U256) -> U256 {
let mut valvec: [u8; 32] = [0; 32];
val.to_big_endian(&mut valvec);
let mut valvec2: [u8; 32] = [0; 32];
val2.to_big_endian(&mut valvec2);
let mut out_vec: Vec<u8> = vec![];
let mut i = 0;
while i < valvec.len(){
let mut int1 = &valvec[i..i+2];
let num1: u32 = int1.read_u16::<BigEndian>().unwrap() as u32;
let mut int2 = &valvec2[i..i+2];
let num2: u32 = int2.read_u16::<BigEndian>().unwrap() as u32;
let mut int3 = &valvec[i+2..i+2+2];
let num3: u32 = int3.read_u16::<BigEndian>().unwrap() as u32;
let mut int4 = &valvec2[i+2..i+2+2];
let num4: u32 = int4.read_u16::<BigEndian>().unwrap() as u32;
let (r1, _) = num1.overflowing_mul(num2);
let (r2, _) = num3.overflowing_mul(num4);
let (result,_) = r1.overflowing_add(r2);
let dst: [u8; 4] = unsafe { transmute(result.to_be()) };
out_vec.extend(dst.to_vec().iter().cloned());
i+=4;
}
let output: U256 = out_vec.as_slice().into();
return output;
}
fn shuffle_2a90(val: U256, val2: U256) -> U256 {
let mut a: [u8; 32] = [0; 32];
val.to_little_endian(&mut a);
let mut b: [u8; 32] = [0; 32];
val2.to_little_endian(&mut b);
let mut r = [0; 32];
for i in 0..16 {
// if the most significant bit of b is set,
// then the destination byte is set to 0.
if b[i] & 0x80 == 0u8 {
r[i] = a[(b[i] % 16) as usize];
}
if b[i + 16] & 0x80 == 0u8 {
r[i + 16] = a[(b[i + 16] % 16 + 16) as usize];
}
}
let output: U256 = U256::from_little_endian(&r);
return output;
}
fn xor_3030(val: U256, val2: U256) -> U256 {
let result = val ^ val2;
return result;
}
fn or_2740( val: U256, val2: U256) -> U256 {
let result = val | val2;
return result;
}
fn and_1dd0( val: U256, val2: U256) -> U256 {
let result = val & val2;
return result;
}
fn permute(val: U256, val2: U256) -> (U256, bool) {
let mut valvec: [u8; 32] = [0; 32];
let mut valvec2: [u8; 32] = [0; 32];
val.to_little_endian(&mut valvec);
val2.to_little_endian(&mut valvec2);
let mut temp: Vec<u8> = vec![];
let test: U256 = 0.into();
let mut i = 0;
while i < valvec.len(){
if val2 == test{
let out: Vec<&[u8]> = valvec.chunks(4).collect();
temp.extend(out[0].iter().cloned());
} else {
let mut chunk = &valvec2[i..i+4];
let result: u32 = chunk.read_u32::<LittleEndian>().unwrap();
let out: Vec<&[u8]> = valvec.chunks(4).collect();
if result as usize <= out.len(){
temp.extend(out[result as usize].iter().cloned());
} else {
let newval: [u8; 4] = unsafe { transmute(0u32.to_be()) };
temp.extend(newval.iter().cloned());
}
}
i+=4;
}
let result: U256 = U256::from_little_endian(temp.as_slice());
return (result, true);
}
fn run(testval:u32, s: &Vec<u8>) -> bool {
let newval: [u8; 4] = unsafe { transmute(testval.to_be()) };
let arg1: [u8; 32] = [
0x46,0x4c,0x41,0x52,0x45,0x32,0x30,0x31,
0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
let mut arg2: [u8; 32] = [
0x5d,0x48,0x43,0x79,0x72,0x41,0x48,0x53,
0x58,0x6D,0x45,0x4b,0x70,0x79,0x71,0x18,
0x45,0x42,0x79,0x47,0x47,0x75,0x68,0x46,
0x79,0x43,0x6d,0x79,0x38,0x36,0x45,0x65];
let solution2: U256 = U256::from_little_endian(&arg2);
let solution: U256 = U256::from_little_endian(&arg1);
let mut memory: Vec<U256> = vec![ 0.into(); 32];
let mut k = 0;
for _i in 0..0x1000{
let b = s[k];
match b {
0 => {
println!("create memory");
k+= 1 },
1 => {
println!("muladdu 2300 {}= {} * {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = vpmaddubsw_2300(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
2 => {
println!("muladd 21E0 {}= {} * {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = vpmaddwd_21e0(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
3 => {
println!("xor 0303 {}= {} ^ {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = xor_3030(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
4 => {
println!("or 2407 {}= {} | {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = or_2740(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
5 => {
println!("and 1DD0 {}= {} & {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = and_1dd0(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
6 => {
println!("xor 2630");
k+=4},
7 => {
println!("addb 1CB0 {}= {} + {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = addb_1cb0(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
8 => {
println!("subb 2F10 {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
9 => {
println!("addw 1950 {}= {} + {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
10 => {
println!("subw 2BB0 {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
11 => {
println!("addd 1A70 {}= {} + {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = addd_1a70(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
12 => {
println!("subd 2CD0 {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
13 => {
println!("addq {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
14 => {
println!("subq {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
15 => {
println!("mulq {}= {} - {}", s[k+1], s[k+2], s[k+3]);
k+= 4},
16 => {},
17 => {
let mut store: U256 = U256::from_little_endian(&s[k+2..k+34]);
if s[k+1] == 0{
memory[s[k+1] as usize] = solution;
} else if s[k+1] == 1{
memory[s[k+1] as usize] = solution2;
}else {
memory[s[k+1] as usize] = store;
}
println!("store 2010 {}={:x}", s[k+1], memory[s[k+1] as usize]);
k+= 34},
18 => {
println!("shr 2980 {}= mem {} >> 0x{:x}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = shrd_2980(memory[s[k+2] as usize], s[k+3] as u32);
k+= 4},
19 => {
println!("shl 20D0 {}= mem {} << 0x{:x}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = shld_20d0(memory[s[k+2] as usize], s[k+3] as u32);
k+= 4},
20 => {
println!("shuffle 2A90 {}= {} <- {}", s[k+1], s[k+2], s[k+3]);
memory[s[k+1] as usize] = shuffle_2a90(memory[s[k+2] as usize], memory[s[k+3] as usize]);
k+= 4},
21 => {
println!("permute 2860 {}= {} <- {}", s[k+1], s[k+2], s[k+3]);
let (result, success) = permute(memory[s[k+2] as usize], memory[s[k+3] as usize]);
if success {
memory[s[k+1] as usize] = result;
}
k+= 4},
22 => {
let mut _iseq = false;
if memory[s[k+2] as usize] == memory[s[k+3] as usize]{
_iseq = true;
let istrue: [u8; 32] = [0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff];
let mut trueval: U256 = istrue.into();
memory[s[k+1] as usize] = trueval;
} else {
let mut falseval: U256 = 0.into();
memory[s[k+1] as usize] = falseval;
}
println!("cmpeqb {} = {} == {} {}", s[k+1], s[k+2], s[k+3], _iseq);
k+= 4},
23 => {k+= 1},
0xff => {
println!("=END=");
break},
_=> println!("?"),
}
if k > s.len(){
break;
}
}
let finalval: U256 = memory[2];
let finalval2: U256 = memory[20];
let mut b: [u8; 32] = [0; 32];
finalval.to_little_endian(&mut b);
let mut a: [u8; 32] = [0; 32];
finalval2.to_little_endian(&mut a);
println!("{:x} {:x}", finalval, finalval2);
if b[0] == a[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]{
println!("{:x}", finalval2);
println!("{:x}", finalval);
return true;
}
return false;
}
fn main() {
let mut file = File::open("./program.bin").unwrap();
let mut s: Vec<u8> = Vec::with_capacity(file.metadata().unwrap().len() as usize);
file.read_to_end(&mut s).unwrap();
run(0u32, &s);
}
The final comparison value is only modified a few about 5 times in the whole program:
0x1ea1f3b229c845e81a861c08a82aa70a615ed201acb27070 ->
0x1ea1f3b2 29c845e81a861c0800000000a82aa70a615ed201acb27070 ->
0xf3a11e00c829b2001ae84500081c8600a72aa8005e610a00ac01d2007070b2 ->
0x11e0F3a09b20C82084501ae0c8600810aa80a72010a05e601d20ac000b20707 ->
0x1e043a3c3226023205212e0606320102282a32290a0426171207002b3202071c
Then the last instructions input values can be either done by hand or brute forced. In this case I just brute forced the input
let mut lookup: [u8; 32] = [
0x00,0x10,0x13,0x04,0xBF,0xBF,0xB9,0xB9,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x10,0x13,0x04,0xBF,0xBF,0xB9,0xB9,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
let mut b: [u8; 32] = [0; 32];
result5.to_little_endian(&mut b);
let mut k: usize = 0;
while k < b.len(){
for i in 0u32..0xFFFFFu32 {
let (res, overflow) = i.overflowing_shr(0x4);
let num1 = res & 0x2f2f;
let mut B: u8 = ((i >> 8) & 0xFF) as u8;
let mut A: u8 = (i & 0xFF) as u8;
let B1: u8 = ((num1 >> 8) & 0xFF) as u8;
let A1: u8 = (num1 & 0xFF) as u8;
if A1 < 32 && B1 < 32{
let (aa, _) = b[k].overflowing_sub(lookup[A1 as usize]);
let (bb, _) = b[k+1].overflowing_sub(lookup[B1 as usize]);
if aa == A && bb == B{
println!("{:x}->{:x} {:x}->{:x}",b[k],A,b[k+1],B);
}
} else {
continue;
}
}
k+=1;
}
The final flag is decrypted with a simple xor based on the input value you provided.
cHCyrAHSXmEKpyqoCByGGuhFyCmy86Ee
Example output:
A x64 Windows memory dump that contains a few kernel drivers and userland Dlls. Looks like a plugin style RAT. I used Rust lang again to help decrypt the data. This challenge had many parts to it. Hopefully I can cover all the major items.
help
1
You're my only hope FLARE-On player! One of our developers was hacked and we're not sure what they took. We managed to set up a packet capture on the network once we found out but they were definitely already on the system. I think whatever they installed must be buggy - it looks like they crashed our developer box. We saved off the dump file but I can't make heads or tails of it - PLEASE HELP!!!!!!
7Zip password: flare
If you open the crash dump in windbg it will show the exception address. This is the address where you should start looking in volatility. Below is a snippet.
FAULTING_IP:
+0
fffffa80`03f9c621 64a10000000050648925 mov eax,dword ptr fs:[2589645000000000h]
EXCEPTION_RECORD: fffff88007c6b958 -- (.exr 0xfffff88007c6b958)
ExceptionAddress: fffffa8003f9c621
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 0000000000000000
Parameter[1]: ffffffffffffffff
Attempt to read from address ffffffffffffffff
CONTEXT: fffff88007c6b1b0 -- (.cxr 0xfffff88007c6b1b0)
rax=fffffa8003f9c610 rbx=fffffa80040c65c0 rcx=fffffa80036ab5c0
rdx=fffff880033c8138 rsi=fffffa80018cc090 rdi=0000000000000001
rip=fffffa8003f9c621 rsp=fffff88007c6bb98 rbp=0000000007c6bbb0
r8=fffff80002c3f400 r9=0000000000000000 r10=0000000000000000
r11=fffff80002c3ae80 r12=fffffa80036ab5c0 r13=fffff880033bdcc0
r14=0000000000000000 r15=fffff80000b94080
iopl=0 nv up ei ng nz na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010286
fffffa80`03f9c621 64a10000000050648925 mov eax,dword ptr fs:[2589645000000000h] fs:0053:25896450`00000000=????????
Resetting default scope
So 0xfffffa800XXXXXXX is where interesting stuff is happening which means this could be a kernel driver. Now to run all the driver related commands (command references):
vol.py -f help.dmp --profile=Win7SP1x64 driverscan
Output:
Offset(P) #Ptr #Hnd Start Size Service Key Name Driver Name
------------------ -------- -------- ------------------ ------------------ -------------------- ------------ -----------
0x000000007d48d9a0 4 1 0xfffffa80042d0000 0xf000 FLARE...ed_0
0x000000007ff87db0 3 0 0xfffff88000e9c000 0xa4000 Wdf01000 Wdf01000 \Driver\Wdf01000
Modules:
vol.py -f help.dmp --profile=Win7SP1x64 drivermodule
Output:
Module Driver Alt. Name Service Key
------------------------------------ ------------------------ ------------------------ -----------
fileinfo.sys FileInfo FileInfo \FileSystem\FileInfo
UNKNOWN FLARE_Loaded_0
Module Scan:
vol.py -f help.dmp --profile=Win7SP1x64 modscan
Output:
Offset(P) Name Base Size File
------------------ -------------------- ------------------ ------------------ ----
0x000000007d48ff30 man.sys 0xfffff880033bc000 0xf000 \??\C:\Users\FLARE ON 2019\Desktop\man.sys
Now to extract man.sys and FLARE_Loaded_0.
vol.py -f help.dmp --profile=Win7SP1x64 moddump -r man.sys --dump-dir ../tmp/
Output Error:
Module Base Module Name Result
------------------ -------------------- ------
0xfffff880033bc000 man.sys Error: e_magic 0000 is not a valid DOS signature.
So it seemed that this driver did not have a header. I didn't feel like extracting out the driver from the help.dmp file manually so I decided to modify volatility code instead. I traced the error to the function get_nt_header
. It was just looking for a simple "MZ" to verify that it was an executable. So I just added this code to the function:
The next step was to rebuild the header of the driver, you can find the driver in memory or just copy an existing header and eyeball the section offsets. IDA should help you determine the driver entry function using it's FLIRT signatures.
I was familiar with writing file system filter drivers but not TCP stream filter drivers. I used the microsoft sample drivers as reference so that I could follow along.
Basically the callout classify function handles the stream buffers.
Stream encoding:
let mut key7777 = vec![0x4a, 0x1f, 0x4b, 0x1c, 0xb0, 0xd8, 0x25, 0xc7];
while i < encoded.len(){
let blah = encoded[i] ^ key7777[i%8] & 0xFF;
out[i] = blah;
i+=1;
}
It was easy to validate the decryption by looking for pool allocation tags in the help.dmp. I just used a hex editor to search for those.
VirtualAddress = ExAllocatePoolWithTag((POOL_TYPE)0x200, *(_QWORD *)(argData + 0x60), 'RALF');
The keys for each port were then easy to find in memory since the port preceded the key in hex:
Key for port 6666
D5 69 94 FA 25 EC DF DA
Key for port 7777
4a 1f 4b 1c b0 d8 25 c7
Key for port 8888
F7 8F 78 48 47 1A 44 9C
This driver serves as the kernel agent. It basically handles all the commands delivered by the userland m.dll it loaded into svchost.exe. The biggest hint here was the validation it did for the PDB file. Every PDB file related had a similar path. You could then search for all the other dll files in the help.dmp file.
e:\dropbox\dropbox\flareon_2019\code\cryptodll\objchk_win7_amd64\amd64\c.pdb
I certainly got quite annoyed from decrypting all those RC4 encrypted strings. Luckily cyberchef had a nifty tool to convert hex into bytes and then process those bytes into RC4. All the major Windows APIs and C std functions were dynamically loaded and executed. It was more tedious to reveal all those strings.
The reason why this challenge took so long, is that I forgot all about non-contiguous memory and Virtual Address Descriptor (VAD) trees. Luckily volatility has a command to dump these sections. Then all you need to do is find the memory dumps from svchost.
vol.py -f help.dmp --profile=Win7SP1x64 vaddump -D vads
svchost.exe.7e2a4b30.0x0000000000d50000-0x0000000000d52fff.dmp
svchost.exe.7e2a4b30.0x0000000000d60000-0x0000000000d62fff.dmp
svchost.exe.7e2a4b30.0x0000000000d80000-0x0000000000d85fff.dmp
...
After grabbing those dlls from memory it was all downhill from there.
All related dll files:
Name | Description |
filedll | Searches filesystem for target file with port 6666 |
screenshotdll | Takes a screenshot with port 7777 |
keylogdll | Monitors keys but ignores some special characters with port 8888 |
cryptodll | Compresses with LZNT1 and encrypts with RC4 with the %Username%\x00 |
networkdll | Sends network traffic |
m.dll | Main dll to handle commands from the C2 server with port 4444. Forwards commands to the main driver. |
Shellcode Driver | This is probably what caused the crash in the first place. |
Wireshark will allow you to dump TCP streams between 192.168.1.243 and 192.168.1.244. At first I wrote a simple PCAP parser program in rust because I originally thought every TCP packet was being modified instead of the stream. So I just ended dumping out all the streams by hand in wireshark which was no big deal.
First I started with decrypting the traffic on port 4444 which are the commands to the kernel agent. It was looking for a keepass kdb file.
00000000: 1f01 0000 b5da 80d1 7484 fade ab58 321e ........t....X2.
00000010: 0501 0000 433a 5c6b 6579 7061 7373 5c6b ....C:\keypass\k
00000020: 6579 732e 6b64 6200 0000 0000 0000 0000 eys.kdb.........
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000110: 0000 0000 0000 0000 002d 9cc6 0000 00 .........-.....
I looked up the header file for KeePass versions and I was able to carve out a KDB file from the help.dmp file. However, I wasn't sure at the time that this was the same KDB. (It actually was).
03 D9 A2 9A 65 FB 4B B5
03 D9 A2 9A 67 FB 4B B5
The next traffic I decrypted was on port 7777. Here are the decrypted screenshots I actually cared about:
I thought this search was quite funny:
At this point I was saddened that I had to find the master key from the keylogging output off of port 8888. So my next step was to decrypt the traffic for port 8888. The streams from the keylogger were using the LZNT1 and RC4 encryption from the crypto dll.
So I plugged in that key which I thought was the masterkey and realized that the length of the key in the screenshot did not match the length of the keylogger output. I had to revisit the keylogger dll to see what was going on.
Turns out it was actually not using the shift keys for special characters, dropping any special characters like "_", and only taking the lowercase version of the key.
I did a simple search in help.dmp for "th3_" and was able to partially find the key:
Master password was Th!s_iS_th3_3Nd!!!
And the flag was f0ll0w_th3_br34dcrumbs@flare-on.com
At this point I'm so happy. Much relieved. My husband is glad I can stop talking about flareon6.
My two favorite challenges this year as 11 & 12. I felt like the clue giving was very sophisticated. Compared to the last time I played (flareon4), I felt that this year was a bit easier. However, I'm very thankful that the Flare Team gives multiple weeks to finish these challenges. I only had a few hours each weekend and some time in the work week evenings to actually work on these challenges.
Thank you Flare Team, I very much enjoyed this year.