平时逆向中常用到的脚本

一些常用的脚本

脚本…

0x00 idapython-ida7.7

memory_dump

1
2
3
4
5
6
7
8
import sys
import idaapi
start_address, data_length = 0x11, 0x22
dump_file = "file_path"
data = idaapi.dbg_read_memory(start_address, data_length)
fp = open(dump_file, 'wb')
fp.write(data)
fp.close()

array_dump

1
2
3
4
5
6
7
import sys
import idaapi
addr = 0xC000088E10
arr = []
for i in range(64):
arr.append(idaapi.get_bytes(addr + i, 1))
print(arr)

匹配字节

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
import sys
import struct
import idaapi

start_addr = 0x000B1000
end_addr = 0x000B6CCE

"""
push eax
push eax
pushf
call $+5
pop eax
add eax, 21h ; '!'
mov [esp+8], eax
popf
pop eax
"""
bytes_arr = [0x50, 0x50, 0x9c, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x58, 0x5, 0x21, 0x00, 0x00, 0x00, 0x89, 0x44, 0x24, 0x8, 0x9d, 0x58]

def patch(addr):
push_addr = 0
for k in range(4):
push_addr = (push_addr << 8) | idaapi.get_byte(addr + 13 - k)
push_addr += addr + 8
push_addr &= 0xffffffff
print("addr", hex(addr), hex(push_addr))
push_addr_arr = struct.pack("<I", push_addr)
# push jmp_addr
idaapi.patch_byte(addr, 0x68)
for ind, bt in enumerate(push_addr_arr):
idaapi.patch_byte(addr + ind + 1, bt)
# nop
for k in range(len(bytes_arr_1) - len(push_addr_arr) - 1):
idaapi.patch_byte(addr + k + 1 + len(push_addr_arr), 0x90)

# retn -> pop eax; jmp eax;
# 58 FF E0
def patch_return(addr):
idaapi.patch_byte(addr, 0x58)
idaapi.patch_byte(addr + 1, 0xFF)
idaapi.patch_byte(addr + 2, 0xE0)
idaapi.patch_byte(addr + 15, 0x90)

i = start_addr
while True:
if i > end_addr:
break
is_match = True
for j in range(len(bytes_arr_1)):
if j in [10, 11, 12, 13]:
continue
cur_byte = idaapi.get_byte(i + j)
if cur_byte != bytes_arr_1[j]:
is_match = False
break
if is_match:
patch(i)
i += 1

# modify return
i = start_addr
while True:
if i > end_addr:
break
is_match = True;
for j in range(15):
if idaapi.get_byte(i + j) != 0x90:
is_match = False
break
if is_match == True and idaapi.get_byte(i + 15) == 0xC3:
patch_return(i)
i += 1

0x01 gdb+python自动化测试

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
"""
gdb ./picStore
source wp.py
see rctf picStore for details
"""
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")
# 因为不只有LoadByte、LoadInt、LoadNumber、LoadInteger函数调用LoadBlock
if rip == 0x55555559C078 :
# rdx为LoadBlock的size参数
rdx = frame.read_register("rdx")
Esp += rdx
# LoadByte
elif rip == 0x55555559C184:
pt(f"{hex(Esp).ljust(10,' ')} => 1")
# LoadInt
elif rip == 0x55555559C1D1:
pt(f"{hex(Esp).ljust(10,' ')} => 4")
# LoadNumber
elif rip == 0x55555559C111:
pt(f"{hex(Esp).ljust(10,' ')} => 8")
# LoadInteger
elif rip == 0x55555559C0A1:
pt(f"{hex(Esp).ljust(10,' ')} => 8")
# 0x19f0是lua字节码文件的大小
if Esp == 0x19f0:
fp.write(strr)
fp.close()
print("finish!!!")
enddd = time.time()
print(enddd - startt)
break
gdb.execute('c')

0x02 Z3相关script

 见rctf picStore

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
from z3 import *
solver = Solver()

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

mappp = [105, 244, 63, 10, 24, 169, 248, 107, 129, 138, 25, 182, 96, 176, 14, 89, 56, 229, 206, 19, 23, 21, 22, 198, 179, 167, 152, 66, 28, 201, 213, 80, 162, 151, 102, 36, 91, 37, 50, 17, 170, 41, 3, 84, 85, 226, 131, 38, 71, 32, 18, 142, 70, 39, 112, 220, 16, 219, 159, 222, 11, 119, 99, 203, 47, 148, 185, 55, 93, 48, 153, 113, 1, 237, 35, 75, 67, 155, 161, 74, 108, 76, 181, 233, 186, 44, 125, 232, 88, 8, 95, 163, 200, 249, 120, 243, 174, 212, 252, 234, 58, 101, 228, 86, 109, 144, 104, 121, 117, 87, 15, 132, 12, 20, 165, 115, 136, 135, 118, 69, 68, 2, 82, 123, 250, 251, 53, 255, 51, 221, 211, 195, 145, 140, 254, 0, 116, 43, 29, 217, 197, 183, 168, 188, 34, 218, 146, 147, 98, 149, 246, 180, 103, 33, 40, 207, 208, 192, 143, 26, 154, 225, 100, 141, 175, 124, 230, 62, 177, 205, 110, 202, 253, 173, 46, 52, 114, 164, 166, 137, 158, 122, 13, 83, 178, 133, 189, 187, 7, 184, 77, 245, 216, 190, 194, 72, 157, 172, 171, 199, 160, 45, 49, 27, 204, 81, 6, 92, 59, 209, 239, 130, 97, 61, 214, 215, 73, 90, 126, 42, 30, 240, 79, 224, 78, 223, 111, 60, 4, 5, 196, 231, 106, 64, 139, 235, 150, 227, 238, 191, 127, 31, 156, 54, 241, 242, 134, 247, 128, 65, 94, 57, 210, 236, 9, 193]
flag = ""
for i in range(len(flag2)):
ttt = mappp.index(flag2[i])
ttt &= 0xFF
flag += chr(ttt ^ 0xFF ^ i)

print(flag)
# flag{U_90t_th3_p1c5t0re_fl49!}
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
# 找出所有解
from z3 import *
import hashlib

def check_and_out(arr):
assert len(arr) == 4
s = ""
for row in arr:
for col in row:
if col == 0:
s += "0"
elif col == 1:
s += "1"
elif col == -1:
s += "2"
md5_hash = hashlib.md5(s.encode()).hexdigest()
if md5_hash == "aac82b7ad77ab00dcef90ac079c9490d":
print("[+] success", s)
return True
else:
# print("fail", s)
return False

def col_to_int(col):
return Sum([If(arr[r][col] == -1, 2, arr[r][col]) * (10 ** (rows - 1 - r)) for r in range(rows)])

solver = Solver()

rows, cols = 0x4, 0x27
arr = [[Int(f"arr_{r}_{c}") for c in range(cols)] for r in range(rows)]

# col should have order
for c in range(cols - 1):
solver.add(col_to_int(c) < col_to_int(c + 1))
first_row = [0] * 13 + [1] * 13 + [-1] * 13
for c in range(cols):
solver.add(arr[0][c] == first_row[c])

# Only could be -1,0,1
for r in range(rows):
for c in range(cols):
solver.add(Or(arr[r][c] == -1, arr[r][c] == 0, arr[r][c] == 1))

# -1,0,1 elements number is equal
for r in range(rows):
solver.add(Sum([If(arr[r][c] == -1, 1, 0) for c in range(cols)]) == cols // 3)
solver.add(Sum([If(arr[r][c] == 0, 1, 0) for c in range(cols)]) == cols // 3)
solver.add(Sum([If(arr[r][c] == 1, 1, 0) for c in range(cols)]) == cols // 3)

# cols have not 1111 or -1-1-1-1
for c in range(cols):
# solver.add(Sum([If(arr[r][c] == 0, 1, 0) for r in range(rows)]) > 0)
solver.add(Sum([If(arr[r][c] == 1, 1, 0) for r in range(rows)]) != 4)

# have opposite cols, like [1,-1,1,0] and [-1,1,-1,0]
for c1 in range(cols):
for c2 in range(c1 + 1, cols):
constraints = [Not(arr[r][c1] == -arr[r][c2]) for r in range(rows)]
solver.add(Or(*constraints))

all_vars = [var for sublist in arr for var in sublist]

may_result_num = 0
while True:
if may_result_num % 100 == 0:
print("[+] number:", may_result_num)
if solver.check() == sat:
model = solver.model()
example_arr = [[model.evaluate(arr[r][c]).as_long() for c in range(cols)] for r in range(rows)]
res = check_and_out(example_arr)
may_result_num += 1
if res:
break
else:
block = []
for v in all_vars:
c = model[v]
block.append(v != c)
solver.add(Or(block))
else:
print("[*] No solution found.")
break

0x03 实用的linux命令

1
2
3
4
grep -rn "AABB" /path                            # Linux在某目录下找字符串"AABB"
c:\Windows\System32\findstr.exe /s "AABB" "*.py" # Windows在某目录下.py文件中找字符串"AABB"
netstat -anp | grep :7777 # 查看占用7777的进程
sudo apt-cache madison curl # 查看curl包有哪些可安装的版本

0x04 密码算法相关脚本

base64

1
2
3
4
5
import base64

res = "xxx".encode()
res = base64.b64decode(res)
print(res)

xxtea|tea|xtea

 见tea-algorithm

rc4

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
from Crypto.Cipher import ARC4

class rc4util():
def __init__(self, key):
if isinstance(key, str):
self.__keyGen = key.encode()
elif isinstance(key, bytes):
self.__keyGen = key
def __encrypt(self, data) -> bytes:
rc4 = ARC4.new(self.__keyGen)
res = rc4.encrypt(data)
return res
def __decrypt(self, data) -> bytes:
rc4 = ARC4.new(self.__keyGen)
res = rc4.decrypt(data)
return res
def Encrypt(self, d) -> bytes:
res = self.__encrypt(d)
return res
def Decrypt(self, d) -> bytes:
res = self.__decrypt(d)
return res

key = "12345678"
def Entry(d):
rc4 = rc4util(key)
ret = rc4.Encrypt(d)
return ret
def Decry(d):
rc4 = rc4util(key)
ret = rc4.Decrypt(d)
return ret

if __name__ == "__main__":
ciphertext = bytes([0xe9,0x97,0x64,0xe6,0x7e,0xeb,0xbd,0xc1,0xab,0x43])
print(Entry(ciphertext))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CRC4::RC4(unsigned char pData[], unsigned long lenData)
{
int sBox[256];
int i = 0, j = 0;
long Offset;

// Create a local copy of the s-box. Better than recreating each time before encrypting/decrypting.
memcpy(sBox, m_sBox, 256 * sizeof(int));

//Encrypt the data
for (Offset = 0; Offset < lenData; Offset++)
{
i = (i + 1) % 256;
j = (j + sBox[i]) % 256;
swap = sBox[i];
sBox[i] = sBox[j];
sBox[j] = swap;
pData[Offset] ^= sBox[(sBox[i] + sBox[j]) % 256];
}
}

RC5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct {
uint32_t p1;
uint32_t p2;
} uint32_pair;

uint32_t ror32(uint32_t v, uint32_t r) {
return (v >> r) | (v << (32 - r));
}

uint32_pair decrypt(uint32_t p1, uint32_t p2) {
for (int i = 21; i > 0; --i) {
p2 -= extendKey[i * 2 + 1];
p2 = ror32(p2, p1 & 31);
p2 ^= p1;
p1 -= extendKey[i * 2];
p1 = ror32(p1, p2 & 31);
p1 ^= p2;
}
p2 -= extendKey[1];
p1 -= extendKey[0];
uint32_pair ret = {p1, p2};
return ret;
}

AES盒求逆

AES加解密(C语言版本):https://github.com/lmshao/AES

1
2
3
4
5
6
7
8
9
10
new_s_box = [...]

new_contrary_sbox = [0]*256

for i in range(256):
line = (new_s_box[i]&0xf0)>>4
rol = new_s_box[i]&0xf
new_contrary_sbox[(line*16)+rol] = i

print (new_contrary_sbox)

0x05 线性规划求解

 见starctf-2023的SIMPLE-WMM:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
# @Time : 2023/09/12 13:22:52
# @Author: wd-2711
# @Link : https://www.cnblogs.com/joy-1120/p/8529772.html
'''

from scipy import optimize
import numpy as np

# 目标参数
c = np.array([70, 65, 80, 75])
# 小于等于
A = np.array([[4,4,3,7],[6,3,5,4],[5,2,3,3],[6,5,1,2]])
b = np.array([90,120,60,100])

# 求解
res = optimize.linprog(-c,A,b,bounds=((0,None),(0,None),(0,None),(0,None)))
print(res)

# x: array([ 0., 15., 10., 0.])
# max = 1775

0x06 frida脚本

https://2298233831.github.io/2022/10/24/So%E5%B1%82Hook/

/proc/self/maps检测

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
function main() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
var readPtr = Module.findExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, 'int', ['int', 'pointer', "int"]);
// 可以事先准备好,也可以不准备,看 app 权限
var fakePath = "/data/data/com.app/maps";
var file = new File(fakePath, "w");
var buffer = Memory.alloc(512);
// 替换 open 函数
Interceptor.replace(openPtr, new NativeCallback(function (pathnameptr, flag) {
var pathname = Memory.readUtf8String(pathnameptr);
var realFd = open(pathnameptr, flag);
// pathname 中含有 "maps",此时返回 fakePath 的 fd
if (pathname.indexOf("maps") >= 0) {
while (parseInt(read(realFd, buffer, 512)) !== 0) {
var oneLine = Memory.readCString(buffer);
if (oneLine.indexOf("frida") === -1) {
file.write(oneLine);
}
}
var filename = Memory.allocUtf8String(fakePath);
return open(filename, flag);
}
var fd = open(pathnameptr, flag);
return fd;
}, 'int', ['pointer', 'int']));
}
setImmediate(main)

打印内存

1
2
3
4
5
6
7
8
9
10
11
12
function print_mem(){
Java.perform(function(){
var addr = Module.findBaseAddress("libezandroid.so")
var byte_A138 = addr.add(0xa138)
console.log(hexdump(byte_A138, {
offset:0,
length: 64,
header: true,
ansi: true,
}))
})
}

0x07 OLLVM 去混淆

angr 版

 脚本逻辑为:

1
2
3
4
5
1. 确定代码的开始与结束位置,让 angr 分析出 CFG 图。入口块的入度为 0,出口块的出度为 0。
2. 找到主分发块(主分发块的入度 > 1,有时这个判断逻辑并不准确,可以通过 patch jump 来解决)。
3. 主分发块的前一个块可以为:入口块、预处理块。通过预处理块可以找到有用块。
4. 因此,我们可以将块分为:入口块、预处理块、有用块。
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
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# angr: https://github.com/cq674350529/deflat
#!/usr/bin/env python3
import sys

import argparse
import angr
import pyvex
import claripy
import struct
from collections import defaultdict

# Refer to https://github.com/cq674350529/deflat/blob/master/am_graph.py
import am_graph

# ---------------------------------------------------------------------------------------------------------------------------------

ARCH_X86 = {"X86", "AMD64"}
ARCH_ARM = {"ARMEL", "ARMHF"}
ARCH_ARM64 = {'AARCH64'}

OPCODES = {
'x86':{'a': b'\x87', 'ae': b'\x83', 'b': b'\x82', 'be': b'\x86', 'c': b'\x82', 'e': b'\x84', 'z': b'\x84', 'g': b'\x8F', 'ge': b'\x8D', 'l': b'\x8C', 'le': b'\x8E', 'na': b'\x86', 'nae': b'\x82', 'nb': b'\x83', 'nbe': b'\x87', 'nc': b'\x83', 'ne': b'\x85', 'ng': b'\x8E', 'nge': b'\x8C', 'nl': b'\x8D', 'nle': b'\x8F', 'no': 'b\x81', 'np': b'\x8B', 'ns': b'\x89', 'nz': b'\x85', 'o': b'\x80', 'p': b'\x8A', 'pe': b'\x8A', 'po': b'\x8B', 's': b'\x88', 'nop': b'\x90', 'jmp': b'\xE9', 'j': b'\x0F'},
'arm':{'nop': b'\x00\xF0\x20\xE3', 'b': b'\xEA', 'blt': b'\xBA', 'beq': b'\x0A', 'bne': b'\x1A', 'bgt': b'\xCA', 'bhi': b'\x8A', 'bls': b'\x9A', 'ble': b'\xDA', 'bge': b'\xAA'},
'arm64':{'nop': b'\x1F\x20\x03\xD5', 'b': b'\x14', 'b_cond':{'eq': 0x0, 'ne': 0x1, 'hs': 0x2, 'lo': 0x3, 'mi': 0x4, 'pl': 0x5, 'vs': 0x6, 'vc': 0x7, 'hi': 0x8, 'ls': 0x9, 'ge': 0xA, 'lt': 0xB, 'gt':0xC, 'le':0xD}}
}

def fill_nop(data, start_addr, length, arch):
if arch.name in ARCH_X86:
# x86 平台
for i in range(0, length):
data[start_addr + i] = ord(OPCODES['x86']['nop'])
elif arch.name in ARCH_ARM | ARCH_ARM64:
# ARM 平台
if arch.name in ARCH_ARM:
nop_value = OPCODES['arm']['nop']
else:
nop_value = OPCODES['arm64']['nop']

if arch.memory_endness == "Iend_BE":
nop_value = nop_value[::-1]
for i in range(0, length, 4):
data[start_addr+i] = nop_value[0]
data[start_addr+i+1] = nop_value[1]
data[start_addr+i+2] = nop_value[2]
data[start_addr+i+3] = nop_value[3]

def patch_instruction(data, offset, value):
for i in range(len(value)):
data[offset+i] = value[i]

def ins_j_jmp_hex_x86(cur_addr, target_addr, j_cond):
if j_cond == 'jmp':
j_opcode = OPCODES['x86']['jmp']
j_ins_size = 5
else:
j_opcode = OPCODES['x86']['j'] + OPCODES['x86'][j_cond]
j_ins_size = 6

jmp_offset = target_addr - cur_addr - j_ins_size
patch_ins_hex = j_opcode + struct.pack('<i', jmp_offset)
return patch_ins_hex

def ins_b_jmp_hex_arm(cur_addr, target_addr, b_cond):
b_offset = (target_addr - cur_addr - 4*2) // 4
patch_ins_hex = struct.pack('<i', b_offset)[:-1] + OPCODES['arm'][b_cond]
return patch_ins_hex

def ins_b_jmp_hex_arm64(cur_addr, target_addr, b_cond):
# reference: https://blog.csdn.net/qianlong4526888/article/details/8247219
if b_cond == 'b':
if cur_addr > target_addr:
patch_ins_hex = struct.pack('<I', ((0x14000000 | 0x03ffffff) - (cur_addr - target_addr) // 4))
else:
patch_ins_hex = struct.pack('<I', ((0x14000000 & 0xfc000000) + (target_addr - cur_addr) // 4))
else:
offset = (((target_addr - cur_addr) // 4) << 5) & 0x00ffffe0
opcode = OPCODES['arm64']['b_cond'][b_cond.lower()]
if opcode % 2 == 0:
opcode += 1
else:
opcode -= 1
patch_ins_hex = struct.pack('<I', 0x54000000 | offset | opcode)
return patch_ins_hex

# ---------------------------------------------------------------------------------------------------------------------------------

def get_relevant_nop_nodes(supergraph, pre_dispatcher_node, prologue_node, retn_node):
"""
Args:
(1) pre_dispatcher_node,预处理块。
(2) prologue_node,入口块。
(3) retn_node,出口块。

Retn:
(1) relevant_nodes,有用块。
(2) nop_nodes,可 nop 掉的块。
"""
relevant_nodes = []
nop_nodes = []
instruction_gate = 16

for node in supergraph.nodes():
if supergraph.has_edge(node, pre_dispatcher_node) and node.size > instruction_gate and node.addr != prologue_node.addr:
# 如果此块是预处理块的前驱,且此块不是入口块,且此块的指令数量大于 instruction_gate,则定义为有用块(relevant_node)
relevant_nodes.append(node)
elif node.addr not in (prologue_node.addr, retn_node.addr, pre_dispatcher_node.addr):
# 如果此块的指令数量小于等于 instruction_gate,且不为入口块、预处理块、出口块,则定义为 nop 块(可以 nop 掉的)
nop_nodes.append(node)
return relevant_nodes, nop_nodes

def symbolic_execution(project, relevant_block_addrs, start_addr,
hook_addrs=None, modify_value=None, inspect=False,
hook_size=None, pre_dispatcher_node=None):
"""
一个块的符号执行。
Args:
(1) project,不再赘述。
(2) relevant_block_addrs,有用块(包括入口块与出口块)的地址。
(3) start_addr,要处理的块的起始地址。
(4) hook_addrs,要 hook 的地址列表。
(5) modify_value,修改条件分支。
(6) inspect,是否要进行检查。
(7) hook_size,一个字典,代表每一个要 hook 的地址的第一条指令的大小。
(8) pre_dispatcher_node,预处理块。

Retn:
(1) active_state.addr,符号执行中真正可以执行到的有用块,也就是 start_addr 对应块的下一个块。
"""
def retn_procedure(state):
ip = state.solver.eval(state.regs.ip)
project.unhook(ip)
return

def statement_inspect(state):
# expressions 代表 IR 语句中的所有表达式
expressions = list(state.scratch.irsb.statements[state.inspect.statement].expressions)
if len(expressions) != 0 and isinstance(expressions[0], pyvex.expr.ITE):
# 当表达式中的第一条为 if-then-else
# 改变执行流,让分支强制走一个方向
state.scratch.temps[expressions[0].cond.tmp] = modify_value
state.inspect._breakpoints['statement'] = []

# 看上去似乎没有什么用
if hook_addrs is not None:
for hook_addr in hook_addrs:
skip_length = hook_size[hook_addr]
# 使用 angr 的 project.hook
project.hook(hook_addr, retn_procedure, length=skip_length)

# 符号执行的起点
state = project.factory.blank_state(addr=start_addr, remove_options={angr.sim_options.LAZY_SOLVES})
if inspect:
# 在执行任何 IR 语句之前设置断点,并运行 statement_inspect
state.inspect.b('statement', when=angr.state_plugins.inspect.BP_BEFORE, action=statement_inspect)

# 符号执行
sm = project.factory.simulation_manager(state)
sm.step()
flag = 0
while len(sm.active) > 0:
for active_state in sm.active:
if active_state.addr in relevant_block_addrs:
return active_state.addr
# 如果两次找到预处理块,那么就返回 None,从而避免死循环
if active_state.addr == pre_dispatcher_node.addr:
flag = flag + 1
if flag == 2:
return None
sm.step()

return None

def main():
# 输入参数
parser = argparse.ArgumentParser(description="deflat control flow script")
parser.add_argument("-f", "--file", help="binary to analyze")
parser.add_argument("--addr_start")
parser.add_argument("--addr_end")
args = parser.parse_args()
if args.file is None or args.addr is None:
parser.print_help()
sys.exit(0)

# 一些配置
filename = args.file
start = int(args.addr_start, 16)
end = int(args.addr_end, 16)
project = angr.Project(filename, load_options={'auto_load_libs': False})

# 避免重叠块,禁用 force_complete_scan 以避免可能的错误块
cfg = project.analyses.CFGFast(
start=start >> 12 << 12,
end=end,
force_complete_scan=False,
normalize=True)
target_function = cfg.functions.get(start)
supergraph = am_graph.to_supergraph(target_function.transition_graph)
base_addr = project.loader.main_object.mapped_base >> 12 << 12

# 获得入口块与出口块
prologue_node = None
retn_node = None
for node in supergraph.nodes():
if supergraph.in_degree(node) == 0:
prologue_node = node
if supergraph.out_degree(node) == 0:
retn_node = node
if prologue_node is None or prologue_node.addr != start:
print("Something must be wrong...")
sys.exit(-1)

# 入口块的后继是主分发块(main_dispatcher_node)
# 主分发块(main_dispatcher_node)的前驱要么是入口块,要么是预处理块(pre_dispatcher_node)
# s0rry: main_dispatcher_node 和 pre_dispatcher_node 如果正确,就可以跑出所有的块
main_dispatcher_node = list(supergraph.successors(prologue_node))[0]
for node in supergraph.predecessors(main_dispatcher_node):
if node.addr != prologue_node.addr:
pre_dispatcher_node = node
break

# 获得有用块(relevant_nodes)与可 nop 的块(nop_nodes)
relevant_nodes, nop_nodes = get_relevant_nop_nodes(
supergraph,
pre_dispatcher_node,
prologue_node,
retn_node)

# 输出一些数据
relevant_block_addrs = [node.addr for node in relevant_nodes]
print('******************* relevant blocks ************************')
print('prologue: %#x' % start)
print('main_dispatcher: %#x' % main_dispatcher_node.addr)
print('pre_dispatcher: %#x' % pre_dispatcher_node.addr)
print('retn: %#x' % retn_node.addr)
print('relevant_blocks:', [hex(addr) for addr in relevant_block_addrs])
print('******************* symbolic execution *********************')

# 定义除了出口块以外的有用块(relevants_without_retn)、有用块(包括入口块与出口块)的地址(relevant_block_addrs)
relevants = relevant_nodes
relevants.append(prologue_node)
relevants_without_retn = list(relevants)
relevants.append(retn_node)
relevant_block_addrs.extend([prologue_node.addr, retn_node.addr])

flow = defaultdict(list)
patch_instrs = {}

# s0rry: 符号执行找有用块的时,会 hook 文件的所有函数,当函数会检查其字节并 unhook 的时候,找有用块的流程会失败
for relevant in relevants_without_retn:
block = project.factory.block(relevant.addr, size=relevant.size)
has_branches = False
hook_addrs = []
hook_size = {}
for ins in block.capstone.insns:
if project.arch.name in ARCH_X86:
# x86 平台
if ins.insn.mnemonic.startswith('cmov'):
# cmov 指令是根据标志位进行 mov 的指令,可以认为是一个条件跳转,因此需要 patch
if relevant not in patch_instrs:
patch_instrs[relevant] = ins
has_branches = True
elif ins.insn.mnemonic.startswith('call'):
# 如果是 call 指令,则需要进行 hook
hook_addrs.append(ins.insn.address)
hook_size[ins.insn.address] = len(ins.insn.bytes)
elif project.arch.name in ARCH_ARM:
# ARM 平台
if ins.insn.mnemonic != 'mov' and ins.insn.mnemonic.startswith('mov'):
# 条件跳转
if relevant not in patch_instrs:
patch_instrs[relevant] = ins
has_branches = True
elif ins.insn.mnemonic in {'bl', 'blx'}:
# 需要进行 hook
hook_addrs.append(ins.insn.address)
elif project.arch.name in ARCH_ARM64:
# ARM64 平台
if ins.insn.mnemonic.startswith('cset'):
if relevant not in patch_instrs:
patch_instrs[relevant] = ins
has_branches = True
elif ins.insn.mnemonic in {'bl', 'blr'}:
hook_addrs.append(ins.insn.address)

if has_branches:
# 如果有用块(relevant_node)的汇编指令中有条件跳转指令,则针对 true 与 false 来找到它的下一个块(最多找到两个,是不全的)
tmp_addr = symbolic_execution(project, relevant_block_addrs, relevant.addr,
hook_addrs=hook_addrs,
modify_value=claripy.BVV(1, 1),
inspect=True,
hook_size=hook_size,
pre_dispatcher_node=pre_dispatcher_node)
if tmp_addr is not None: flow[relevant].append(tmp_addr)

tmp_addr = symbolic_execution(project, relevant_block_addrs, relevant.addr,
hook_addrs=hook_addrs,
modify_value=claripy.BVV(0, 1),
inspect=True,
hook_size=hook_size,
pre_dispatcher_node=pre_dispatcher_node)
if tmp_addr is not None: flow[relevant].append(tmp_addr)
else:
# 如果有用块(relevant_node)的汇编指令中没有条件跳转指令
tmp_addr = symbolic_execution(project, relevant_block_addrs, relevant.addr,
hook_addrs=hook_addrs,
hook_size=hook_size,
pre_dispatcher_node=pre_dispatcher_node)
if tmp_addr is not None: flow[relevant].append(tmp_addr)

# 输出块之间的逻辑关系
print('************************ flow ******************************')
for k, v in flow.items():
print('%#x: ' % k.addr, [hex(child) for child in v])
print('%#x: ' % retn_node.addr, [])
print('************************ patch *****************************')

with open(filename, 'rb') as origin:
origin_data = bytearray(origin.read())
origin_data_len = len(origin_data)
recovery_file = filename + '_recovered'
recovery = open(recovery_file, 'wb')

# nop 可以 nop 的块与主分发块
for nop_node in nop_nodes:
fill_nop(origin_data, nop_node.addr - base_addr, nop_node.size, project.arch)
fill_nop(origin_data, main_dispatcher_node.addr - base_addr, main_dispatcher_node.size, project.arch)

# 进行 patch
for parent, childs in flow.items():
if len(childs) == 1:
# 块之后仅有一个块,那么就是无条件跳转
# 拿到块的最后两条指令
parent_block = project.factory.block(parent.addr, size=parent.size)
last_instr = parent_block.capstone.insns[-1]
size_last = len(last_instr.insn.bytes)
last_two_instr = parent_block.capstone.insns[-2]
size_last_two = len(last_two_instr.insn.bytes)
nop_size = size_last + size_last_two
file_offset = last_two_instr.address - base_addr

# 将最后两条指令 patch 成 jmp
if project.arch.name in ARCH_X86:
fill_nop(origin_data, file_offset, nop_size, project.arch)
patch_value = ins_j_jmp_hex_x86(last_two_instr.address, childs[0], 'jmp')
elif project.arch.name in ARCH_ARM:
patch_value = ins_b_jmp_hex_arm(last_instr.address, childs[0], 'b')
if project.arch.memory_endness == "Iend_BE":
patch_value = patch_value[::-1]
elif project.arch.name in ARCH_ARM64:
if parent.addr == start:
# 对于 ARM64,入口块的倒数第二条指令有时候是有用的
file_offset += 4
patch_value = ins_b_jmp_hex_arm64(last_instr.address + 4, childs[0], 'b')
else:
patch_value = ins_b_jmp_hex_arm64(last_instr.address, childs[0], 'b')
if project.arch.memory_endness == "Iend_BE":
patch_value = patch_value[::-1]
patch_instruction(origin_data, file_offset, patch_value)
else:
# 说明是条件跳转,之前已经记录过要 patch 的相关信息(也不是很全面,patch_instrs 最好是一个 key -> list 的映射,而不是 key -> val)
# 这里其实有一个隐含假设,就是要 patch 的指令是 block 的末尾
instr = patch_instrs[parent]
file_offset = instr.address - base_addr
size = 0
fill_nop(origin_data, file_offset, parent.addr + parent.size - instr.address, project.arch)
if project.arch.name in ARCH_X86:
# patch the cmovx instruction to jx instruction
patch_value = ins_j_jmp_hex_x86(instr.address, childs[0], instr.mnemonic[len('cmov'):])
patch_instruction(origin_data, file_offset, patch_value, size)

file_offset += 6
# patch the next instruction to jmp instrcution
patch_value = ins_j_jmp_hex_x86(instr.address + 6, childs[1], 'jmp')
patch_instruction(origin_data, file_offset, patch_value, size)
elif project.arch.name in ARCH_ARM:
# patch the movx instruction to bx instruction
bx_cond = 'b' + instr.mnemonic[len('mov'):]
patch_value = ins_b_jmp_hex_arm(instr.address, childs[0], bx_cond)
if project.arch.memory_endness == 'Iend_BE':
patch_value = patch_value[::-1]
patch_instruction(origin_data, file_offset, patch_value, size)

file_offset += 4
# patch the next instruction to b instrcution
patch_value = ins_b_jmp_hex_arm(instr.address + 4, childs[1], 'b')
if project.arch.memory_endness == 'Iend_BE':
patch_value = patch_value[::-1]
patch_instruction(origin_data, file_offset, patch_value, size)
elif project.arch.name in ARCH_ARM64:
# patch the cset.xx instruction to bx instruction
bx_cond = instr.op_str.split(',')[-1].strip()
patch_value = ins_b_jmp_hex_arm64(instr.address, childs[0], bx_cond)
if project.arch.memory_endness == 'Iend_BE':
patch_value = patch_value[::-1]
patch_instruction(origin_data, file_offset, patch_value, size)

file_offset += 4
# patch the next instruction to b instruction
patch_value = ins_b_jmp_hex_arm64(instr.address + 4, childs[1], 'b')
if project.arch.memory_endness == 'Iend_BE':
patch_value = patch_value[::-1]
patch_instruction(origin_data, file_offset, patch_value, size)

# 已经 patch 完毕
assert len(origin_data) == origin_data_len, "Error: size of data changed!!!"
recovery.write(origin_data)
recovery.close()
print('Successful! The recovered file: %s' % recovery_file)

if __name__ == '__main__':
main()

0x08 Unicorn 相关脚本

 Unicorn 可以模拟 CPU 进行去花,在此对 s0rry 的 blog 进行学习。主要就是给出了几个脚本,具体逻辑我会在脚本中注释。

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
# 执行 ARM_CODE 并读取执行结束后的 R0/R1 寄存器

from unicorn import *
from unicorn.arm_const import *

"""
mov r0, #0x37
sub r1, r2, r3
"""
ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"

def hook_code(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))

def test_arm():
print("Emulate ARM code")
try:

# Step1:设置执行汇编的指令集,对应的位数或模式
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)

# Step2:映射内存(2MB)
ADDRESS = 0x10000
mu.mem_map(ADDRESS, 2 * 0x10000)

# Step3:写入 ARM_CODE(ARM_CODE 只支持 python 的 byte 数组)
mu.mem_write(ADDRESS, ARM_CODE)

# Step4:设置寄存器
mu.reg_write(UC_ARM_REG_R0, 0x1234)
mu.reg_write(UC_ARM_REG_R2, 0x6789)
mu.reg_write(UC_ARM_REG_R3, 0x3333)

# Step5:在 ADDRESS 地址处做 hook,执行 hook_code 代码
mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)

# Step6:开始执行
mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))

# Step7:读取 R0 与 R1 寄存器
r0 = mu.reg_read(UC_ARM_REG_R0)
r1 = mu.reg_read(UC_ARM_REG_R1)
print(">>> R0 = 0x%x" % r0)
print(">>> R1 = 0x%x" % r1)

except UcError as e:
print("ERROR: %s" % e)

if __name__ == "__main__":
test_arm()
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# 基于 unicorn 的调试器(仍需修改)

from unicorn import *
from unicorn import arm_const
from unicorn import x86_const
from unicorn.arm_const import *
import sys
import hexdump
import capstone as cp

UDBG_MODE_ALL = 1
UDBG_MODE_FAST = 2

REG_ARM = {
arm_const.UC_ARM_REG_R0: "R0",
arm_const.UC_ARM_REG_R1: "R1",
arm_const.UC_ARM_REG_R2: "R2",
arm_const.UC_ARM_REG_R3: "R3",
arm_const.UC_ARM_REG_R4: "R4",
arm_const.UC_ARM_REG_R5: "R5",
arm_const.UC_ARM_REG_R6: "R6",
arm_const.UC_ARM_REG_R7: "R7",
arm_const.UC_ARM_REG_R8: "R8",
arm_const.UC_ARM_REG_R9: "R9",
arm_const.UC_ARM_REG_R10: "R10",
arm_const.UC_ARM_REG_R11: "R11",
arm_const.UC_ARM_REG_R12: "R12",
arm_const.UC_ARM_REG_R13: "R13",
arm_const.UC_ARM_REG_R14: "R14",
arm_const.UC_ARM_REG_R15: "R15",
arm_const.UC_ARM_REG_PC: "PC",
arm_const.UC_ARM_REG_SP: "SP",
arm_const.UC_ARM_REG_LR: "LR"
}

REG_X86 = {
x86_const.UC_X86_REG_EAX: "EAX",
x86_const.UC_X86_REG_EBX: "EBX",
x86_const.UC_X86_REG_ECX: "ECX",
x86_const.UC_X86_REG_EDX: "EDX",
x86_const.UC_X86_REG_EDI: "EDI",
x86_const.UC_X86_REG_ESI: "ESI",
x86_const.UC_X86_REG_EBP: "EBP",
x86_const.UC_X86_REG_ESP: "ESP",
x86_const.UC_X86_REG_EIP: "EIP",
}

REG_TABLE = {UC_ARCH_ARM: REG_ARM, UC_ARCH_X86: REG_X86}

def str2int(s):
if s.startswith('0x') or s.startswith("0X"):
return int(s[2:], 16)
return int(s)

def advance_dump(data, base):
PY3K = sys.version_info >= (3, 0)
generator = hexdump.genchunks(data, 16)
retstr = ''
for addr, d in enumerate(generator):
# 00000000:
line = '%08X: ' % (base + addr * 16)
# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
dumpstr = hexdump.dump(d)
line += dumpstr[:8 * 3]
if len(d) > 8: # insert separator if needed
line += ' ' + dumpstr[8 * 3:]
# ................
# calculate indentation, which may be different for the last line
pad = 2
if len(d) < 16:
pad += 3 * (16 - len(d))
if len(d) <= 8:
pad += 1
line += ' ' * pad

for byte in d:
# printable ASCII range 0x20 to 0x7E
if not PY3K:
byte = ord(byte)
if 0x20 <= byte <= 0x7E:
line += chr(byte)
else:
line += '.'
retstr += line + '\n'
return retstr

def _dbg_memory(mu, access, address, length, value, self):
pc = mu.reg_read(arm_const.UC_ARM_REG_PC)
print("memory error: pc: %x access: %x address: %x length: %x value: %x" % (pc, access, address, length, value))
_dbg_trace_internal(mu, pc, 4, self)
mu.emu_stop()
return True

def _dbg_trace(mu, address, size, self):
self._tracks.append(address)
# 步入/步过相关逻辑,但是没太看明白
if not self._is_step and self._tmp_bpt == 0:
if address not in self._list_bpt:
return
if self._tmp_bpt != address and self._tmp_bpt != 0:
return

return _dbg_trace_internal(mu, address, size, self)

def _dbg_trace_internal(mu, address, size, self):
self._is_step = False
print("======================= Registers =======================")
self.dump_reg()
print("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)

# 调试器主要逻辑
while True:
raw_command = input(">")
if raw_command == '':
raw_command = self._last_command
self._last_command = raw_command

# 获得当前的调试器指令
command = []
for c in raw_command.split(" "):
if c != "":
command.append(c)

# 根据调试器指令做相应操作
try:
if command[0] == 'set':
if command[1] == 'reg':
# "set reg EAX 0x1234"
self.write_reg(command[2], str2int(command[3]))
elif command[1] == 'bpt':
# "set bpt 0x1234"
self.add_bpt(str2int(command[2]))
else:
raise RuntimeError("Command set error")
elif command[0] == 's' or command[0] == 'step':
self._tmp_bpt = 0
self._is_step = True
elif command[0] == 'n' or command[0] == 'next':
self._tmp_bpt = address + size
self._is_step = False
elif command[0] == 'r' or command[0] == 'run':
self._tmp_bpt = 0
self._is_step = False
elif command[0] == 'dump':
# "dump address size"
if len(command) >= 3:
nsize = str2int(command[2])
else:
nsize = 4 * 16
self.dump_mem(str2int(command[1]), nsize)
elif command[0] == 'list':
# "list bpt"
if command[1] == 'bpt':
self.list_bpt()
elif command[0] == 'del':
# "del bpt 0x1234"
if command[1] == 'bpt':
self.del_bpt(str2int(command[2]))
elif command[0] == 'stop':
exit(0)
elif command[0] == 'track':
for i in self._tracks[-10:-1]:
print(self.sym_handler(i))
else:
raise RuntimeError("Command Not Found")
except:
raise RuntimeError("Debugger Error")

class UnicornDebugger:
def __init__(self, mu, mode=UDBG_MODE_ALL):
self._mu = mu
self._arch = mu._arch
self._mode = mu._mode
self._tracks = []
# 断点相关
self._list_bpt = []
self._tmp_bpt = 0
# 输出 asm 的指令数量
self.dis_count = 5
self._last_command = ''
self._is_step = False
self.sym_handler = self._default_sym_handler

if self._arch not in REG_TABLE.keys():
raise RuntimeError("arch:%d is not supported! " % self._arch)
self._regs = REG_TABLE[self._arch]

# 初始化 capstone 相关配置
self._capstone_arch = None
self._capstone_mode = None
self._capstone = None

if self._arch == UC_ARCH_ARM:
self._capstone_arch = cp.CS_ARCH_ARM
elif self._arch == UC_ARCH_ARM64:
self._capstone_arch = cp.CS_ARCH_ARM64
elif self._arch == UC_ARCH_X86:
self._capstone_arch = cp.CS_ARCH_X86
else:
mu.emu_stop()
raise RuntimeError("arch:%d is not supported! " % self._arch)

if self._mode == UC_MODE_THUMB:
self._capstone_mode = cp.CS_MODE_THUMB
elif self._mode == UC_MODE_ARM:
self._capstone_mode = cp.CS_MODE_ARM
elif self._mode == UC_MODE_32:
self._capstone_mode = cp.CS_MODE_32
elif self._mode == UC_MODE_64:
self._capstone_mode = cp.CS_MODE_64
else:
mu.emu_stop()
raise RuntimeError("mode:%d is not supported! " % self._mode)

self._capstone = cp.Cs(self._capstone_arch, self._capstone_mode)

if mode == UDBG_MODE_ALL:
# 执行每条指令前调用 _dbg_trace
mu.hook_add(UC_HOOK_CODE, _dbg_trace, user_data=self)

# 如果访问了未映射的内存地址或者执行特定内存区域(例如数据段),则会调用 _dbg_memory
mu.hook_add(UC_HOOK_MEM_UNMAPPED, _dbg_memory, user_data=self)
mu.hook_add(UC_HOOK_MEM_FETCH_PROT, _dbg_memory, user_data=self)

def dump_mem(self, addr, size):
data = self._mu.mem_read(addr, size)
print(advance_dump(data, addr))

def dump_asm(self, addr, size):
code = self._mu.mem_read(addr, size)
for ins in self._capstone.disasm(code, addr):
print("%s:\t%s\t%s" % (self.sym_handler(ins.address), ins.mnemonic, ins.op_str))

def dump_reg(self):
result_format = ''
for rid in self._regs.keys():
rname = self._regs[rid]
value = self._mu.reg_read(rid)
result_format += rname + " -> " + hex(value) + "\n"
print(result_format)

def write_reg(self, reg_name, value):
for rid in self._regs.keys():
rname = self._regs[rid]
if rname == reg_name:
self._mu.reg_write(rid, value)
return
raise RuntimeError("Reg not found")

def list_bpt(self):
for idx in range(len(self._list_bpt)):
print("[%d] %s" % (idx, self.sym_handler(self._list_bpt[idx])))

def add_bpt(self, addr):
self._list_bpt.append(addr)

def del_bpt(self, addr):
self._list_bpt.remove(addr)

def get_tracks(self):
for i in self._tracks[-100:-1]:
print (self.sym_handler(i))
return self._tracks

def _default_sym_handler(self, address):
return hex(address)

def set_symbol_name_handler(self, handler):
self.sym_handler = handler

"""
mov r0, #0x37
sub r1, r2, r3
"""
ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"

if __name__ == "__main__":
try:
# Step1:设置执行汇编的指令集,对应的位数或模式
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)

# Step2:映射内存(2MB)
ADDRESS = 0x10000
mu.mem_map(ADDRESS, 2 * 0x10000)

# Step3:写入 ARM_CODE(ARM_CODE 只支持 python 的 byte 数组)
mu.mem_write(ADDRESS, ARM_CODE)

# Step4:设置寄存器
mu.reg_write(UC_ARM_REG_R0, 0x1234)
mu.reg_write(UC_ARM_REG_R2, 0x6789)
mu.reg_write(UC_ARM_REG_R3, 0x3333)

# Step5:设置调试器
udbg = UnicornDebugger(mu, mode=UDBG_MODE_ALL)
udbg.add_bpt(ADDRESS)

# Step6:开始执行
mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))

except UcError as e:
print("ERROR: %s" % e)

0x08 注入

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
// DLL inject
#include <windows.h>
#include <iostream>
#include "tlhelp32.h"
#include "tchar.h"

BOOL CreateRemoteThreadInjectDll(DWORD pid, char* dll_name) {
HANDLE hProcess = NULL;
SIZE_T dwSize = 0;
LPVOID pDllAddr = NULL;
FARPROC pFuncProcAddr = NULL;

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (NULL == hProcess) return FALSE;

dwSize = 0x200;
pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pDllAddr) return FALSE;

if (FALSE == WriteProcessMemory(hProcess, pDllAddr, dll_name, dwSize, NULL))
return FALSE;

pFuncProcAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
if (NULL == pFuncProcAddr) return FALSE;

HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
if (NULL == hRemoteThread) return FALSE;

CloseHandle(hProcess);
return TRUE;
}

BOOL UnloadDll(DWORD pid, char* dll_name) {

int bufferSize = MultiByteToWideChar(CP_UTF8, 0, dll_name, -1, NULL, 0);
wchar_t* dll_name_c = new wchar_t[bufferSize];
MultiByteToWideChar(CP_UTF8, 0, dll_name, -1, dll_name_c, bufferSize);
LPCTSTR dll_name_ = dll_name_c;

BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me)) {
if (!_tcsicmp((LPCTSTR)me.szModule, dll_name_) || !_tcsicmp((LPCTSTR)me.szExePath, dll_name_)) {
bFound = TRUE;
break;
}
}

if (!bFound) {
CloseHandle(hSnapshot);
return FALSE;
}

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid))) {
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", pid, GetLastError());
return FALSE;
}

hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
}

int main(int argc, char* argv[]) {

// Input pid and dll_name
DWORD pid;
std::cout << "Pls input process pid:" << std::endl;
std::cin >> pid;
std::cout << "PID: " << pid << std::endl;

char dll_name_c[256];
std::cout << "Pls input dll path:" << std::endl;
std::cin >> dll_name_c;

// Inject DLL
bool flag = CreateRemoteThreadInjectDll(pid, dll_name_c);
if (flag == FALSE) {
std::cout << "Result: FAIL" << std::endl;
return 0;
}
std::cout << "Result: SUCCESS" << std::endl;

std::cout << "Program is stopped, input 'enter' to continue..." << std::endl;
std::cin.get();
std::cin.get();

// Unload DLL
bool unload = UnloadDll(pid, dll_name_c);
if (flag == FALSE) {
std::cout << "Unload: FAIL" << std::endl;
return 0;
}
std::cout << "Unload: SUCCESS" << std::endl;
return 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
// Injected DLL using minhook
#include "pch.h"
#include <Windows.h>
#include <MinHook.h>
#include <iostream>

#pragma comment(lib,"libMinHook.x64.lib")

typedef int (WINAPI* OldMessageBox)(HWND, LPCSTR, LPCSTR, UINT);
typedef HANDLE(WINAPI* OldCreateFileA)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);

OldCreateFileA fpCreateFileA = NULL;

HANDLE WINAPI MyCreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile) {
const char *path = "C:\\2024GameSafeRace.token1";
if (strcmp(lpFileName, path)) {
MessageBoxA(NULL, lpFileName, "MsgBox", NULL);
}
HANDLE ret = fpCreateFileA(lpFileName,
dwDesiredAccess,
dwShareMode,
lpSecurityAttributes,
dwCreationDisposition,
dwFlagsAndAttributes,
hTemplateFile);
return ret;
}

void SetHook() {
if (MH_Initialize() == MB_OK) {
MH_CreateHook(&CreateFileA, &MyCreateFileA, reinterpret_cast<void**>(&fpCreateFileA));
MH_EnableHook(&CreateFileA);
}
}

void UnHook() {
if (MH_DisableHook(&CreateFileA) == MB_OK) {
MH_Uninitialize();
}
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
printf("Attach\n");
SetHook();
break;
case DLL_PROCESS_DETACH:
printf("Detach\n");
UnHook();
break;
}
return TRUE;
}

0x09 其他

mgc binary to mgc code

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
import hexdump
import struct
import string

f = open("filepath.mgc", 'rb')

# 每次 0x178 字节,但是每 1 行是 0x30 bytes
b = f.read(0x178)
indexes = []
table = ''
while True:
b = f.read(0x178)
if len(b) != 0x178:
break
line = b[:0x30]
# type
_type = line[6]
# offset
# line 的偏移量 0x0c 处开始,按照小端序解析数据,将该位置的 4 个字节解析为一个无符号的 32 位整数
off = struct.unpack_from('<I', line, 0x0c)[0]
s = f'type: {_type:02X}, off: {off:02X}'
if _type == 5:
# 5 代表解析 line[0x20:] 的字符串
s += ', str: '+line[0x20:].decode()
elif _type == 1:
# 1 代表 line[0x18]^line[0x20] 异或的 byte
n1, n2 = line[0x18], line[0x20]
v = n1 ^ n2
s += f', byte: {n1:02X} {n2:02X} {v:02X} {chr(v)}'
table += chr(v)
elif _type == 10:
# 10 代表 line[0x18]^line[0x20] 异或的 16 比特数
n1, n2 = line[0x18], line[0x20]
v = n1 ^ n2
s += f', leshort: {n1:02X} {n2:02X} {v:02X} {v}'
indexes.append(v)
print(s)

byte2int

1
2
3
s1 = b'\xf1\xff'
print(int.from_bytes(s1, byteorder='big', signed=False))
print(int.from_bytes(s1, byteorder='little', signed=True))

int2byte

1
2
3
integer_val = 5
bytes_val = integer_val.to_bytes(2, 'big')
print(bytes_val)

留言

© 2024 wd-z711

⬆︎TOP