This commit is contained in:
WitherOrNot 2023-08-18 17:21:35 -04:00
parent b648b8ed59
commit 11476da1f4
3 changed files with 74 additions and 8553 deletions

View File

@ -2,14 +2,15 @@ from qiling import *
from qiling.const import * from qiling.const import *
from capstone import * from capstone import *
from keystone import * from keystone import *
from subprocess import run, PIPE
import pefile import pefile
import json import json
import re import re
import os import os
import sys
import struct import struct
# Set to binary name BIN_NAME = sys.argv[1]
BIN_NAME = "sppsvc.exe"
# These magic regexes are derived from the byte markers in notes.txt # These magic regexes are derived from the byte markers in notes.txt
PUSH_REGEX = rb"(?:\x8dd\$\xfc\x89[\x04\x0c\x14\x1c,4<]\$|[PQRSUVW]){2}\x8d[x\x05\r\x15\x1d-5=].{4}" PUSH_REGEX = rb"(?:\x8dd\$\xfc\x89[\x04\x0c\x14\x1c,4<]\$|[PQRSUVW]){2}\x8d[x\x05\r\x15\x1d-5=].{4}"
@ -28,35 +29,36 @@ REG_NAMES = {
INDIR_REGS = ["ECX", "EDI", "EBX", "EBP", "ESP", "EAX", "ESI", "EDX"] INDIR_REGS = ["ECX", "EDI", "EBX", "EBP", "ESP", "EAX", "ESI", "EDX"]
with open("syms.json", "r") as f:
sym_data = json.loads(f.read())
sym_data = {int(a, 16): b for a, b in sym_data.items()}
sym_data_inv = {b: a for a, b in sym_data.items()}
sym_addrs = sorted(sym_data)
ks = Ks(KS_ARCH_X86, KS_MODE_32) ks = Ks(KS_ARCH_X86, KS_MODE_32)
md = Cs(CS_ARCH_X86, CS_MODE_32) md = Cs(CS_ARCH_X86, CS_MODE_32)
md.detail = True md.detail = True
md.skipdata = True md.skipdata = True
ql = Qiling(["./sppsvc.exe"], ".", verbose=QL_VERBOSE.DISABLED) ql = Qiling([f"./{BIN_NAME}"], ".", verbose=QL_VERBOSE.DISABLED)
image_start = ql.loader.images[0].base image_start = ql.loader.images[0].base
image_end = ql.loader.images[0].end image_end = ql.loader.images[0].end
image_size = image_end - image_start image_size = image_end - image_start
pe = pefile.PE(data=ql.mem.read(image_start, image_size)) pe = pefile.PE(data=ql.mem.read(image_start, image_size))
scratch_base = ql.mem.map_anywhere(0x1000) scratch_base = ql.mem.map_anywhere(0x1000)
def func_boundary(fun_name): def load_syms():
f_start = sym_data_inv[ANLZ_FUNC] text = run(["llvm-pdbutil", "pretty", "-externals", BIN_NAME.replace(".dll", ".pdb").replace(".exe", ".pdb"), f"-load-address={hex(image_start)}"], stdout=PIPE).stdout.decode("utf-8")
ind = sym_addrs.index(f_start) symdata = re.findall(r" public \[(\w+)\] (\S+)", text, re.MULTILINE)
f_end = sym_addrs[ind+1]
return f_start, f_end addrs = []
unique_syms = []
def save_patched_exe(): for addr, sym in symdata:
print("Fixing up sections...") if addr not in addrs:
with open(BIN_NAME.replace(".exe", ".stoned.exe"), "wb") as f: unique_syms.append((int(addr, 16), sym))
f.write(ql.mem.read(exe_start, exe_end - exe_start)) addrs.append(addr)
unique_syms = dict(unique_syms)
return unique_syms
sym_data = load_syms()
sym_data_inv = {b: a for a, b in sym_data.items()}
sym_addrs = sorted(sym_data)
def mem_read_int(addr): def mem_read_int(addr):
return ql.unpack(ql.mem.read(addr, 4)) return ql.unpack(ql.mem.read(addr, 4))
@ -611,12 +613,13 @@ def get_all_stubs():
# "nooo write another function dont just copy paste a loop twice" :nerd: # "nooo write another function dont just copy paste a loop twice" :nerd:
for match in re.finditer(STUB_RET4_REGEX, pe_data): for match in re.finditer(STUB_RET4_REGEX, pe_data):
match_addr = image_start + match.start() match_addr = image_start + match.start()
# print(hex(match_addr)) print(hex(match_addr))
stub_code = ql.mem.read(match_addr - 0x50, 0x50) stub_code = ql.mem.read(match_addr - 0x50, 0x50)
try: try:
stub_start_offset = list(re.finditer(PUSH_REGEX, stub_code))[0].start() stub_start_offset = list(re.finditer(PUSH_REGEX, stub_code))[0].start()
except: except:
# print("A")
continue continue
stub_start_addr = match_addr - 0x50 + stub_start_offset stub_start_addr = match_addr - 0x50 + stub_start_offset
@ -631,6 +634,7 @@ def get_all_stubs():
break break
if ret < 8: if ret < 8:
# print("B")
continue continue
# min 7 backwards -> first push instr, then stop # min 7 backwards -> first push instr, then stop
@ -886,6 +890,7 @@ if __name__ == "__main__":
# os.makedirs("bins", exist_ok=True) # os.makedirs("bins", exist_ok=True)
#"""
print("Finding all obfuscated jump stubs...") print("Finding all obfuscated jump stubs...")
obfu_jmps, bad_stubs = get_all_stubs() obfu_jmps, bad_stubs = get_all_stubs()
@ -905,7 +910,7 @@ if __name__ == "__main__":
print() print()
print("Deobfuscating...") print("Deobfuscating...")
pe = pefile.PE("sppsvc.exe") pe = pefile.PE(BIN_NAME)
add_pe_section(pe, ".pstone") add_pe_section(pe, ".pstone")
deobf_cur = pe.sections[-1].VirtualAddress deobf_cur = pe.sections[-1].VirtualAddress
@ -940,6 +945,8 @@ if __name__ == "__main__":
ecstart_replace_code = assemble(f"jmp {image_start + deobf_offset - arg}") ecstart_replace_code = assemble(f"jmp {image_start + deobf_offset - arg}")
pe.set_bytes_at_rva(arg - image_start, ecstart_replace_code) pe.set_bytes_at_rva(arg - image_start, ecstart_replace_code)
deobf_table[arg] = deobf_cur
deobf_cur += len(df_code) deobf_cur += len(df_code)
stub_replace_code = assemble(f"jmp {image_start + deobf_offset - start}") stub_replace_code = assemble(f"jmp {image_start + deobf_offset - start}")
@ -947,16 +954,59 @@ if __name__ == "__main__":
elif handler == verify_handler: elif handler == verify_handler:
# print("^VERIFY") # print("^VERIFY")
stub_replace_code = assemble(f"jmp {arg + 0x10 - start}") stub_replace_code = assemble(f"jmp {arg + 0x10 - start}")
stub_replace_code += b"\x90" * (end - start - len(stub_replace_code)) stub_replace_code += b"\x90" * (end - start - len(stub_replace_code)) + b"\x90" * 16
else: else:
# print("^NEITHER") # print("^NEITHER")
stub_replace_code = assemble(f"call {handler - start}") stub_replace_code = assemble(f"call {handler - start}")
stub_replace_code = b"\x90" * (end - start - len(stub_replace_code)) + stub_replace_code stub_replace_code += b"\x90" * (end - start - len(stub_replace_code))
pe.set_bytes_at_rva(start - image_start, stub_replace_code) pe.set_bytes_at_rva(start - image_start, stub_replace_code)
#"""
md.detail = False md.detail = False
print("Removing thunk indirection...")
func_addrs = sym_addrs + sorted(map(lambda a: a + image_start, deobf_table.values()))
for i, func in enumerate(func_addrs):
# All MSVC-compiled functions start with mov edi, edi (8B FF)
if pe.get_data(func - image_start, 2) == b"\x8b\xff":
# print(hex(func))
if i < len(func_addrs) - 1:
read_len = func_addrs[i+1] - func
else:
read_len = 0x10000
for instr in md.disasm(pe.get_data(func - image_start, read_len), func):
# print(instr)
if instr.mnemonic == "call":
# print("CALL")
try:
# print("VALID")
jmp_target = int(instr.op_str, 16)
target_instr = next(md.disasm(ql.mem.read(jmp_target, 16), jmp_target))
# print(target_instr)
if target_instr.mnemonic == "jmp":
# print("PATCH")
true_target = int(target_instr.op_str, 16)
replace_call = assemble(f"call {true_target - instr.address}")
pe.set_bytes_at_rva(instr.address - image_start, replace_call)
except:
continue
if BIN_NAME[-4:] == ".dll":
orig_main = sym_data_inv["__DllMainCRTStartup@12"]
else:
orig_main = sym_data_inv["_wmainCRTStartup"]
print("Patching entry point...")
pe.OPTIONAL_HEADER.AddressOfEntryPoint = orig_main - image_start
print("Done! Saving.") print("Done! Saving.")
pe.write(BIN_NAME.replace(".exe", ".stoned.exe").replace(".dll", ".stoned.dll")) pe.write(BIN_NAME.replace(".exe", ".stoned.exe").replace(".dll", ".stoned.dll"))

8508
syms.json

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
import re
import json
# llvm-pdbutil pretty -externals sppsvc.pdb -load-address=0x01000000 > syms.txt
with open("syms.txt", "r") as f:
text = f.read()
symdata = re.findall(r" public \[(\w+)\] (\S+)", text, re.MULTILINE)
addrs = []
unique_syms = []
for addr, sym in symdata:
if addr not in addrs:
unique_syms.append((addr, sym))
addrs.append(addr)
unique_syms = dict(unique_syms)
with open("syms.json", "w") as g:
g.write(json.dumps(unique_syms, indent=4))