archive: add 5 repo prompt(s) [skip ci]

This commit is contained in:
github-actions[bot]
2026-02-24 13:31:07 +00:00
parent 6619781b27
commit b3b049a771
5 changed files with 136991 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+471
View File
@@ -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:
![alt text](img/5.png)
F5:
![alt text](img/6.png)
#### 使用后:
CFG:
![alt text](img/8.png)
F5:
![alt text](img/7.png)
### libtersafe
python debr.py -f libtersafe.so -s 1BDC58 -e 1BDE3C
#### 使用前:
CFG:
![alt text](img/4.png)
F5:
![alt text](img/1.png)
#### 使用后:
CFG:
![alt text](img/3.png)
F5:
![alt text](img/2.png)
```
`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")
```
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+870
View File
@@ -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
```