This page looks best with JavaScript enabled

Flare-On 8 2021 Challenge 7 Solution - 07_spel

Hosted by FireEye's FLARE team from 10 September - 22 October

 ·  ☕ 8 min read  ·  🌚 drome

Thanks drome for sharing his knowledge and skills! He completed all 10 challenges and this series of writeups is done by him :)

Details Links
Official Challenge Site https://flare-on.com/
Official Challenge Announcement https://www.fireeye.com/blog/threat-research/2021/08/announcing-the-eighth-annual-flare-on-challenge.html
Official Solutions https://www.mandiant.com/resources/flare-on-8-challenge-solutions
Official Challenge Binaries http://flare-on.com/files/Flare-On8_Challenges.zip

07_spel

Pro-tip: start disassembling this one then take a nice long break, you’ve earned it kid.
7-zip password: flare

The file given is a 64-bit executable with the following properties

arch     x86
baddr    0x140000000
binsz    4376064
bintype  pe
bits     64
canary   false
retguard false
class    PE32+
cmp.csum 0x004375cd
compiled Tue Jul 27 01:22:41 2021
crypto   false
endian   little
havecode true
hdr.csum 0x00000000
laddr    0x0
lang     c
linenum  false
lsyms    false
machine  AMD 64
nx       true
os       windows
overlay  false
cc       ms
pic      true
relocs   false
signed   false
sanitize false
static   false
stripped false
subsys   Windows GUI
va       true

Running it produces an alert with the following error message, An error occurred. Please close the application and try again.

Running in IDA, trace where it went before the message appeared, goes to 0x7FF7ED092CB0 which has too many variable declarations (because it uses a huge portion of the stack) to view properly as a function so we hide the first node. This function essentially uses CDialog::DoModal to create the modal dialog with the message, then it uses GetProcAddress to get VirtualAllocExNuma, then it starts writing a huge chunk of shellcode at rsp+2F0h which goes all the way until rsp+0x2F01C, then it allocates an area of memory in the heap to write the shellcode to, then it jumps into the shellcode (instruction at 0x7FF7ED20972F)

Shellcode

To make analysis easier, instead of analyzing the extracted shellcode binary, we make the heap segment containing it a loader segment, then reanalyze, then take a memory snapshot.

With the shellcode segment being at address 0x1C616490000, the function quickly jumps into sub_1C616490040. This function loads a PE file embedded in the shellcode.

Used pecheck.py to carve out the PE file from the shellcode.

> ./pecheck.py -l P .\shellcode.bin
1: 0x00000b28 DLL 64-bit 0x0002ed27 aee298f73b1b9ef48e125dfc649fa2ba 0x0002ed2c (EOF) b'' b''
2: 0x00015a18 DLL 64-bit 0x0002d417 60dbeb2094bb0b5a439b9e2f3ecd06b5 0x0002ed2c (EOF) b'' b'ldr.dll'

From pecheck.py,

The first column is the position of the embedded PE file, the fourth column is the end of the embedded PE file without overlay, and the sixth column is the end with overlay.
The fifth column is the hash of the embedded PE file without overlay.

DLL 1

The first extracted DLL has the following properties

arch     x86
baddr    0x180000000
binsz    188927
bintype  pe
bits     64
canary   false
retguard false
class    PE32+
cmp.csum 0x0003e06d
compiled Tue Jul 27 00:48:18 2021
crypto   false
endian   little
havecode true
hdr.csum 0x00000000
laddr    0x0
lang     c
linenum  false
lsyms    false
machine  AMD 64
nx       true
os       windows
overlay  true
cc       ms
pic      true
relocs   false
signed   false
sanitize false
static   false
stripped false
subsys   Windows CUI
va       true 

On brief analysis, looks like this sample manually loads the second embedded DLL then calls it’s export Start, then terminates.

DLL 2

arch     x86
baddr    0x180000000
binsz    96768
bintype  pe
bits     64
canary   false
retguard false
class    PE32+
cmp.csum 0x0001be44
compiled Tue Jul 27 00:39:26 2021
crypto   false
endian   little
havecode true
hdr.csum 0x00000000
laddr    0x0
lang     c
linenum  false
lsyms    false
machine  AMD 64
nx       true
os       windows
overlay  false
cc       ms
pic      true
relocs   false
signed   false
sanitize false
static   false
stripped false
subsys   Windows CUI
va       true

Analysis base address of this DLL is at 0x7FFDFF9E0000.

Start calls sub_7FFDFF9E1990.

Afterwards it calls sub_7FFDFF9E12C0. sub_7FFDFF9E12C0 converts a weird looking number like 0x1A10BD8B into a function like LoadResource. We will call it the hash-function converter, because it finds a function in kernel32 that has a name whose hash matches the number, using this hash algorithm

1
2
3
4
5
def hash_name(function_name):
    hash_value = 0
    for b in function_name:
        hash_value = b ^ rol4(hash_value, 7)
    return hash_value

We hence use the following script which will help us later on to find the function corresponding to a given hash using static analysis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pefile

def rol4(x, n):
  return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF

def hash_name(function_name):
    hash_value = 0
    for b in function_name:
        hash_value = b ^ rol4(hash_value, 7)
    return hash_value

pe = pefile.PE('C:/windows/system32/kernel32.dll')
entry_export = [pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]]
pe.parse_data_directories(directories=entry_export)
exports = [e.name for e in pe.DIRECTORY_ENTRY_EXPORT.symbols]

pe = pefile.PE('C:/windows/system32/ws2_32.dll')
pe.parse_data_directories(directories=entry_export)
exports += [e.name for e in pe.DIRECTORY_ENTRY_EXPORT.symbols]

pe = pefile.PE('C:/windows/system32/bcrypt.dll')
pe.parse_data_directories(directories=entry_export)
exports += [e.name for e in pe.DIRECTORY_ENTRY_EXPORT.symbols]

hash_to_name = {hash_name(name):name for name in exports}

sub_7FFDFF9E1990 used the hash-function converter to get VirtualAlloc, then creates a buffer of size 480.

Afterwards it calls sub_7FFDFF9E1A40. sub_7FFDFF9E1A40 is a function that has 2 parts, depending on the second argument. The first time it is called, the second argument is 1, so it loads the resource named PNG and places a pointer to it in the 480-byte buffer. The offsets and corresponding value meanings are documented below in Object Struct

After that, sub_7FFDFF9E1990 calls sub_7FFDFF9E2E60 which essentially checks if the process name is Spell.EXE.

After that it calls SleepEx then calls sub_7FFDFF9E1A40 this time with the argument as 8 or 2, depending on whether the process is named Spell.EXE (being so gives 2).

The second time it’s run, if the argument is 2, then it calls sub_7FFDFF9E1F80 which is a network function, repeatedly calling sub_7FFDFF9E2070 which contacts inactive.flare-on.com:888. It sends a single byte @, receive response, and does some checks

  • If the response is exe, it goes into sub_7FFDFF9E2410 which sends #, then recv 512 bytes of shellcode and runs it.
  • If it’s run, it would go to sub_7FFDFF9E2590 which sends an &, then again recv 512 bytes of shellcode and run it.
  • If it’s flare-on.com, it returns 1 and set pObj+408 to be the socket.

After calling the network function, sub_7FFDFF9E1A40 calls sub_7FFDFF9E2A20 to write to the registry key HKCU\Software\Microsoft\Spell with the name as a string 1 and value as the string flare-on.com xored with the bytes at 0x7FFDFF9F5170.

After that it calls sub_7FFDFF9E2F70 with uses the BCrypt library to do AES decryption, using the string "d41d8cd98f00b204e9800998ecf8427e" as secret to generate the symmetric key, uses bytes at 0x7FFDFF9F5140 as IV (which are just a bunch of 0x80 bytes), and decrypts 32 bytes of the PNG file starting at offset 95. After decrypting that we get the string l3rlcps_7r_vb33eehskc3. (l3rlcps_7r_vb33eehskc3@flare-on.com doesn’t work but it was worth a try).

Then, it calls sub_7FFDFF9E2730 which uses a strange switch table to xor some reordered version of our l3rlcps_7r_vb33eehskc3 string with bytes at 0x7FFDFF9F5180 and then 0x7FFDFF9F5160. Then it calls sub_7FFDFF9E2A20 again to write to the registry key HKCU\Software\Microsoft\Spell with the name as a string 0 and value as our reordered string.

In essence, HKCU\Software\Microsoft\Spell has two values, 0 which is the first part of the flag (the part before and including the @ sign) xored with the bytes at 0x7FFDFF9F5180 and then 0x7FFDFF9F5160, and 1 which is the second part (the flare-on.com) xored with the bytes at 0x7FFDFF9F5170.

Object struct

The object is 480 bytes

0: (12 bytes) Date string in MM-dd-yyyy format
24: (pointer) PNG resource
32: PNG resource size
36: 2
40: (260 bytes) Module file name
301: (9 bytes) "inactive"
336: (BOOL) isWow64Process - False
340: Number of failed network tries
384: 3
392: (pointer 32 bytes) "flare-on.com"
408: Socket
416: (pointer 33 bytes) "d41d8cd98f00b204e9800998ecf8427e\x00"
424: (24 bytes) "l3rlcps_7r_vb33eehskc3"
448: (32 bytes) recv buf from "inactive.flare-on.com:888"

Solution

At least two methods are possible, the first one was what we did initially, and the second doesn’t involve writing code.

Method 1: Reordering the flag string

We can change the code in the switch statement sub_7FFDFF9E2730 to get our flag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
ans = ''
a1 = 'l3rlcps_7r_vb33eehskc3'

ans += a1[436-424]
ans += a1[437-424]
ans += a1[430-424]
ans += a1[432-424]
ans += a1[431-424]
ans += a1[430-424]
ans += a1[429-424]
ans += a1[425-424]
ans += a1[424-424]
ans += a1[427-424]
ans += a1[428-424]
ans += a1[441-424]
ans += a1[439-424]
ans += a1[444-424]
ans += a1[443-424]
ans += a1[445-424]
ans += a1[426-424]
ans += a1[434-424]
ans += a1[440-424]
ans += a1[435-424]
ans += a1[438-424]
ans += a1[426-424]
ans += '@'

print(ans+'flare-on.com')

Method 2: Running and patching

  • Rename the binary Spell.EXE (case sensitive).
  • Set the hosts to point inactive.flare-on.com to the localhost, and run nc -l 888.
  • Run the binary, breakpoint at kernelbase_SleepEx, run (and close the dialog) until you hit the breakpoint, then change RCX to 0 and step out of the call.
  • Binary search for the bytes C3 C1 A8 06 C2 96 33, then choose the one that doesn’t come from the stack, address doesn’t start with 1800, and ends with 60, then go there and zero out the entire region from 60 to 90.
  • Run it, recv the @ on nc, then send back flare-on.com, then the program will exit.
  • Check HKCU\SOFTWARE\Microsoft\Spell. The two parts of the flag will be there.

Flag

b3s7_sp3llcheck3r_ev3r@flare-on.com
Share on