这个贴主要还是对算法本身结构部分描述会多点,憋问,问就是过去太久了,很多逆向过程不一定能还原(主要是懒,不想原路再走一遍),所以可能有部分跳跃的内容,会给具体代码,但对应的偏移地址和具体信息没有,给大家一个锻炼自己的机会 ( •_•)
继续观看前申明:本文涉及的内容是为给大家提供适合大家巩固基础及进阶更高的技术,别做不好的事情哦。
1、查找java调用jdgs算法位置,frida主动调用获取参数;
2、unidbg模拟算法so执行;
3、枯燥的边调边复现算法;
这部分直接参考其他佬的,挺详细的[原创]某东 APP 逆向分析+ Unidbg 算法模拟 ;
jdgs算法的unidbg模拟执行上面链接里的结果出现以下情况问题解决:
看到java层和so层都对i2出现了不同参数对应不同功能的分支就要打起精神了,需要判断在走i2=101主体算法获取jdgs结果之前是否有走其他流程,明显是的,它执行了i2=103的init初始化部分,你在分析java层调用jdgs native算法的时候会看到init部分,so层分析时也能看到i2值会走不同的分支;
所以需要在unidbg里提前执行一步init:
jdgs算法过程会调用具体的两个资源文件,位置在解压文件夹assets里,后缀是.jdg.jpg和.jdg.xbt,通过unidbg自带的HookZz框架将这两个本地文件先入内存再写入到寄存器里(这部分我不贴代码了,新手可以练练手);
这个问题就是需要你手动去利用unidbg调试算法过程,去查看控制台报红日志代码位置点在哪,追溯为什么会走这个报红日志,去手动修改这些点,这里我就直接贴代码给大家:
这些问题主要还是动手能力的体现,就算天赋异禀,也要老老实实的动手;
最终会看到满意的结果:
首先看下jdgs的结果
通过重复抓包和执行,确定固定值b1(.jdg.xbt文件名)、b2、b3、b7(时间戳,分析过程通过hook固定),
需要分析b4、b5、b6,其实实际走完算法,主要是考验你对标准算法的熟悉程度(ida脚本Findcrypt),因为并没有出现魔改算法,自定义算法也没混淆,难度不大,但详细写篇幅有点大了,适合新手进阶,所以我说下算法具体实现,就不参照ida和unidbg调试过程手摸手复现;
经验之谈,分析算法过程,时间戳一般都是在算法中主要变动参数之一,为了减小分析影响,我们可以选择固定时间戳值:
so直接搜索获取时间戳的常见函数名进行回溯找到时间戳生成位置:
然后通过unidbg的HookZz实现固定
首先传参拼接一串json:e1:参数三eid,e2:参数二finger和一些常量,e3:时间戳
这一串json会进行压缩操作,返回值:comp_json
接下来是获取一块0x100自定义加密数据:buf_sf_0x100
然后将buf_sf_0x100和comp_json进行处理获得新的:comp_json
最后对comp_json进行base64即可获得b4
首先对b1进行计算
然后对参数一的comm_body也进行同样处理,
然后对body_eny 进行md5得到md5_text
然后通过Findcrpy知道md5_text要进行aes加密得到aes_text,key和iv是内存值,不难找
接下来是sha1加密算法,明文comm_body+" "+aes_text
结果就是b5
b6的参数是拼接值
然后和b5相同的算法结构,得到b6
这个算法过程其实非常适合新手进阶,并没有混淆和魔改,但遇到的问题都非常典型,文章的本身也是抱着锻炼的想法写的,不喜勿喷,希望大家可以互相交流,一起进步。
/
/
jdgs初始化
public void doInit()
{
/
/
init
System.out.println(
"=== init begin ===="
);
Object
[] init_param
=
new
Object
[
1
];
Integer init
=
103
;
DvmObject<?> ret
=
module.callStaticJniMethodObject(
emulator,
"main(I[Ljava/lang/Object;)[Ljava/lang/Object;"
,
init,
ProxyDvmObject.createObject(vm, init_param));
System.out.println(
"=== init end ===="
);
}
/
/
jdgs初始化
public void doInit()
{
/
/
init
System.out.println(
"=== init begin ===="
);
Object
[] init_param
=
new
Object
[
1
];
Integer init
=
103
;
DvmObject<?> ret
=
module.callStaticJniMethodObject(
emulator,
"main(I[Ljava/lang/Object;)[Ljava/lang/Object;"
,
init,
ProxyDvmObject.createObject(vm, init_param));
System.out.println(
"=== init end ===="
);
}
/
/
patch C0
53
00
B5, 将反转条件跳转CBZ
-
-
>CBNZ 会报:[main]E
/
JDG: [make_header:
491
] [
-
] Error pp
not
init
byte[] CBNZ
=
new byte[]{(byte)
0xC0
, (byte)
0x53
, (byte)
0x00
, (byte)
0xB5
};
UnidbgPointer p
=
UnidbgPointer.pointer(emulator,dm.getModule().base
+
地址);
p.write(CBNZ);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address)
-
> {
System.out.println(
"====== patch 反转条件跳转CBZ-->CBNZ ======"
);
return
true;
});
/
/
干掉一个free (这个会影响结果) 会报:[main]E
/
JDG: [make_header:
491
] [
-
] Error pp
not
init
byte[] NOP
=
new byte[]{(byte)
0xC1F
, (byte)
0xC20
, (byte)
0xC03
, (byte)
0xD5
};
UnidbgPointer p
=
UnidbgPointer.pointer(emulator,dm.getModule().base
+
地址 );
p.write(NOP);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address)
-
> {
System.out.println(
"======= 干掉一个free ======="
);
return
true;
});
/
/
patch C0
53
00
B5, 将反转条件跳转CBZ
-
-
>CBNZ 会报:[main]E
/
JDG: [make_header:
491
] [
-
] Error pp
not
init
byte[] CBNZ
=
new byte[]{(byte)
0xC0
, (byte)
0x53
, (byte)
0x00
, (byte)
0xB5
};
UnidbgPointer p
=
UnidbgPointer.pointer(emulator,dm.getModule().base
+
地址);
p.write(CBNZ);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address)
-
> {
System.out.println(
"====== patch 反转条件跳转CBZ-->CBNZ ======"
);
return
true;
});
/
/
干掉一个free (这个会影响结果) 会报:[main]E
/
JDG: [make_header:
491
] [
-
] Error pp
not
init
byte[] NOP
=
new byte[]{(byte)
0xC1F
, (byte)
0xC20
, (byte)
0xC03
, (byte)
0xD5
};
UnidbgPointer p
=
UnidbgPointer.pointer(emulator,dm.getModule().base
+
地址 );
p.write(NOP);
MyDbg.addBreakPoint(dm.getModule(), 地址, (emulator, address)
-
> {
System.out.println(
"======= 干掉一个free ======="
);
return
true;
});
{
"b1"
:
".jdg.xbt文件名"
,
"b2"
:
"***"
,
"b3"
:
"***"
,
"b4"
:
"yY8lbpaUOZeQ3fyCiccRrM66O+Nzo/mhwP4wIa8C8JOZ6aJgSdfTJl2a6Q4oeMBx+2P4ySmoN/AtDHutJNGd/lImZaXQkwd00ZyfFGn2PmTk4uorMcnQUrKbmPRHlcKx6iOwmt8RoYf9C7l7bGWQ/COl6HcUT199wCWGjI5+u4mxfvLmiCSqhJ8qbLgVx9KQrRLXW1oDY1sf1RdNl1cYe6GfpF8kwgNMQJif9EIUBw0Td64cduT7MKAFjA3oew02IyWX2aSJaOuWaULTUqO4al9SIyRYojxQCEiMzF5UMxV6Zwu2lw1uZ6+22fJgxbEBv2LeGUpPPzXGF6E2vC0vb9sE5in3CkrKHwM+QfA5CasSPwpAmzQyr5iGyl9o6g=="
,
"b5"
:
"7e640fcb8293d390b3758974b75e9dad5082bed9"
,
"b7"
:
"1724176633106"
,
"b6"
:
"30ed898f8d129b6d16c3f0c49efae07e8de4ee0e"
}
{
"b1"
:
".jdg.xbt文件名"
,
"b2"
:
"***"
,
"b3"
:
"***"
,
"b4"
:
"yY8lbpaUOZeQ3fyCiccRrM66O+Nzo/mhwP4wIa8C8JOZ6aJgSdfTJl2a6Q4oeMBx+2P4ySmoN/AtDHutJNGd/lImZaXQkwd00ZyfFGn2PmTk4uorMcnQUrKbmPRHlcKx6iOwmt8RoYf9C7l7bGWQ/COl6HcUT199wCWGjI5+u4mxfvLmiCSqhJ8qbLgVx9KQrRLXW1oDY1sf1RdNl1cYe6GfpF8kwgNMQJif9EIUBw0Td64cduT7MKAFjA3oew02IyWX2aSJaOuWaULTUqO4al9SIyRYojxQCEiMzF5UMxV6Zwu2lw1uZ6+22fJgxbEBv2LeGUpPPzXGF6E2vC0vb9sE5in3CkrKHwM+QfA5CasSPwpAmzQyr5iGyl9o6g=="
,
"b5"
:
"7e640fcb8293d390b3758974b75e9dad5082bed9"
,
"b7"
:
"1724176633106"
,
"b6"
:
"30ed898f8d129b6d16c3f0c49efae07e8de4ee0e"
}
/
/
固定时间戳 修改获取毫秒时间戳系统函数返回值十六进制
hook.replace(dm.getModule().base
+
地址, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator,
long
originFunction) {
return
super
.onCall(emulator, originFunction);
}
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context,
long
originFunction) {
System.out.println(
"\n=========== HooZz 修改固定时间戳 =========\n"
);
return
super
.onCall(emulator, context, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
long
a
=
(
long
) emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0);
System.out.println(
"修改前时间戳:"
+
Long
.toHexString(a));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W0,
0x18f70ef8d12L
);
System.out.println(
"修改后时间戳:"
+
Long
.toString(
0x18f70ef8d12L
,
16
));
}
},true);
/
/
固定时间戳 修改获取毫秒时间戳系统函数返回值十六进制
hook.replace(dm.getModule().base
+
地址, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator,
long
originFunction) {
return
super
.onCall(emulator, originFunction);
}
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context,
long
originFunction) {
System.out.println(
"\n=========== HooZz 修改固定时间戳 =========\n"
);
return
super
.onCall(emulator, context, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
long
a
=
(
long
) emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0);
System.out.println(
"修改前时间戳:"
+
Long
.toHexString(a));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_W0,
0x18f70ef8d12L
);
System.out.println(
"修改后时间戳:"
+
Long
.toString(
0x18f70ef8d12L
,
16
));
}
},true);
def
fun_compress(
self
, json):
comp_data
=
zlib.compress(json.encode(
'utf-8'
))
return
bytearray(comp_data)
def
fun_compress(
self
, json):
comp_data
=
zlib.compress(json.encode(
'utf-8'
))
return
bytearray(comp_data)
def
fun_sf(
self
, salt):
salt
=
bytearray(salt,
"utf-8"
)
int_list
=
[i
for
i
in
range
(
256
)]
ret_arr
=
bytearray(int_list)
var2
=
0
salt_len
=
len
(salt)
for
i
in
range
(
0x100
):
salt_chunk
=
int
(i
/
salt_len)
ret_i
=
ret_arr[i]
salt_chunk
=
i
-
salt_chunk
*
salt_len
salt_chunk
=
salt[salt_chunk]
var2
=
ret_i
+
var2
var2
=
var2
+
salt_chunk
salt_chunk
=
var2 &
0xff
ret_arr[i]
=
ret_arr[salt_chunk]
ret_arr[salt_chunk]
=
ret_i
return
ret_arr
def
fun_sf(
self
, salt):
salt
=
bytearray(salt,
"utf-8"
)
int_list
=
[i
for
i
in
range
(
256
)]
ret_arr
=
bytearray(int_list)
var2
=
0
salt_len
=
len
(salt)
for
i
in
range
(
0x100
):
salt_chunk
=
int
(i
/
salt_len)
ret_i
=
ret_arr[i]
salt_chunk
=
i
-
salt_chunk
*
salt_len
salt_chunk
=
salt[salt_chunk]
var2
=
ret_i
+
var2
var2
=
var2
+
salt_chunk
salt_chunk
=
var2 &
0xff
ret_arr[i]
=
ret_arr[salt_chunk]
ret_arr[salt_chunk]
=
ret_i
return
ret_arr
def
tool_range0xff(
self
, var):
return
var &
0xff
def
fun_xor(
self
, buf_sf, comp_json):
buf_sf.append(
0
)
buf_sf.append(
0
)
self
.jdgstools.tool_bytearray2str(buf_sf)
comp_json_len
=
len
(comp_json)
i
=
0
while
True
:
comp_json_len
-
=
1
buf_0x100
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x100
])
buf_0x101
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x101
])
buf_0x100_i
=
self
.jdgstools.tool_range0xff(
buf_0x100
+
1
)
buf_sf[
0x100
]
=
buf_0x100_i
buf_0x100_i
=
buf_0x100_i &
0xff
buf_var
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x100_i])
buf_0x101
=
buf_0x101
+
buf_var
buf_sf[
0x101
]
=
self
.jdgstools.tool_range0xff(
buf_0x101)
buf_0x101
=
buf_0x101 &
0xff
buf_var
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x101])
var
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x100_i])
buf_sf[buf_0x100_i]
=
buf_var
buf_sf[buf_0x101]
=
var
buf_0x100_i
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x100
])
buf_0x101
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x101
])
buf_0x100_i
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x100_i])
buf_0x101
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x101])
var_comp_json
=
self
.jdgstools.tool_range0xff(
comp_json[i])
buf_0x100_i
=
buf_0x101
+
buf_0x100_i
buf_0x100_i
=
buf_0x100_i &
0xFF
buf_0x100_i
=
self
.jdgstools.tool_range0xff(
buf_sf[buf_0x100_i])
buf_0x100_i
=
buf_0x100_i ^ var_comp_json
comp_json[i]
=
buf_0x100_i
i
+
=
1
if
comp_json_len
=
=
0
:
break
return
comp_json
def
tool_range0xff(
self
, var):
return
var &
0xff
def
fun_xor(
self
, buf_sf, comp_json):
buf_sf.append(
0
)
buf_sf.append(
0
)
self
.jdgstools.tool_bytearray2str(buf_sf)
comp_json_len
=
len
(comp_json)
i
=
0
while
True
:
comp_json_len
-
=
1
buf_0x100
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x100
])
buf_0x101
=
self
.jdgstools.tool_range0xff(
buf_sf[
0x101
])
buf_0x100_i
=
self
.jdgstools.tool_range0xff(
buf_0x100
+
1
)
buf_sf[
0x100
]
=
buf_0x100_i
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!