mirror of
https://github.com/gmh5225/awesome-game-security
synced 2026-06-21 13:56:22 +00:00
archive: add 5 repo prompt(s) [skip ci]
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,471 @@
|
||||
Project Path: arc_Mrack_DeObfBR_nd5k_wu2
|
||||
|
||||
Source Tree:
|
||||
|
||||
```txt
|
||||
arc_Mrack_DeObfBR_nd5k_wu2
|
||||
├── README.md
|
||||
├── bin
|
||||
│ ├── libtersafe.so
|
||||
│ └── libtprt.so
|
||||
├── debr.py
|
||||
├── img
|
||||
│ ├── 1.png
|
||||
│ ├── 2.png
|
||||
│ ├── 3.png
|
||||
│ ├── 4.png
|
||||
│ ├── 5.png
|
||||
│ ├── 6.png
|
||||
│ ├── 7.png
|
||||
│ └── 8.png
|
||||
└── out
|
||||
├── libtersafe.so
|
||||
└── libtprt.so
|
||||
|
||||
```
|
||||
|
||||
`README.md`:
|
||||
|
||||
```md
|
||||
# DeObfBr
|
||||
|
||||
使用unicorn去除BR混淆
|
||||
|
||||
## 用法
|
||||
```
|
||||
usage: debr.py [-h] -f FILE -s START -e END [-o OUTPUT]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-f FILE, --file FILE The name of the library.
|
||||
-s START, --start START
|
||||
The start address of the function.
|
||||
-e END, --end END The end address of the function.
|
||||
-o OUTPUT, --output OUTPUT
|
||||
The output file.
|
||||
```
|
||||
|
||||
|
||||
## 示例
|
||||
|
||||
|
||||
### libtprt
|
||||
python debr.py -f libtprt.so -s 63884 -e 63C9C
|
||||
|
||||
#### 使用前:
|
||||
|
||||
CFG:
|
||||
|
||||

|
||||
|
||||
F5:
|
||||
|
||||

|
||||
|
||||
|
||||
#### 使用后:
|
||||
|
||||
CFG:
|
||||
|
||||

|
||||
|
||||
F5:
|
||||
|
||||

|
||||
|
||||
|
||||
### libtersafe
|
||||
python debr.py -f libtersafe.so -s 1BDC58 -e 1BDE3C
|
||||
|
||||
#### 使用前:
|
||||
CFG:
|
||||
|
||||

|
||||
|
||||
F5:
|
||||
|
||||

|
||||
|
||||
|
||||
#### 使用后:
|
||||
CFG:
|
||||
|
||||

|
||||
|
||||
F5:
|
||||
|
||||

|
||||
|
||||
|
||||
```
|
||||
|
||||
`debr.py`:
|
||||
|
||||
```py
|
||||
import json
|
||||
import queue
|
||||
import warnings
|
||||
|
||||
from capstone import *
|
||||
from elftools.elf.elffile import *
|
||||
from keystone import *
|
||||
from unicorn import *
|
||||
from unicorn.arm64_const import *
|
||||
|
||||
CS = Cs(CS_ARCH_ARM64, CS_MODE_ARM)
|
||||
CS.detail = True
|
||||
|
||||
KS = keystone.Ks(keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN)
|
||||
|
||||
|
||||
def align_addr(addr):
|
||||
return addr // 1024 * 1024
|
||||
|
||||
|
||||
def align_size(size):
|
||||
return (size + 0x1000) & ~0xfff
|
||||
|
||||
|
||||
def print_regs(uc):
|
||||
for i in range(UC_ARM64_REG_X0, UC_ARM64_REG_X28):
|
||||
print(f"x{i - UC_ARM64_REG_X0}: {hex(uc.reg_read(i))}")
|
||||
|
||||
|
||||
def set_context(uc, regs):
|
||||
if regs is None:
|
||||
return
|
||||
for i in range(29): # x0 ~ x28
|
||||
idx = UC_ARM64_REG_X0 + i
|
||||
uc.reg_write(idx, regs[i])
|
||||
uc.reg_write(UC_ARM64_REG_FP, regs[29]) # fp
|
||||
uc.reg_write(UC_ARM64_REG_LR, regs[30]) # lr
|
||||
uc.reg_write(UC_ARM64_REG_SP, regs[31]) # sp
|
||||
|
||||
|
||||
def get_context(uc):
|
||||
regs = []
|
||||
for i in range(29):
|
||||
idx = UC_ARM64_REG_X0 + i
|
||||
regs.append(uc.reg_read(idx))
|
||||
regs.append(uc.reg_read(UC_ARM64_REG_FP))
|
||||
regs.append(uc.reg_read(UC_ARM64_REG_LR))
|
||||
regs.append(uc.reg_read(UC_ARM64_REG_SP))
|
||||
return regs
|
||||
|
||||
|
||||
class DeBr:
|
||||
|
||||
def __init__(self, name):
|
||||
self.first = None
|
||||
self.emu = None
|
||||
self.temp_emu = None
|
||||
self.file = open(name, "rb")
|
||||
self.buf = self.file.read()
|
||||
self.elf = ELFFile(self.file)
|
||||
self.base = 0x0000000
|
||||
self.pc = 0
|
||||
self.traced = {}
|
||||
self.q = queue.Queue()
|
||||
self.ins_stack = []
|
||||
self.ins_entry = []
|
||||
self.jump_table = {}
|
||||
|
||||
def save(self, name):
|
||||
with open(name, "wb") as f:
|
||||
f.write(self.buf)
|
||||
|
||||
def load_segment(self):
|
||||
start, end = 0xffffffff, 0
|
||||
for i in range(0, self.elf.num_segments()):
|
||||
seg = self.elf.get_segment(i)
|
||||
if seg.header["p_type"] == 'PT_LOAD':
|
||||
v_addr = align_addr(seg.header["p_vaddr"])
|
||||
v_size = align_size(seg.header["p_memsz"])
|
||||
if start > v_addr:
|
||||
start = v_addr
|
||||
if v_addr + v_size > end:
|
||||
end = v_addr + v_size
|
||||
|
||||
self.emu.mem_map(self.base + start, end - start)
|
||||
self.temp_emu.mem_map(self.base + start, end - start)
|
||||
|
||||
for seg in self.elf.iter_segments("PT_LOAD"):
|
||||
f_offset = seg.header["p_offset"]
|
||||
f_size = seg.header["p_filesz"]
|
||||
v_addr = seg.header["p_vaddr"]
|
||||
v_size = seg.header["p_memsz"]
|
||||
|
||||
self.emu.mem_write(self.base + v_addr, self.buf[f_offset:f_offset + f_size])
|
||||
self.temp_emu.mem_write(self.base + v_addr, self.buf[f_offset:f_offset + f_size])
|
||||
|
||||
def virtual_to_fileoffset(self, v_addr):
|
||||
v_addr = v_addr - self.base
|
||||
for seg in self.elf.iter_segments("PT_LOAD"):
|
||||
v_start = seg.header["p_vaddr"]
|
||||
v_end = v_start + seg.header["p_memsz"]
|
||||
f_start = seg.header["p_offset"]
|
||||
if v_start <= v_addr < v_end:
|
||||
return v_addr - v_start + f_start
|
||||
return None
|
||||
|
||||
def patch_bytes(self, old_bytes, new_bytes, addr, length):
|
||||
tmp_bytes = old_bytes[:addr] + bytes(new_bytes) + old_bytes[addr + length:]
|
||||
return tmp_bytes
|
||||
|
||||
def patch_branch(self, uc, addr, branch):
|
||||
if len(branch) == 3:
|
||||
self.jump_table[addr] = [
|
||||
branch[0],
|
||||
branch[1],
|
||||
branch[2].op_str.split(', ')[-1]
|
||||
]
|
||||
elif len(branch) == 1:
|
||||
self.jump_table[addr] = [
|
||||
branch[0]
|
||||
]
|
||||
|
||||
if len(branch) == 1:
|
||||
asm = 'b' + ' ' + hex(branch[0])
|
||||
data1 = KS.asm(asm, addr)[0]
|
||||
self.buf = self.patch_bytes(self.buf, data1, self.virtual_to_fileoffset(addr), 4)
|
||||
else:
|
||||
offset1 = branch[0]
|
||||
offset2 = branch[1]
|
||||
cond = branch[2]
|
||||
# 'x9, x28, x23, lt'
|
||||
condstr = cond.op_str.split(', ')[-1]
|
||||
|
||||
asm = 'b' + condstr + ' ' + hex(offset1)
|
||||
data1 = KS.asm(asm, cond.address)[0]
|
||||
|
||||
asm1 = 'b' + ' ' + hex(offset2)
|
||||
data2 = KS.asm(asm1, addr)[0]
|
||||
|
||||
self.buf = self.patch_bytes(self.buf, data1, self.virtual_to_fileoffset(cond.address), 4)
|
||||
self.buf = self.patch_bytes(self.buf, data2, self.virtual_to_fileoffset(addr), 4)
|
||||
|
||||
def emulate_execution(self, start_addr, end_addr):
|
||||
self.emu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
self.temp_emu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
self.load_segment()
|
||||
stack_address = 0xf0000000
|
||||
stack_size = 0x100000
|
||||
self.emu.mem_map(stack_address, stack_size)
|
||||
self.temp_emu.mem_map(stack_address, stack_size)
|
||||
self.emu.reg_write(UC_ARM64_REG_SP, stack_address + int(stack_size / 2))
|
||||
self.temp_emu.reg_write(UC_ARM64_REG_SP, stack_address + int(stack_size / 2))
|
||||
|
||||
self.first = True
|
||||
|
||||
def hook_code1(uc: Uc, address, size, user_data):
|
||||
data = uc.mem_read(address, size)
|
||||
ins = list(CS.disasm(data, address))[0]
|
||||
|
||||
if 'bl' in ins.mnemonic:
|
||||
uc.reg_write(UC_ARM64_REG_PC, uc.reg_read(UC_ARM64_REG_PC) + 4)
|
||||
# print(f"0x{address:x}: {ins.mnemonic} {ins.op_str}")
|
||||
|
||||
def hook_code(uc: Uc, address, size, user_data):
|
||||
data = uc.mem_read(address, size)
|
||||
ins = list(CS.disasm(data, address))[0]
|
||||
|
||||
if self.first:
|
||||
if 'bl' not in ins.mnemonic and 'b' in ins.mnemonic:
|
||||
self.first = False
|
||||
self.ins_entry = [i for i in self.ins_stack[::-1]]
|
||||
|
||||
self.ins_stack.append((address, get_context(uc), ins))
|
||||
|
||||
if 'ret' in ins.mnemonic:
|
||||
uc.emu_stop()
|
||||
|
||||
if 'b.' in ins.mnemonic:
|
||||
ctx = get_context(uc)
|
||||
self.q.put((ins.address + 4, ctx))
|
||||
self.q.put((ins.operands[0].imm, ctx))
|
||||
uc.emu_stop()
|
||||
|
||||
if 'udf' in ins.mnemonic:
|
||||
self.ins_stack.clear()
|
||||
uc.emu_stop()
|
||||
if 'bl' in ins.mnemonic:
|
||||
self.pc = uc.reg_read(UC_ARM64_REG_PC) + 4
|
||||
uc.reg_write(UC_ARM64_REG_PC, self.pc)
|
||||
|
||||
if 'br' == ins.mnemonic:
|
||||
block_start = self.ins_stack[0][0]
|
||||
block_end = self.ins_stack[-1][0]
|
||||
|
||||
def get_double_branch(uc: Uc, ins_stack):
|
||||
jump_regs = None
|
||||
for tup in ins_stack[::-1]:
|
||||
ins = tup[2]
|
||||
ctx = tup[1]
|
||||
if ins.address == 0x7c730:
|
||||
pass
|
||||
|
||||
if 'br' in ins.mnemonic:
|
||||
jump_regs = ins.operands[0].reg - 218
|
||||
|
||||
if 'cs' in ins.mnemonic:
|
||||
if ins.address == 0xE5430:
|
||||
pass
|
||||
org = get_context(self.temp_emu)
|
||||
if 'csel' in ins.mnemonic:
|
||||
# CSEL X9, X28, X23, LT
|
||||
arr = ins.op_str.split(", ")
|
||||
if arr[0][0] in ['x', 'w']:
|
||||
dest = int(arr[0][1:])
|
||||
|
||||
if 'xzr' in arr[1] or 'wzr' in arr[1]:
|
||||
src1v = 0
|
||||
elif arr[1][0] in ['x', 'w']:
|
||||
src1 = int(arr[1][1:])
|
||||
src1v = ctx[src1]
|
||||
else:
|
||||
print("------------------------")
|
||||
|
||||
if 'xzr' in arr[2] or 'wzr' in arr[2]:
|
||||
src2v = 0
|
||||
elif arr[2][0] in ['x', 'w']:
|
||||
src2 = int(arr[2][1:])
|
||||
src2v = ctx[src2]
|
||||
else:
|
||||
print("------------------------")
|
||||
elif 'cset' in ins.mnemonic:
|
||||
arr = ins.op_str.split(", ")
|
||||
if 'x' in arr[0] or 'w' in arr[0]:
|
||||
dest = int(arr[0][1:])
|
||||
src1v = 0
|
||||
src2v = 1
|
||||
elif 'csinc' in ins.mnemonic:
|
||||
# CSINC X9, X28, X23, LT
|
||||
arr = ins.op_str.split(", ")
|
||||
if arr[0][0] in ['x', 'w']:
|
||||
dest = int(arr[0][1:])
|
||||
|
||||
if 'xzr' in arr[1] or 'wzr' in arr[1]:
|
||||
src1v = 0
|
||||
elif arr[1][0] in ['x', 'w']:
|
||||
src1 = int(arr[1][1:])
|
||||
src1v = ctx[src1]
|
||||
else:
|
||||
print("------------------------")
|
||||
|
||||
if 'xzr' in arr[2] or 'wzr' in arr[2]:
|
||||
src2v = 0 + 1
|
||||
elif arr[2][0] in ['x', 'w']:
|
||||
src2 = int(arr[2][1:])
|
||||
src2v = ctx[src2] + 1
|
||||
else:
|
||||
print("------------------------")
|
||||
|
||||
start_ = tup[0] + 4
|
||||
end = ins_stack[-1][0]
|
||||
|
||||
set_context(self.temp_emu, ctx)
|
||||
self.temp_emu.reg_write(UC_ARM64_REG_X0 + dest, src1v)
|
||||
|
||||
try:
|
||||
self.temp_emu.emu_start(start_, end)
|
||||
except:
|
||||
pass
|
||||
b1 = self.temp_emu.reg_read(UC_ARM64_REG_X0 + jump_regs)
|
||||
|
||||
set_context(self.temp_emu, ctx)
|
||||
self.temp_emu.reg_write(UC_ARM64_REG_X0 + dest, src2v)
|
||||
try:
|
||||
self.temp_emu.emu_start(start_, end)
|
||||
except:
|
||||
pass
|
||||
b2 = self.temp_emu.reg_read(UC_ARM64_REG_X0 + jump_regs)
|
||||
|
||||
set_context(self.temp_emu, org)
|
||||
|
||||
if b1 != b2:
|
||||
return b1, b2, ins
|
||||
|
||||
ret = None
|
||||
try:
|
||||
ret = get_double_branch(uc, self.ins_stack)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
ctx = get_context(uc)
|
||||
if ret is None:
|
||||
print(f"analysis failed: {hex(ins.address)}")
|
||||
else:
|
||||
self.q.put((ret[0], ctx))
|
||||
self.q.put((ret[1], ctx))
|
||||
self.patch_branch(uc, block_end, ret)
|
||||
print(f"{block_start:x} Double Branch: {ret[0]:x}, {ret[1]:x}")
|
||||
self.ins_stack.clear()
|
||||
uc.emu_stop()
|
||||
|
||||
self.pc = self.base + start_addr
|
||||
self.emu.reg_write(UC_ARM64_REG_LR, 0x90000000)
|
||||
self.emu.hook_add(UC_HOOK_CODE, hook_code)
|
||||
self.temp_emu.hook_add(UC_HOOK_CODE, hook_code1)
|
||||
self.q.put((start_addr, None))
|
||||
while not self.q.empty():
|
||||
addr, context = self.q.get()
|
||||
if addr in self.traced:
|
||||
continue
|
||||
|
||||
self.traced[addr] = 1
|
||||
set_context(self.emu, context)
|
||||
self.pc = addr
|
||||
while True:
|
||||
try:
|
||||
self.emu.emu_start(self.pc, 0x90000000)
|
||||
break
|
||||
except Exception as e:
|
||||
if not start_addr <= self.pc <= end_addr:
|
||||
warnings.warn(f"pc out of range: {hex(self.pc)}")
|
||||
print_regs(self.emu)
|
||||
break
|
||||
self.pc = self.emu.reg_read(UC_ARM64_REG_PC) + 4
|
||||
|
||||
with open("jump_table.json", 'w+') as f:
|
||||
f.write(json.dumps(self.jump_table))
|
||||
|
||||
def get_csel_cset(self, start, end):
|
||||
res = []
|
||||
code = self.buf[start:end]
|
||||
codes = [ins for ins in CS.disasm(code, start)]
|
||||
for ins in codes:
|
||||
if ins.mnemonic == 'csel' or ins.mnemonic == 'cset':
|
||||
print(ins)
|
||||
|
||||
def get_first_block(self, start, end):
|
||||
res = []
|
||||
code = self.buf[start:end]
|
||||
for ins in CS.disasm(code, start):
|
||||
if 'bl' not in ins.mnemonic and 'b' in ins.mnemonic:
|
||||
break
|
||||
else:
|
||||
res.append(ins)
|
||||
|
||||
return res, self.buf[start:len(res) * 4]
|
||||
|
||||
|
||||
import argparse
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-f", "--file", help="The name of the library.", type=str, required=True)
|
||||
parser.add_argument('-s', "--start", help="The start address of the function.", required=True)
|
||||
parser.add_argument('-e', "--end", help="The end address of the function.", required=True)
|
||||
parser.add_argument('-o', "--output", help="The output file.")
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.output is None:
|
||||
args.output = args.file
|
||||
|
||||
start = int(args.start, 16)
|
||||
end = int(args.end, 16)
|
||||
obf = DeBr(args.file)
|
||||
obf.emulate_execution(start, end)
|
||||
obf.save(args.output)
|
||||
print("Done")
|
||||
|
||||
```
|
||||
+103937
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,870 @@
|
||||
Project Path: arc_stuxnet147_Themida-Research_447_v4n4
|
||||
|
||||
Source Tree:
|
||||
|
||||
```txt
|
||||
arc_stuxnet147_Themida-Research_447_v4n4
|
||||
├── README-KR.md
|
||||
└── README.md
|
||||
|
||||
```
|
||||
|
||||
`README-KR.md`:
|
||||
|
||||
```md
|
||||
# 더미다(Themida) VM 분석
|
||||
|
||||
## 목차
|
||||
|
||||
1. [개요](#개요)
|
||||
2. [Themida VM 컨텍스트 구조 (`VM_CONTEXT`)](#themida-vm-컨텍스트-구조-vm_context)
|
||||
3. [핸들러 동작 원리 및 특징](#핸들러-동작-원리-및-특징)
|
||||
4. [핸들러 코드 분석 예시](#핸들러-코드-분석-예시)
|
||||
|
||||
---
|
||||
|
||||
## 개요
|
||||
|
||||
Themida(또는 WinLicense 계열) 난독화 엔진은 **가상 머신(VM)**을 통해 바이너리 코드를 난독화 한다. 특히 x64 코드를 커스텀 바이트코드 형태로 바꿔서, 다양한 **가상 레지스터**, **가상 스택** 연산, **안티 디버깅** 등을 섞어 높은 난이도를 제공한다.
|
||||
|
||||
역분석 과정의 핵심은 다음과 같다.
|
||||
|
||||
1. **VM CONTEXT** 파악
|
||||
- Themida VM 실행 시점에 실제 CPU 레지스터 / 플래그를 백업하고, 내부적으로 VM 전용 레지스터와 스택을 운영한다.
|
||||
2. **핸들러(Handler) 분석**
|
||||
- 각 **가상화된 명령**(ADD, SUB, SHIFT, ROTATE, PUSH, POP, CALL, RET, …)을 처리하는 핸들러가 난독화되어 있다.
|
||||
3. **바이트코드 디스패치**
|
||||
- VM 바이트코드(또는 워드 단위 opcode) + 핸들러 테이블로, 다음 핸들러 주소를 동적으로 계산한다.
|
||||
4. **리프팅(Devirtualization) 구현** 아이디어
|
||||
- Python + Triton 등으로 **“바이트코드 → IR/원본 x86 코드”**를 반자동/자동으로 복원.
|
||||
|
||||
---
|
||||
|
||||
## Themida VM 컨텍스트 구조 (`VM_CONTEXT`)
|
||||
|
||||
Themida VM은 **0x200 바이트 내외**의 VM CONTEXT를 사용한다. VM CONTEXT는 더미다 난독화 적용 과정에서 랜덤하게 오프셋이 정해진다.
|
||||
|
||||
```c
|
||||
// 실제와 다름.
|
||||
struct VM_CONTEXT
|
||||
{
|
||||
char VM_CONTEXT_START;
|
||||
char field_1;
|
||||
char field_2;
|
||||
char field_3;
|
||||
char field_4;
|
||||
char field_5;
|
||||
char field_6;
|
||||
char field_7;
|
||||
char field_8;
|
||||
char field_9;
|
||||
char field_A;
|
||||
char field_B;
|
||||
char field_C;
|
||||
char field_D;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_1;
|
||||
__unaligned __declspec(align(1)) __int64 field_16;
|
||||
__unaligned __declspec(align(1)) int vm_some_key2;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opaque_022;
|
||||
__unaligned __declspec(align(1)) int field_2A;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R11;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opaque_036;
|
||||
__unaligned __declspec(align(1)) int vm_some_key1;
|
||||
char vm_unknown_flag;
|
||||
char field_43[12];
|
||||
char vm_branch_flag_maybe;
|
||||
__int64 vm_scratch_qword;
|
||||
int vm_some_key3;
|
||||
__unaligned __declspec(align(1)) __int64 field_5C;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_2;
|
||||
char vm_instruction_opcode5;
|
||||
__unaligned __declspec(align(1)) __int16 vm_opaque_06D;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R15;
|
||||
char vm_opaque_077[12];
|
||||
__unaligned __declspec(align(1)) __int64 vm_handlerScratch;
|
||||
__unaligned __declspec(align(1)) __int16 vm_opaque_08B;
|
||||
__unaligned __declspec(align(1)) __int64 vm_instruction_pointer;
|
||||
__unaligned __declspec(align(1)) int vm_some_key5;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R13;
|
||||
char vm_instruction_opcode2;
|
||||
__unaligned __declspec(align(1)) __int64 vm_some_key4;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RDI;
|
||||
__int16 field_B2;
|
||||
int vm_some_key7;
|
||||
char vm_instruction_opcode1;
|
||||
__unaligned __declspec(align(1)) __int16 vm_some_key6;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RBP;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RBX;
|
||||
__unaligned __declspec(align(1)) int pad;
|
||||
__unaligned __declspec(align(1)) __int64 vm_left_value_1;
|
||||
char vm_instruction_opcode4;
|
||||
int vm_opaque_0D8;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R9;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R10;
|
||||
__unaligned __declspec(align(1)) __int64 *vm_handlerTable;
|
||||
int vm_opaque_0F4;
|
||||
__int64 vReg_RCX;
|
||||
__int64 vm_opaque_100;
|
||||
int vm_handlerKey;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R8;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R14;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_3;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RAX;
|
||||
char vm_instruction_opcode3;
|
||||
char vm_opaque_12D[12];
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R12;
|
||||
char vm_opaque_141[12];
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RSI;
|
||||
char vm_operation_result_selector_maybe;
|
||||
__int16 vm_opaque_156;
|
||||
int vm_spinlock;
|
||||
__unaligned __declspec(align(1)) __int64 vm_stack_pointer;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RDX;
|
||||
__unaligned __declspec(align(1)) __int64 vm_left_value_2;
|
||||
char vm_opaque_174[140];
|
||||
};
|
||||
```
|
||||
|
||||
- **`vm_handlerTable`**: 바이트코드 인덱스(또는 opcode)로부터 “핸들러” 주소를 가져올 때 사용.
|
||||
- **`vm_instruction_pointer`**: 현재 해석 중인 바이트코드 포인터?
|
||||
- **``vm_stack_pointer``**: vsp
|
||||
|
||||
---
|
||||
|
||||
## 더미다 동작 흐름 개요
|
||||
|
||||
### VM ENTER:
|
||||
|
||||
원본 컨텍스트를 스택에 푸시한다
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
push rdi
|
||||
push rsi
|
||||
push rbp
|
||||
push rbx
|
||||
push rdx
|
||||
push rcx
|
||||
push rax
|
||||
push eflags
|
||||
push some_key
|
||||
push first_handler_offset
|
||||
push retaddr
|
||||
...
|
||||
```
|
||||
|
||||
더미다는 단 한개의 VM CONTEXT 전역 변수를 모든 가상화 코드에서 공유하므로 진입 전에 스핀락을 설정한다.
|
||||
|
||||
```
|
||||
.themida:1400931D0 loc_1400931D0:
|
||||
.themida:1400931D0 xor eax, eax
|
||||
.themida:1400931D2 lock cmpxchg [rbx+rbp], ecx ;rbx는 스핀락 오프셋이고 rbp는 VM_CONTEXT의 시작주소
|
||||
.themida:1400931D7 jz loc_1400931E4
|
||||
.themida:1400931DD pause
|
||||
.themida:1400931DF jmp loc_1400931D0
|
||||
```
|
||||
|
||||
스택에 푸시된 컨텍스트 정보를 VM CONTEXT로 옮긴다..
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
pop qword ptr [r9] ;eflags to VM_CONTEXT
|
||||
pop qword ptr [r14] ;rax
|
||||
pop qword ptr [r14] ;rcx
|
||||
pop qword ptr [r15] ;rdx
|
||||
pop qword ptr [r15] ;rbx
|
||||
pop qword ptr [r15] ;rbp
|
||||
pop qword ptr [rsi] ;rsi
|
||||
pop qword ptr [rsi] ;rdi
|
||||
pop qword ptr [r12] ;r15
|
||||
pop qword ptr [r14] ;r14
|
||||
pop qword ptr [r14] ;r13
|
||||
pop qword ptr [r15] ;r12
|
||||
pop qword ptr [r15] ;r11
|
||||
pop qword ptr [r15] ;r10
|
||||
pop qword ptr [r15] ;r9
|
||||
pop qword ptr [r14] ;r8
|
||||
...
|
||||
```
|
||||
|
||||
### VM HANDLER:
|
||||
|
||||
간단히 말해서 이 과정에서는 바이트 코드를 해석하고 실행한다. 참고로 더미다의 바이트 코드는 바이트 배열이 아니다.
|
||||
|
||||
예시 핸들러:
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
// mov operation
|
||||
if ( v0->vm_instruction_opcode1 == (char)0xEA )
|
||||
{
|
||||
v84 = v0->vm_opnd_maybe_1 + (v0->vm_some_key4 ^ v0->vm_opnd_maybe_2) - 0x6C1BFD75;
|
||||
v85 = v0->vm_left_value_1 + v0->vm_opnd_maybe_3 + 0x2787FB21 - v0->vm_some_key4;
|
||||
if ( v0->vm_instruction_opcode3 == 0x75 )
|
||||
v84 = (char)v85;
|
||||
if ( v0->vm_instruction_opcode3 == 0x76 )
|
||||
v84 = (__int16)(LOWORD(v0->vm_left_value_1) + LOWORD(v0->vm_opnd_maybe_3) - 0x4DF - LOWORD(v0->vm_some_key4));
|
||||
if ( v0->vm_instruction_opcode3 == 0x77 )
|
||||
v84 = (unsigned int)v85;
|
||||
v50 = v84 + v85;
|
||||
__readeflags();
|
||||
if ( v0->vm_operation_result_selector_maybe <= 0x8Eu )
|
||||
v0->vm_left_value_1 = v84 ^ 0x2787FB21;
|
||||
else
|
||||
v0->vm_left_value_2 = v84 + 0x116ABA2E;
|
||||
}
|
||||
|
||||
// shr operation
|
||||
if ( v0->vm_instruction_opcode1 == 0x6D )
|
||||
{
|
||||
v214 = v0->vm_opnd_maybe_1 + (v0->vm_some_key4 ^ v0->vm_opnd_maybe_2) - 0x6C1BFD75;
|
||||
v215 = v0->vm_left_value_1 + v0->vm_opnd_maybe_3 + 0x2787FB21 - v0->vm_some_key4;
|
||||
v237 = *(_QWORD *)(&v0->VM_CONTEXT_START + *(unsigned __int16 *)(v0->vm_instruction_pointer + 0xB));
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA2 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
LOBYTE(v214) = (unsigned __int8)v214 >> v215;
|
||||
v216 = __readeflags();
|
||||
v237 = v216;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA3 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
LOWORD(v214) = (unsigned __int16)v214 >> v215;
|
||||
v217 = __readeflags();
|
||||
v237 = v217;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA4 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
v214 = (unsigned int)v214 >> v215;
|
||||
v218 = __readeflags();
|
||||
v237 = v218;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA5 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
v214 >>= v215;
|
||||
__readeflags();
|
||||
}
|
||||
vm_operation_result_selector_maybe = v0->vm_operation_result_selector_maybe;
|
||||
if ( vm_operation_result_selector_maybe <= 0x8Eu )
|
||||
v0->vm_left_value_1 = v214 ^ 0x2787FB21;
|
||||
else
|
||||
v0->vm_left_value_2 = v214 + 0x116ABA2E;
|
||||
}
|
||||
```
|
||||
|
||||
참고로 산술 연산이라면 오퍼레이션 직후 pushfq (__readeflags) 가 존재 할 가능성이 높다.
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
// push operation
|
||||
if ( vm_instruction_opcode1 == 0x6A )
|
||||
{
|
||||
if ( (_BYTE)p_vm_instruction_opcode5 == 2 )
|
||||
{
|
||||
LOWORD(v34) = v18;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOWORD(v34) = HIWORD(v18);
|
||||
v20 = ((_BYTE)v0 - 0x48) & (v18 + v20) ^ 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
// stack pointer adjust
|
||||
v26 = (p_vm_instruction_opcode5 | 0x47) - 4;
|
||||
v27 = v0->vm_instruction_opcode1 - 0x6A;
|
||||
if ( v0->vm_instruction_opcode1 == 0x6A )
|
||||
{
|
||||
if ( v24 == 2 )
|
||||
{
|
||||
*(_QWORD *)vm_stack_pointer -= 2LL;
|
||||
}
|
||||
else
|
||||
{
|
||||
*(_QWORD *)vm_stack_pointer -= 8LL;
|
||||
LOBYTE(v26) = v27 ^ (((v27 ^ v26) - 0xF) | 0x28);
|
||||
}
|
||||
v26 = (unsigned __int8)v26 & 0xA8;
|
||||
}
|
||||
LOBYTE(p_vm_instruction_opcode5) = v0->vm_instruction_opcode1;
|
||||
v28 = p_vm_instruction_opcode5 | (v26 + 0x8000FBFFLL);
|
||||
if ( (_BYTE)p_vm_instruction_opcode5 == 0x1C )
|
||||
{
|
||||
vm_opnd_maybe_1 = v0->vm_opnd_maybe_1;
|
||||
v28 = 0x400 - vm_opnd_maybe_1;
|
||||
if ( (char *)(vm_opnd_maybe_1 - 0x31AC9D7C) != vm_stack_pointer )
|
||||
{
|
||||
if ( v24 == 2 )
|
||||
*(_QWORD *)vm_stack_pointer += 2LL;
|
||||
else
|
||||
*(_QWORD *)vm_stack_pointer += 8LL;
|
||||
v28 = 0x3E4LL;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
더미다는 핸들러에서 디스패쳐를 따로 거치치 않는다. 핸들러에서 바로 다음 핸들러로 이동한다.
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
vmctx->vm_handlerKey -= 0x5AC92481;
|
||||
vmctx->vm_handlerKey ^= 0x3F8BFC4F;
|
||||
vmctx->vm_handlerKey ^= 0x2DDDE2;
|
||||
|
||||
v1 = *(unsigned __int16 *)(vmctx->vm_instruction_pointer + 4);
|
||||
index = v1 - vmctx->vm_handlerKey;
|
||||
vmctx->vm_handlerKey &= index;
|
||||
|
||||
next_handler = vmctx->vm_handlerTable[(unsigned __int16)index];
|
||||
|
||||
// VIP 업데이트
|
||||
vmctx->vm_instruction_pointer += *(int *)vmctx->vm_instruction_pointer;
|
||||
|
||||
jump next_handler;
|
||||
```
|
||||
|
||||
### VM EXIT:
|
||||
|
||||
VM CONTEXT에 들어있던 레지스터들을 실제 레지스터로 전환한다. 그리고 스핀락을 해제한다.
|
||||
|
||||
따라서 VM CONTEXT -> STACK -> REAL CONTEXT 전환 과정이 들어있다.
|
||||
|
||||
```
|
||||
// 간소화된 예시임. 실제와 다름.
|
||||
.themida:14000AEA2 pop r8
|
||||
.themida:14000AEA4 pop r9
|
||||
.themida:14000AEA6 pop r10
|
||||
.themida:14000AEA8 pop r11
|
||||
.themida:14000AEAA pop r12
|
||||
.themida:14000AEAC pop r13
|
||||
.themida:14000AEAE pop r14
|
||||
.themida:14000AEB0 pop r15
|
||||
.themida:14000AEB2 pop rdi
|
||||
.themida:14000AEB3 pop rsi
|
||||
.themida:14000AEB4 pop rbp
|
||||
.themida:14000AEB5 pop rbx
|
||||
.themida:14000AEB6 pop rdx
|
||||
.themida:14000AEB7 pop rcx
|
||||
.themida:14000AEB8 pop rax
|
||||
.themida:14000AEB9 popfq
|
||||
.themida:14000AEBA popfq
|
||||
.themida:14000AEBB retn 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 핸들러 동작 원리 및 특징
|
||||
|
||||
1. **바이트코드에서 opcode fetch**
|
||||
- `*(unsigned __int16*)(vm_bytecodePtr + offset)` 등으로 2바이트를 읽고, `vm_handlerKey`와 XOR/ADD 등 수행.
|
||||
- 결과를 `& 0xffff` 후 × 8 → **handler table**에서 “다음 핸들러” 주소를 구한다.
|
||||
2. **VM CONTEXT 조작**
|
||||
- 산술/논리 연산을 `vReg_RAX`, `vReg_RBX`, `vm_flagsA/B`, `field_0[...](스택)` 등에 적용.
|
||||
- EFLAGS 대신 `vm_flagsA` 등으로 시뮬레이션하거나, 부분적으로 pushfq/popfq, __readeflags()/__writeeflags() 등을 써서 난독화.
|
||||
3. **옵코드**
|
||||
- “if (vm_instruction_opcodeN == 0x16) then pop 2 bytes from field_0[...]” 식 로직이 흔함.
|
||||
- opcode 상위/하위 4비트(`(value & 0xF0)>>4`, `(value & 0xF)`) 같은게 보였음. opcodeMain, subOpcode 같은 개념이 존재 할수도 있음
|
||||
4. **다음 바이트코드로 이동**
|
||||
- `vm_bytecodePtr += *(int*)(vm_bytecodePtr + someOffset)`
|
||||
- 핸들러마다 다른 offset이 있다. ex) +6, +3, +4, etc.
|
||||
|
||||
------
|
||||
|
||||
## 핸들러 코드 분석 예시
|
||||
|
||||
아래는 일부 실제 핸들러(난독화된) 형태를 단순화한 예시:
|
||||
|
||||
```c
|
||||
__int64 __fastcall sub_14003411C(VM_CONTEXT *v0) {
|
||||
if ((v0->vm_handlerKey & 2) != 0)
|
||||
v0->vm_flagsA += 0x7660110;
|
||||
// swap(vm_handlerKey, vm_flagsA)
|
||||
int tmp = v0->vm_handlerKey;
|
||||
v0->vm_handlerKey = v0->vm_flagsA;
|
||||
v0->vm_flagsA = tmp;
|
||||
|
||||
// ... 각종 VM 명령어 실행 (오퍼랜드 fetch, 실행 등)
|
||||
|
||||
// bytecode fetch?
|
||||
unsigned int opcode = *(unsigned __int16*)v0->vm_bytecodePtr;
|
||||
// next handler = handlerTable[opcode * 8]
|
||||
__int64 nextHandler = *( (__int64*)(v0->vm_handlerTable + (opcode & 0xFFFF) * 8) );
|
||||
|
||||
// vm_bytecodePtr += *(int*)(v0->vm_bytecodePtr + 3)
|
||||
v0->vm_bytecodePtr += *(int*)(v0->vm_bytecodePtr + 3);
|
||||
|
||||
// call/jmp nextHandler
|
||||
return ((handlerFuncType)nextHandler)(...);
|
||||
}
|
||||
```
|
||||
|
||||
이처럼 **핸들러**는 “(1) VM CONTEXT 업데이트 → (2) 바이트코드에서 opcode 추출 → (3) 다음 핸들러 주소 계산 → (4) 바이트코드 포인터 이동” 순으로 동작한다.
|
||||
|
||||
---
|
||||
|
||||
## 더미다 분석기 제작 아이디어
|
||||
|
||||
1. 초기 분석 단계
|
||||
- 바이너리를 로드하고 지정된 주소에서 가상화된 코드 실행 시작
|
||||
- 모든 레지스터들을 심볼릭하게 설정하고 Triton 엔진으로 명령어 에뮬레이션
|
||||
- vm context의 주요 오프셋 식별 (vip/vsp 레지스터 등)
|
||||
- vip/vsp 레지스터를 심볼릭화하여 이후 메모리 접근 추적
|
||||
2. 핸들러 패턴 매칭
|
||||
- 각 핸들러의 마지막 store 명령에서 저장되는 값의 AST 분석
|
||||
- 패턴과 매칭하여 해당 핸들러의 연산 의미 파악
|
||||
- 패턴 예시:
|
||||
- "[vsp] + [vsp]" → ADD
|
||||
- "~[vsp] | ~[vsp]" → NAND
|
||||
- "[vsp] >> ([vsp] & 0x3f)" → SHR
|
||||
3. 제어 흐름 분석과 변환
|
||||
- 식별된 핸들러를 basic block으로 변환
|
||||
- RIP 레지스터를 슬라이싱하여 다음 basic block 주소 탐색
|
||||
- Basic block들을 연결하여 제어 흐름 그래프 구성
|
||||
- 모든 basic block을 LLVM IR로 변환하여 실행 가능한 코드로 복원
|
||||
4. 최적화 단계 (옵션)
|
||||
- LLVM 최적화 패스 적용
|
||||
|
||||
```
|
||||
|
||||
`README.md`:
|
||||
|
||||
```md
|
||||
# Themida VM Analysis
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Themida VM Context Structure (`VM_CONTEXT`)](#themida-vm-context-structure-vm_context)
|
||||
3. [Handler Operation Principles and Characteristics](#handler-operation-principles-and-characteristics)
|
||||
4. [Handler Code Analysis Example](#handler-code-analysis-example)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Themida (or WinLicense family) obfuscation engine obfuscates binary code through a **virtual machine (VM)**. It particularly transforms x64 code into custom bytecode format, mixing various **virtual registers**, **virtual stack** operations, and **anti-debugging** techniques to provide a high level of complexity.
|
||||
|
||||
The key steps in reverse engineering are as follows:
|
||||
|
||||
1. **Understanding VM CONTEXT**
|
||||
- When executing the Themida VM, it backs up actual CPU registers/flags and internally operates VM-specific registers and stack.
|
||||
2. **Handler Analysis**
|
||||
- Analysis of handlers that process each **virtualized instruction** (ADD, SUB, SHIFT, ROTATE, PUSH, POP, CALL, RET, ...).
|
||||
3. **Bytecode Dispatch**
|
||||
- Using VM bytecode (or word-sized opcode) + handler table to dynamically calculate the next handler address.
|
||||
4. **Implementing Lifting (Devirtualization)**
|
||||
- Semi-automatic/automatic restoration of **"bytecode → IR/original x86 code"** using Python + Triton, etc.
|
||||
|
||||
---
|
||||
|
||||
## Themida VM Context Structure (`VM_CONTEXT`)
|
||||
|
||||
Themida VM uses a VM CONTEXT of about **0x200 bytes**. The offsets of VM CONTEXT are randomly determined during the Themida obfuscation process.
|
||||
|
||||
```c
|
||||
// This is a simplified example. Differs from actual implementation.
|
||||
struct VM_CONTEXT
|
||||
{
|
||||
char VM_CONTEXT_START;
|
||||
char field_1;
|
||||
char field_2;
|
||||
char field_3;
|
||||
char field_4;
|
||||
char field_5;
|
||||
char field_6;
|
||||
char field_7;
|
||||
char field_8;
|
||||
char field_9;
|
||||
char field_A;
|
||||
char field_B;
|
||||
char field_C;
|
||||
char field_D;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_1;
|
||||
__unaligned __declspec(align(1)) __int64 field_16;
|
||||
__unaligned __declspec(align(1)) int vm_some_key2;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opaque_022;
|
||||
__unaligned __declspec(align(1)) int field_2A;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R11;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opaque_036;
|
||||
__unaligned __declspec(align(1)) int vm_some_key1;
|
||||
char vm_unknown_flag;
|
||||
char field_43[12];
|
||||
char vm_branch_flag_maybe;
|
||||
__int64 vm_scratch_qword;
|
||||
int vm_some_key3;
|
||||
__unaligned __declspec(align(1)) __int64 field_5C;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_2;
|
||||
char vm_instruction_opcode5;
|
||||
__unaligned __declspec(align(1)) __int16 vm_opaque_06D;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R15;
|
||||
char vm_opaque_077[12];
|
||||
__unaligned __declspec(align(1)) __int64 vm_handlerScratch;
|
||||
__unaligned __declspec(align(1)) __int16 vm_opaque_08B;
|
||||
__unaligned __declspec(align(1)) __int64 vm_instruction_pointer;
|
||||
__unaligned __declspec(align(1)) int vm_some_key5;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R13;
|
||||
char vm_instruction_opcode2;
|
||||
__unaligned __declspec(align(1)) __int64 vm_some_key4;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RDI;
|
||||
__int16 field_B2;
|
||||
int vm_some_key7;
|
||||
char vm_instruction_opcode1;
|
||||
__unaligned __declspec(align(1)) __int16 vm_some_key6;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RBP;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RBX;
|
||||
__unaligned __declspec(align(1)) int pad;
|
||||
__unaligned __declspec(align(1)) __int64 vm_left_value_1;
|
||||
char vm_instruction_opcode4;
|
||||
int vm_opaque_0D8;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R9;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R10;
|
||||
__unaligned __declspec(align(1)) __int64 *vm_handlerTable;
|
||||
int vm_opaque_0F4;
|
||||
__int64 vReg_RCX;
|
||||
__int64 vm_opaque_100;
|
||||
int vm_handlerKey;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R8;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R14;
|
||||
__unaligned __declspec(align(1)) __int64 vm_opnd_maybe_3;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RAX;
|
||||
char vm_instruction_opcode3;
|
||||
char vm_opaque_12D[12];
|
||||
__unaligned __declspec(align(1)) __int64 vReg_R12;
|
||||
char vm_opaque_141[12];
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RSI;
|
||||
char vm_operation_result_selector_maybe;
|
||||
__int16 vm_opaque_156;
|
||||
int vm_spinlock;
|
||||
__unaligned __declspec(align(1)) __int64 vm_stack_pointer;
|
||||
__unaligned __declspec(align(1)) __int64 vReg_RDX;
|
||||
__unaligned __declspec(align(1)) __int64 vm_left_value_2;
|
||||
char vm_opaque_174[140];
|
||||
};
|
||||
```
|
||||
|
||||
- **`vm_handlerTable`**: Used to fetch "handler" addresses from bytecode index (or opcode).
|
||||
- **`vm_instruction_pointer`**: Pointer to the bytecode currently being interpreted?
|
||||
- **`vm_stack_pointer`**: vsp
|
||||
|
||||
---
|
||||
|
||||
## Themida Operation Flow Overview
|
||||
|
||||
### VM ENTER:
|
||||
|
||||
Pushes the original context onto the stack
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
push rdi
|
||||
push rsi
|
||||
push rbp
|
||||
push rbx
|
||||
push rdx
|
||||
push rcx
|
||||
push rax
|
||||
push eflags
|
||||
push some_key
|
||||
push first_handler_offset
|
||||
push retaddr
|
||||
...
|
||||
```
|
||||
|
||||
Themida sets a spinlock before entry because all virtualized code shares a single global VM CONTEXT variable.
|
||||
|
||||
```
|
||||
.themida:1400931D0 loc_1400931D0:
|
||||
.themida:1400931D0 xor eax, eax
|
||||
.themida:1400931D2 lock cmpxchg [rbx+rbp], ecx ;rbx is spinlock offset and rbp is the start address of VM_CONTEXT
|
||||
.themida:1400931D7 jz loc_1400931E4
|
||||
.themida:1400931DD pause
|
||||
.themida:1400931DF jmp loc_1400931D0
|
||||
```
|
||||
|
||||
Moves context information pushed onto the stack to VM CONTEXT.
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
pop qword ptr [r9] ;eflags to VM_CONTEXT
|
||||
pop qword ptr [r14] ;rax
|
||||
pop qword ptr [r14] ;rcx
|
||||
pop qword ptr [r15] ;rdx
|
||||
pop qword ptr [r15] ;rbx
|
||||
pop qword ptr [r15] ;rbp
|
||||
pop qword ptr [rsi] ;rsi
|
||||
pop qword ptr [rsi] ;rdi
|
||||
pop qword ptr [r12] ;r15
|
||||
pop qword ptr [r14] ;r14
|
||||
pop qword ptr [r14] ;r13
|
||||
pop qword ptr [r15] ;r12
|
||||
pop qword ptr [r15] ;r11
|
||||
pop qword ptr [r15] ;r10
|
||||
pop qword ptr [r15] ;r9
|
||||
pop qword ptr [r14] ;r8
|
||||
...
|
||||
```
|
||||
|
||||
### VM HANDLER:
|
||||
|
||||
In simple terms, this process interprets and executes bytecode. Note that Themida's bytecode is not a byte array.
|
||||
|
||||
Example handlers:
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
// mov operation
|
||||
if ( v0->vm_instruction_opcode1 == (char)0xEA )
|
||||
{
|
||||
v84 = v0->vm_opnd_maybe_1 + (v0->vm_some_key4 ^ v0->vm_opnd_maybe_2) - 0x6C1BFD75;
|
||||
v85 = v0->vm_left_value_1 + v0->vm_opnd_maybe_3 + 0x2787FB21 - v0->vm_some_key4;
|
||||
if ( v0->vm_instruction_opcode3 == 0x75 )
|
||||
v84 = (char)v85;
|
||||
if ( v0->vm_instruction_opcode3 == 0x76 )
|
||||
v84 = (__int16)(LOWORD(v0->vm_left_value_1) + LOWORD(v0->vm_opnd_maybe_3) - 0x4DF - LOWORD(v0->vm_some_key4));
|
||||
if ( v0->vm_instruction_opcode3 == 0x77 )
|
||||
v84 = (unsigned int)v85;
|
||||
v50 = v84 + v85;
|
||||
__readeflags();
|
||||
if ( v0->vm_operation_result_selector_maybe <= 0x8Eu )
|
||||
v0->vm_left_value_1 = v84 ^ 0x2787FB21;
|
||||
else
|
||||
v0->vm_left_value_2 = v84 + 0x116ABA2E;
|
||||
}
|
||||
|
||||
// shr operation
|
||||
if ( v0->vm_instruction_opcode1 == 0x6D )
|
||||
{
|
||||
v214 = v0->vm_opnd_maybe_1 + (v0->vm_some_key4 ^ v0->vm_opnd_maybe_2) - 0x6C1BFD75;
|
||||
v215 = v0->vm_left_value_1 + v0->vm_opnd_maybe_3 + 0x2787FB21 - v0->vm_some_key4;
|
||||
v237 = *(_QWORD *)(&v0->VM_CONTEXT_START + *(unsigned __int16 *)(v0->vm_instruction_pointer + 0xB));
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA2 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
LOBYTE(v214) = (unsigned __int8)v214 >> v215;
|
||||
v216 = __readeflags();
|
||||
v237 = v216;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA3 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
LOWORD(v214) = (unsigned __int16)v214 >> v215;
|
||||
v217 = __readeflags();
|
||||
v237 = v217;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA4 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
v214 = (unsigned int)v214 >> v215;
|
||||
v218 = __readeflags();
|
||||
v237 = v218;
|
||||
}
|
||||
if ( v0->vm_instruction_opcode2 == (char)0xA5 )
|
||||
{
|
||||
__writeeflags(v237);
|
||||
v214 >>= v215;
|
||||
__readeflags();
|
||||
}
|
||||
vm_operation_result_selector_maybe = v0->vm_operation_result_selector_maybe;
|
||||
if ( vm_operation_result_selector_maybe <= 0x8Eu )
|
||||
v0->vm_left_value_1 = v214 ^ 0x2787FB21;
|
||||
else
|
||||
v0->vm_left_value_2 = v214 + 0x116ABA2E;
|
||||
}
|
||||
```
|
||||
|
||||
Note that if it's an arithmetic operation, there is likely to be a pushfq (__readeflags) right after the operation.
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
// push operation
|
||||
if ( vm_instruction_opcode1 == 0x6A )
|
||||
{
|
||||
if ( (_BYTE)p_vm_instruction_opcode5 == 2 )
|
||||
{
|
||||
LOWORD(v34) = v18;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOWORD(v34) = HIWORD(v18);
|
||||
v20 = ((_BYTE)v0 - 0x48) & (v18 + v20) ^ 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
// stack pointer adjust
|
||||
v26 = (p_vm_instruction_opcode5 | 0x47) - 4;
|
||||
v27 = v0->vm_instruction_opcode1 - 0x6A;
|
||||
if ( v0->vm_instruction_opcode1 == 0x6A )
|
||||
{
|
||||
if ( v24 == 2 )
|
||||
{
|
||||
*(_QWORD *)vm_stack_pointer -= 2LL;
|
||||
}
|
||||
else
|
||||
{
|
||||
*(_QWORD *)vm_stack_pointer -= 8LL;
|
||||
LOBYTE(v26) = v27 ^ (((v27 ^ v26) - 0xF) | 0x28);
|
||||
}
|
||||
v26 = (unsigned __int8)v26 & 0xA8;
|
||||
}
|
||||
LOBYTE(p_vm_instruction_opcode5) = v0->vm_instruction_opcode1;
|
||||
v28 = p_vm_instruction_opcode5 | (v26 + 0x8000FBFFLL);
|
||||
if ( (_BYTE)p_vm_instruction_opcode5 == 0x1C )
|
||||
{
|
||||
vm_opnd_maybe_1 = v0->vm_opnd_maybe_1;
|
||||
v28 = 0x400 - vm_opnd_maybe_1;
|
||||
if ( (char *)(vm_opnd_maybe_1 - 0x31AC9D7C) != vm_stack_pointer )
|
||||
{
|
||||
if ( v24 == 2 )
|
||||
*(_QWORD *)vm_stack_pointer += 2LL;
|
||||
else
|
||||
*(_QWORD *)vm_stack_pointer += 8LL;
|
||||
v28 = 0x3E4LL;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Themida doesn't go through a separate dispatcher in the handler. It moves directly from the handler to the next handler.
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
vmctx->vm_handlerKey -= 0x5AC92481;
|
||||
vmctx->vm_handlerKey ^= 0x3F8BFC4F;
|
||||
vmctx->vm_handlerKey ^= 0x2DDDE2;
|
||||
|
||||
v1 = *(unsigned __int16 *)(vmctx->vm_instruction_pointer + 4);
|
||||
index = v1 - vmctx->vm_handlerKey;
|
||||
vmctx->vm_handlerKey &= index;
|
||||
|
||||
next_handler = vmctx->vm_handlerTable[(unsigned __int16)index];
|
||||
|
||||
// VIP update
|
||||
vmctx->vm_instruction_pointer += *(int *)vmctx->vm_instruction_pointer;
|
||||
|
||||
jump next_handler;
|
||||
```
|
||||
|
||||
### VM EXIT:
|
||||
|
||||
Converts registers from VM CONTEXT to actual registers and releases the spinlock.
|
||||
|
||||
Therefore, it includes the process of VM CONTEXT -> STACK -> REAL CONTEXT conversion.
|
||||
|
||||
```
|
||||
// Simplified example. Differs from actual implementation.
|
||||
.themida:14000AEA2 pop r8
|
||||
.themida:14000AEA4 pop r9
|
||||
.themida:14000AEA6 pop r10
|
||||
.themida:14000AEA8 pop r11
|
||||
.themida:14000AEAA pop r12
|
||||
.themida:14000AEAC pop r13
|
||||
.themida:14000AEAE pop r14
|
||||
.themida:14000AEB0 pop r15
|
||||
.themida:14000AEB2 pop rdi
|
||||
.themida:14000AEB3 pop rsi
|
||||
.themida:14000AEB4 pop rbp
|
||||
.themida:14000AEB5 pop rbx
|
||||
.themida:14000AEB6 pop rdx
|
||||
.themida:14000AEB7 pop rcx
|
||||
.themida:14000AEB8 pop rax
|
||||
.themida:14000AEB9 popfq
|
||||
.themida:14000AEBA popfq
|
||||
.themida:14000AEBB retn 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handler Operation Principles and Characteristics
|
||||
|
||||
1. **Fetching opcode from bytecode**
|
||||
- Read 2 bytes using `*(unsigned __int16*)(vm_bytecodePtr + offset)`, then perform XOR/ADD etc. with `vm_handlerKey`.
|
||||
- After `& 0xffff` × 8 → Get the "next handler" address from the **handler table**.
|
||||
2. **VM CONTEXT manipulation**
|
||||
- Apply arithmetic/logical operations to `vReg_RAX`, `vReg_RBX`, `vm_flagsA/B`, `field_0[...](stack)`, etc.
|
||||
- Instead of EFLAGS, simulate with `vm_flagsA`, or partially obfuscate using pushfq/popfq, __readeflags()/__writeeflags().
|
||||
3. **Opcodes**
|
||||
- Logic like "if (vm_instruction_opcodeN == 0x16) then pop 2 bytes from field_0[...]" is common.
|
||||
- Observed upper/lower 4 bits of opcode (`(value & 0xF0)>>4`, `(value & 0xF)`). Concepts like opcodeMain and subOpcode might exist.
|
||||
4. **Moving to next bytecode**
|
||||
- `vm_bytecodePtr += *(int*)(vm_bytecodePtr + someOffset)`
|
||||
- Different handlers have different offsets. e.g., +6, +3, +4, etc.
|
||||
|
||||
------
|
||||
|
||||
## Handler Code Analysis Example
|
||||
|
||||
Below is a simplified example of the form of some actual (obfuscated) handlers:
|
||||
|
||||
```c
|
||||
__int64 __fastcall sub_14003411C(VM_CONTEXT *v0) {
|
||||
if ((v0->vm_handlerKey & 2) != 0)
|
||||
v0->vm_flagsA += 0x7660110;
|
||||
// swap(vm_handlerKey, vm_flagsA)
|
||||
int tmp = v0->vm_handlerKey;
|
||||
v0->vm_handlerKey = v0->vm_flagsA;
|
||||
v0->vm_flagsA = tmp;
|
||||
|
||||
// ... Various VM command executions (operand fetch, execution, etc.)
|
||||
|
||||
// bytecode fetch
|
||||
unsigned int opcode = *(unsigned __int16*)v0->vm_bytecodePtr;
|
||||
// next handler = handlerTable[opcode * 8]
|
||||
__int64 nextHandler = *( (__int64*)(v0->vm_handlerTable + (opcode & 0xFFFF) * 8) );
|
||||
|
||||
// vm_bytecodePtr += *(int*)(v0->vm_bytecodePtr + 3)
|
||||
v0->vm_bytecodePtr += *(int*)(v0->vm_bytecodePtr + 3);
|
||||
|
||||
// call/jmp nextHandler
|
||||
return ((handlerFuncType)nextHandler)(...);
|
||||
}
|
||||
```
|
||||
|
||||
Thus, a **handler** operates in the order of "(1) Update VM CONTEXT → (2) Extract opcode from bytecode → (3) Calculate next handler address → (4) Move bytecode pointer."
|
||||
|
||||
---
|
||||
|
||||
## Themida Analyzer Development Ideas
|
||||
|
||||
1. Initial Analysis Phase
|
||||
- Load the binary and start executing virtualized code at the specified address
|
||||
- Set all registers symbolically and emulate instructions using the Triton engine
|
||||
- Identify key offsets of vm context (vip/vsp registers, etc.)
|
||||
- Symbolize vip/vsp registers to trace subsequent memory accesses
|
||||
2. Handler Pattern Matching
|
||||
- Analyze the AST of values stored in the last store instruction of each handler
|
||||
- Match with patterns to understand the operational meaning of the handler
|
||||
- Pattern examples:
|
||||
- "[vsp] + [vsp]" → ADD
|
||||
- "~[vsp] | ~[vsp]" → NAND
|
||||
- "[vsp] >> ([vsp] & 0x3f)" → SHR
|
||||
3. Control Flow Analysis and Transformation
|
||||
- Convert identified handlers to basic blocks
|
||||
- Slice the RIP register to search for the next basic block address
|
||||
- Connect basic blocks to construct a control flow graph
|
||||
- Convert all basic blocks to LLVM IR for restoration as executable code
|
||||
4. Optimization Phase (Optional)
|
||||
- Apply LLVM optimization passes
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user