-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnotes.txt
169 lines (127 loc) · 4.92 KB
/
notes.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
NOTES
These are observations made while looking through WARBIRD code.
Some are relevant to deobfuscation, while some are just oddities and curiosities.
Hopefully, once I have a better understanding of everything, these notes will look a lot nicer!
===========================================================
Obfuscated jmp structure
; where reg is one of: eax, ecx, edx, ebx, ebp, esi, edi
; push is sometimes replaced with the following equivalent instructions
; lea esp, [esp-4]
; mov [esp], reg
; IDA (and perhaps other disassemblers idk) confuses the first parts of the stub for constant bytes
; This combined with the random instruction replacement will make the stub look different, but its infact always the same
push reg
push reg
lea reg, [addr1]
lea reg, [reg+const1] ; reg = [actual routine - 10h] = start of checksum data
mov dword ptr [esp+4], reg
lea reg, [addr2]
lea reg, [reg+const2] ; reg = [verifier stub]
push reg
mov reg, dword ptr [esp+4] ; restore original value of reg
; now the stack looks like this
; [esp]: [verifier stub]
; [esp+4]: original value of reg
; [esp+8]: [checksum data]
ret 4
; here we jump to verifier
; now esp moves 8 bytes forward (4-byte ret address plus 4 from operand), so it points to the checksum data
; after verifier finishes, it adds 0x10 to the value at [esp]
; now esp points to the target jump address, and another ret is done to "return" to it
; rarely (~1% of the time), the last three instructions are replaced with:
; xchg [esp], reg
; ret
; the principle of operation and results are the same, except only 2 values are placed on the stack
; =======BYTE MARKERS=======
; Useful for finding obfuscated jumps in a disassembler
lea esp, [esp - 4]
mov [esp], reg
; eax - 8d 64 24 fc 89 04 24
; ecx - 8d 64 24 fc 89 0c 24
; edx - 8d 64 24 fc 89 14 24
; ebx - 8d 64 24 fc 89 1c 24
; ebp - 8d 64 24 fc 89 2c 24
; esi - 8d 64 24 fc 89 34 24
; edi - 8d 64 24 fc 89 3c 24
push reg
; eax - 50
; ecx - 51
; edx - 52
; ebx - 53
; ebp - 55
; esi - 56
; edi - 57
; 0x11223344 is an example address
lea reg, [0x11223344]
; eax - 8d 05 44 33 22 11
; ecx - 8d 0d 44 33 22 11
; edx - 8d 15 44 33 22 11
; ebx - 8d 1d 44 33 22 11
; ebp - 8d 2d 44 33 22 11
; esi - 8d 35 44 33 22 11
; edi - 8d 3d 44 33 22 11
mov reg, [esp+4]
ret 4
; eax - 8b 44 24 04 c2 04 00
; ecx - 8b 4c 24 04 c2 04 00
; edx - 8b 54 24 04 c2 04 00
; ebx - 8b 5c 24 04 c2 04 00
; ebp - 8b 6c 24 04 c2 04 00
; esi - 8b 74 24 04 c2 04 00
; edi - 8b 7c 24 04 c2 04 00
xchg [esp], reg
ret
; eax - 87 04 24 c3
; ecx - 87 0c 24 c3
; edx - 87 14 24 c3
; ebx - 87 1c 24 c3
; ebp - 87 2c 24 c3
; esi - 87 34 24 c3
; edi - 87 3c 24 c3
===========================================================
Hidden functions in CRT initialization
Upon launch, msvcrt will run a list of functions to initialize the C++ runtime like so:
static bool __cdecl initialize_c()
{
_initialize_onexit_table(&__acrt_atexit_table);
_initialize_onexit_table(&__acrt_at_quick_exit_table);
// Do C initialization:
if (_initterm_e(__xi_a, __xi_z) != 0)
{
return false;
}
// Do C++ initialization:
_initterm(__xc_a, __xc_z);
return true;
}
Of particular interest is the _initterm function, with the following implementation:
// Calls each function in [first, last). [first, last) must be a valid range of
// function pointers. Each function is called, in order.
extern "C" void __cdecl _initterm(_PVFV* const first, _PVFV* const last)
{
for (_PVFV* it = first; it != last; ++it)
{
if (*it == nullptr)
continue;
(**it)();
}
}
(Both code examples taken from UCRT 10.0.14393.0)
The addresses __xc_a and __xc_z are symbols in the pdb, so we can find the list of these functions.
Many are not interesting (just allocating class memory), but some odd functions are included as well (see following sections).
When in doubt on where a function is being executed, consider checking at __xc_a!
===========================================================
Debugger detection
In _initterm initialization routines, the following device names are referenced:
\\.\SICE
\\.\NTICE
\\.\SIWVID
these correspond to the SoftICE kernel-level debugger. It seems to be rather famous for software cracking.
I have not looked hard into how these strings are used, but they appear to be XORed against some constant data, with the result held in an array somewhere in .data.
Not like its much of a hindrance anyway, since we have x64dbg :P
===========================================================
EnterObfuscatedMode
This function is also called from _initterm, and is always called from an obfuscated jump block.
The first steps are to perform a binary search within the block of data specified by the symbol WARBIRD::g_ObfuscatedBlockData.
(Fill in later)
Lastly, the return is called, and the function returns to the decrypted code.