mgaic

爱错


  • 首页

  • 标签

  • 分类

  • 归档

某app rsa 算法逆向踩坑记录

发表于 2025-06-26 | 分类于 逆向
抓包

img_11

利用 charles 的 compose 功能 修改一个字符, 发现 403. 但是可以repeat, 但几分钟后 repeat 照样 403, 说明后台校验时间

jadx 全局搜索

img_11

进入 this.b.a 看看

img_11

是个接口, 查看交叉引用.
img_11

第一个很可疑, 我们进入看看

img_11

可以看到, 这个方法就是具体实现.

简单分析了一下, 其中 bytes 就是 时间 + url 路径 + app_type + version
关键函数是 e71.a.a, 我们进入这个函数看看.

img_11

frida hook 看看

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
function getJavaClassName(obj) {
return Java.cast(obj, Java.use("java.lang.Object")).getClass().getName();
}


Java.perform(function () {
var Base64 = Java.use("android.util.Base64");

var TargetClass = Java.use("e71");

TargetClass.a.overload("java.security.PrivateKey", "[B").implementation = function (key22, data) {
console.log("=== Hooked method a(PrivateKey, byte[]) ===");

// 打印 data
var dataHex = hexdump(data);
var dataBase64 = Base64.encodeToString(data, 0);

var StringClass = Java.use("java.lang.String");
var dataStr = StringClass.$new(data, "UTF-8");

console.log("[*] Data (hex):", dataHex);
console.log("[*] Data (base64):", dataBase64);
console.log(111)
console.log("[*] Data (string):", dataStr.toString());
console.log(111)

// 打印 key 信息
console.log("[*] PrivateKey class:", getJavaClassName(key22));

try {
var RSAPrivateKey = Java.use("java.security.interfaces.RSAPrivateKey");
if (RSAPrivateKey.class.isInstance(key22)) {
var castKey = Java.cast(key22, RSAPrivateKey);
var encoded = castKey.getEncoded();
var keyBase64 = Base64.encodeToString(encoded, 0);
console.log("[+] PrivateKey (Base64):", keyBase64);
} else {
console.log("[-] Not an RSAPrivateKey instance.");
}
} catch (e) {
console.log("[-] Exception extracting key:", e);
}

// 调用原函数
var result = this.a(key22, data);

// 打印签名结果
var resultBase64 = Base64.encodeToString(result, 0);
console.log("[+] Signature (base64):", resultBase64);
console.log("=========================================");

return result;
};

function hexdump(bytes) {
return Array.prototype.map.call(Java.array('byte', bytes), function (b) {
return ('00' + (b & 0xff).toString(16)).slice(-2);
}).join(' ');
}
});

注意这里有两个坑, 一个是如何打印 privatekey 的问题, 另外一个是查看类型的问题, 总是 not a function, 我记得之前frida 还可以用

obj.getClass().getName() 来查看一个对象的类型的, 但是这个样本就是 not a function, 然后我降frida-server版本, 从 frida-server 16.6.6,
降为 frida-server 14.2.18, 还是 not a function, 我问了下chatgpt 和 deepseek 是这么解答的

img_11

我们再次打印 privatekey 的类型

PrivateKey class: com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey

然后交给chatgpt 问gpt 这个类型要怎么打印私钥.
结合 gpt 给出的答案, 我们整合一下代码, 最终的代码如下

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
function getJavaClassName(obj) {
return Java.cast(obj, Java.use("java.lang.Object")).getClass().getName();
}


Java.perform(function () {
var Base64 = Java.use("android.util.Base64");
console.log(1222)

// 替换成你的实际类名,比如 com.example.MyClass
var TargetClass = Java.use("e71");

// Hook public final byte[] a(PrivateKey key, byte[] data)
TargetClass.a.overload("java.security.PrivateKey", "[B").implementation = function (key22, data) {
console.log("=== Hooked method a(PrivateKey, byte[]) ===");

// 打印 data
var dataHex = hexdump(data);
var dataBase64 = Base64.encodeToString(data, 0);

var StringClass = Java.use("java.lang.String");
var dataStr = StringClass.$new(data, "UTF-8");

console.log("[*] Data (hex):", dataHex);
console.log("[*] Data (base64):", dataBase64);
console.log(111)
console.log("[*] Data (string):", dataStr.toString());
console.log(111)

// 打印 key 信息
console.log("[*] PrivateKey class:", getJavaClassName(key22));

try {
const Base64 = Java.use("android.util.Base64");
const encoded = Java.cast(key22, Java.use("java.security.Key")).getEncoded();
const keyBase64 = Base64.encodeToString(encoded, 0);
console.log("[+] PrivateKey PKCS#8 (Base64):");
console.log(keyBase64);
} catch (e) {
console.log("[-] Exception dumping key:", e);
}

// 调用原函数
var result = this.a(key22, data);

// 打印签名结果
var resultBase64 = Base64.encodeToString(result, 0);
console.log("[+] Signature (base64):", resultBase64);
console.log("=========================================");

return result;
};

function hexdump(bytes) {
return Array.prototype.map.call(Java.array('byte', bytes), function (b) {
return ('00' + (b & 0xff).toString(16)).slice(-2);
}).join(' ');
}
});

我们再次 frida hook 一下,结果如下
img_11

可以看到 私钥已经出来了, 剩下的就是如何 使用私钥进行 rsa sha256签名了, 下面给出 Java 代码

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
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;


class MyClass {
public static void main(String[] args) throws Exception {
// 假设你已经有 PrivateKey 对象
PrivateKey privateKey = loadPrivateKey(); // 你需要从文件或 Base64 解析

//String data = "hello world";
String data = "1750927494\n/graphql/v2\nXXX-xxxxxxx\n11.31.0\n";
byte[] dataBytes = data.getBytes("UTF-8");

// 创建 SHA256withRSA 签名对象
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(dataBytes);
byte[] signed = signature.sign();

// 输出 Base64 签名结果
String signatureBase64 = Base64.getEncoder().encodeToString(signed);
System.out.println("Signature (Base64): " + signatureBase64);
}

// 示例:从 PKCS#8 Base64 字符串中加载私钥
public static PrivateKey loadPrivateKey() throws Exception {
String base64Key =
"MIIEvAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxx==";

// 用 MIME 解码器自动忽略换行
byte[] keyBytes = Base64.getMimeDecoder().decode(base64Key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
}
}

然后运行 结果如下

img_11

然后用 java 生成的结果, 替换 charles 中的请求的加密参数. 发现可以成功获取到数据, 到此基本就成功了.

如何用 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import base64
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend


def load_private_key():
base64_key = """
MIIEvXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXX=="""

# 移除空白和换行
base64_key = "".join(base64_key.split())

# 解码Base64并加载私钥
key_bytes = base64.b64decode(base64_key)
private_key = serialization.load_der_private_key(
key_bytes,
password=None,
backend=default_backend()
)
return private_key


def main():
# 加载私钥
private_key = load_private_key()

# 准备要签名的数据
data = "1750929449\n/graphql/v2\nXXXX-XXXXX\n11.31.0\n"
data_bytes = data.encode('utf-8')

# 使用SHA256withRSA进行签名
signature = private_key.sign(
data_bytes,
padding.PKCS1v15(),
hashes.SHA256()
)

# 输出Base64编码的签名
signature_base64 = base64.b64encode(signature).decode('utf-8')
print("Signature (Base64):", signature_base64)


if __name__ == "__main__":
main()

python 需要安装 cryptography

1
$ python3 -m pip install cryptography -i https://pypi.tuna.tsinghua.edu.cn/simple

通过私钥 进行签名, 使用的是 SHA256withRSA

改为 python 后,同样也可以生成加密结果 并且可以通过校验.

总结
  • 遇到的核心问题, getClass().getName() 会报错 not a function, 可能是由于android 高版本的优化问题. 并不是 frida-server 和 frida 以及 frida-tools 的问题.
  • privateKey 如何打印的问题, 上面已经给出答案. 还有些是通过 keyStore 这种, 这种导出就很麻烦.
  • python3.8 安装 frida 14.2.18 会遇到 frida-14.2.18-py3.8-linux-x86_64.egg 问题, 直接下载 放到指定位置就好.
  • 关于 RSA private, 或者 public key 也可以考虑 hook base64 的 decode 方法, 一般公钥要base64 解码后然后加载字节数据.
收获

在分析这个样本的过程中, 想借助一些文章, 发现了一些站点, 貌似可以低成本下载文档。

1
2
http://toolman.dedyn.io:9192/#/main/csdnPaper
https://github.com/bigintpro/csdn_downloader

充值了一块钱, 结果是个垃圾文章, 骗加公众号的, 无语子.

另外记录一下本人使用的 frida稳定的版本

1
2
3
4
5
6
7
8
9
10
11
12
pip install frida==14.2.18
pip install frida-tools==9.2.5
pip install objection==1.11.0

# 下面是本人使用到的 objection 的脚本, 这里记录一下
objection -g com.xxxxxxx.android explore -P ~/.objection/plugins
plugin wallbreaker objectsearch com.xxxxxxx.android.internal.auth.signing.util.RSASigner
plugin wallbreaker objectdump 0x4142 --fullname


# http://littleshark.space/2024/08/25/Frida%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Frida%E4%BD%BF%E7%94%A8%E5%A4%A7%E5%85%A8/
# https://bbs.kanxue.com/thread-277929.htm

记某app逆向分析

发表于 2025-05-14 | 分类于 逆向

最近热爱健身, 用了一款app, 想分析一下这款app

今天是2025年五月, 我下载了一个历史版本. 大概是 2024年12月的一个版本. 打开app, 提示要更新, 并且是强制更新.

使用 frida hook 看看, nop掉弹窗
1
2
3
4
5
6
7
8
Java.perform(function () {
var Dialog = Java.use("android.app.Dialog");
Dialog.show.implementation = function () {
console.log("[*] 拦截 Dialog.show(),不显示强制更新弹窗");
// 不调用 this.show() 即可屏蔽
return;
};
});
执行完后 黑屏, 有的时候执行完后 确实没有弹窗了, 但是app 其他功能无法使用. 无奈只好换新版本.

这次找到了一个较新的版本. 结果打开后就显示 ‘检测到该应用在hook环境中运行’, 然后程序就退出了.

此时我想 反编译一下 这个app,然后直接nop 掉检测. 于是我 jadx 打开app, 然后我看到了 com.netease.nis.wrapper, 这一款就是加了壳, 网易易盾.

于是我打算脱壳, 但是程序一打开, 不到一秒就直接退出了, 还没来得及暴力搜索 dex呢.

这可怎么办啊, 我想这换一种脱壳方式把?

等等, 过root 检测, 可以试试 Magisk + Shamiko。 我按照操作试了一下, 果然ok了, 程序不会再退出了.

贴上链接

Magisk + Shamiko 过root检测

按照教程 做到 ‘4、开启Shamiko模块’ 这一步 就可以过root检测了

为了防止失效, 我将网页缓存了一份
magisk_root.html

然后我用自己的手机号登录, 发现登录失败, 只能接收语音验证码 继续登录, 但是我的手机并没有收到语音验证.

所以我找了一个 小X分身工具, 结果刚打开, app又退出了, 提示 “检测到在双开app中运行”

我怀疑语音验证的包是不是真的发出去了, 否则我怎么接收不到语音验证呢? 我们抓包看看.

img_11

可能是有 ssl panning

我们用 lsposed 下装个 just trust me 看看.

结果app闪退, 提示 当前app在hook环境中运行.

emmm 还有什么方法能快速分析呢, 试试看 frida 把.
frida hook 结果闪退.

试试看 Strong frida. 结果也是闪退. frida hook 看看是加载了哪个so。然后程序退出的.

1
2
3
4
5
6
7
8
9
10
11
12
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext != null){
Interceptor.attach(android_dlopen_ext,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
},
onLeave: function(retval){
Thread.sleep(0.3);
}
});
}

原来罪魁祸首在这里. 这里更加确定是网易的盾了.
img_11

应该是这个so, 检测frida 然后就退出了. 一般 native层都是新开一个线程去检测的. 我们hook libc 的 pthread_create 的方法看看.

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
var soaddr = null;
function hook_dlopen(soName = '') {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];

if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) != -1) {

this.hook = true;

}
console.log(path);

}
},
onLeave:function(ret){
if (this.hook = true) {

soaddr = Module.findBaseAddress("libnesec.so");
hook_pthread_create();
}
}
}
);
}
function printNativeStack(context, name) {
var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
console.log(trace)
}
function hook_pthread_create() {

Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
onEnter(args) {
var func_addr = args[2]

var offes = func_addr.sub(soaddr);
console.log("The thread function address is " + offes);



}
})
}
setImmediate(hook_dlopen,"libnesec.so");

可以看到, 加载了 libnesec.so 文件后, 这些 pthread_create 依此被调用
img_11

这些线程都有可能是检测frida的线程. 我们从后往前一个个试, patch pthread_create 方法.
我的运气很不好 试到最后一个, 他才是 检测frida 的线程

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
var soaddr = null;
function hook_dlopen(soName = '') {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];

if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) != -1) {

this.hook = true;

}
console.log(path);

}
},
onLeave:function(ret){
if (this.hook = true) {

soaddr = Module.findBaseAddress("libnesec.so");
hook_pthread_create();
}
}
}
);
}
function printNativeStack(context, name) {
var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
console.log(trace)
}
function hook_pthread_create() {

Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
onEnter(args) {
var func_addr = args[2]
// console.log("sub soaddr", soaddr)
var offes = func_addr.sub(soaddr);
console.log("The thread function address is " + offes);

if (offes == 0xa08d8) {


Interceptor.replace(func_addr,new NativeCallback(function(){
console.log("0x82fac replaces");
},'void',[]));

}



}
})
}
setImmediate(hook_dlopen,"libnesec.so");

ok 现在可以看到 frida没有退出了。

到这里, 发现app还是无法抓包. 我们 adb logcat 看看

1
$ adb logcat | grep 20667 # 过滤 20667 进程的输出.

打印如下

img_11

我们把这一段 copy给 chatgpt, 看看它能否帮我们生成frida hook 代码.

img_11

我们就用这段代码试试, 把这段代码插入到 patch thread_create 之后

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

var soaddr = null;
function hook_dlopen(soName = '') {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];

if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) != -1) {

this.hook = true;

}
console.log(path);

}
},
onLeave:function(ret){
if (this.hook = true) {

soaddr = Module.findBaseAddress("libnesec.so");
hook_pthread_create();
}
}
}
);
}
function printNativeStack(context, name) {
var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
console.log(trace)
}
function hook_pthread_create() {

Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
onEnter(args) {
var func_addr = args[2]
// console.log("sub soaddr", soaddr)
var offes = func_addr.sub(soaddr);
// console.log("The thread function address is " + offes);

if (offes == 0xa08d8) {


Interceptor.replace(func_addr,new NativeCallback(function(){
console.log("0x82fac replaces");
},'void',[]));


Java.perform(function () {
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var SSLContext = Java.use('javax.net.ssl.SSLContext');

// 创建一个空的 TrustManager
var TrustManager = Java.registerClass({
name: 'com.example.MyTrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {},
checkServerTrusted: function (chain, authType) {},
getAcceptedIssuers: function () { return []; }
}
});

var TrustManagers = [TrustManager.$new()];
var SSLContextInit = SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');

SSLContextInit.implementation = function (keyManager, trustManager, secureRandom) {
console.log('[+] Overriding SSLContext.init() with custom TrustManager');
SSLContextInit.call(this, keyManager, TrustManagers, secureRandom);
};
});

}



}
})
}
setImmediate(hook_dlopen,"libnesec.so");

然后我们 执行

1
$ frida -U -f com.xxx.xxxx -l hook.js

发现charles 可以正常抓包了.

想脱壳试试, 反正目前frida 也可以正常使用了.

试了 hook openMemery 似乎失败了, android 10 下签名变了.

1
2
3
4
https://github.com/hluwa/frida-dexdump
frida-dexdump -FU

成功脱壳.
补充 最近遇到了一个 360的壳. 没有anti-frida

先使用 frida-dexdump 脱壳.

1
frida-dexdump -FU

部分报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ERROR:frida-dexdump:[-] Error: access violation accessing 0x7b6f5e7000
at <anonymous> (frida/runtime/core.js:145)
at memorydump (src/search.ts:41)
at call (native)
at <anonymous> (frida/runtime/message-dispatcher.js:11)
at o (frida/runtime/message-dispatcher.js:23): {'addr': '0x7b6f4241e0', 'size': 6738564}
Traceback (most recent call last):
File "/home/chilly/zhipu/aminer_crawler/aminer_env/lib/python3.8/site-packages/frida_dexdump/__main__.py", line 81, in dump
bs = self.agent.memory_dump(dex['addr'], dex['size'])
File "/home/chilly/zhipu/aminer_crawler/aminer_env/lib/python3.8/site-packages/frida_dexdump/agent/__init__.py", line 24, in memory_dump
return self._rpc.memorydump(base, size)
File "/home/chilly/zhipu/aminer_crawler/aminer_env/lib/python3.8/site-packages/frida/core.py", line 180, in method
return script._rpc_request(request, data, **kwargs)
File "/home/chilly/zhipu/aminer_crawler/aminer_env/lib/python3.8/site-packages/frida/core.py", line 86, in wrapper
return f(*args, **kwargs)
File "/home/chilly/zhipu/aminer_crawler/aminer_env/lib/python3.8/site-packages/frida/core.py", line 497, in _rpc_request
raise result.error

jadx 打开发现, 只脱下了一些非源代码的三方包.

接着试试 FRIDA-DEXDump

保证apk 运行, 然后执行如下脚本

1
python3 main.py

发现报错如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[Except] - Error: access violation accessing 0x7b6f5dc000
at <anonymous> (frida/runtime/core.js:145)
at memorydump (/script1.js:110)
at call (native)
at <anonymous> (frida/runtime/message-dispatcher.js:11)
at o (frida/runtime/message-dispatcher.js:23): {'addr': '0x7b6f4243a0', 'size': 6738564}
[Except] - Error: access violation accessing 0x7b6f5dc000
at <anonymous> (frida/runtime/core.js:145)
at memorydump (/script1.js:110)
at call (native)
at <anonymous> (frida/runtime/message-dispatcher.js:11)
at o (frida/runtime/message-dispatcher.js:23): {'addr': '0x7b6f424bf0', 'size': 7375508}
[Except] - Error: access violation accessing 0x7b6f5dc000
at <anonymous> (frida/runtime/core.js:145)
at memorydump (/script1.js:110)
at call (native)
at <anonymous> (frida/runtime/message-dispatcher.js:11)
at o (frida/runtime/message-dispatcher.js:23): {'addr': '0x7b6f424c60', 'size': 4650360}

跟 frida-dexdump 效果差不多.

最后我们试试 frida_dump

1
$ frida -U -f com.xxxxxxx  -l dump_dex.js

jadx 打开后发现成功脱壳.

记一次aes dfa 逆向, unidbg 注入故障

发表于 2025-05-08 | 分类于 逆向

接着上篇, 上篇使用的是 frida 注入故障, 我们这篇使用 unidbg 来注入故障

先补环境, 给出完整代码, 很简单

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
package com.aes;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.SystemService;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import javax.crypto.Mac;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
//import com.github.unidbg.pointer.Pointer;


public class Signer extends AbstractJni implements IOResolver {
@Override
public FileResult resolve(Emulator emulator, String pathname, int oflags) {
System.out.println("file open:"+pathname);
return null;
}

private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmObject NativeLibHelper;

Signer() {
// 创建模拟器实例
emulator = AndroidEmulatorBuilder
.for64Bit() // 我选择分析 ARM64 的 SO
.setProcessName("com.example.aes") // 传入进程名
.build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\aes\\aes.apk"));


vm.setJni(this); // 设置JNI
vm.setVerbose(true); // 打印日志
emulator.getSyscallHandler().addIOResolver(this);// 设置文件处理器
DalvikModule dm = vm.loadLibrary("aes", true); // 加载 libsigner.so,Unidbg 会到 apk 的 lib/arm64-v8a 下寻找。
module = dm.getModule(); //获取目标模块的句柄

dm.callJNI_OnLoad(emulator); // 调用目标 SO 的 JNI_OnLoad
// 构造调用目标函数的对象
NativeLibHelper = vm.resolveClass("com.example.aes.MainActivity").newObject(null);


}

public static void main(String[] args) {
Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);
Signer signer = new Signer();
signer.callNsign();
}
public void callNsign(){
// arg1
DvmObject context = vm.resolveClass("com.example.aes.Application", vm.resolveClass("android/content/Context")).newObject(null);
// arg2
ByteArray barr2 = new ByteArray(vm, "1111222233334444".getBytes());
// arg3
ByteArray barr3 = new ByteArray(vm, "1234567890123456".getBytes());
// arg4
String ret = (String) (NativeLibHelper.callJniMethodObject(emulator, "openssl_encrypt", barr2, barr3).getValue());
System.out.println("ret " + ret);
// Inspector.inspect(ret, "result");




// List<Object> list = new ArrayList<>(10);
// // arg1 env
// list.add(vm.getJNIEnv());
// // arg2 jobject/jclazz 一般用不到,直接填0liboasiscore.so
// list.add(0);
//
// DvmObject<?> p2 = vm.resolveClass("com.example.aes.Application", vm.resolveClass("android/content/Context")).newObject(null);
//
// byte[] p3 = "1111222233334444".getBytes();
// byte[] p4 = "1234567890123456".getBytes();
//
//
//// list.add(vm.addLocalObject(p2));
// list.add(vm.addLocalObject(new ByteArray(vm, p3)));
// list.add(vm.addLocalObject(new ByteArray(vm, p4)));
//
// Number number = module.callFunction(emulator, 0x207D8, list.toArray());
// System.out.println(number);
//// Number number = module.callFunction(emulator, 0xef7c, list.toArray())[0];
//
// System.out.println(vm.getObject(number.intValue()).getValue());

}

public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}

注意哦, 静态注册的函数, 不需要context 这个函数, 否则会报错

1
java.lang.ClassCastException: class com.github.unidbg.linux.android.dvm.DvmObject cannot be cast to class com.github.unidbg.linux.android.dvm.array.ByteArray 

我们来看看运行结果.

img_12

可以看到结果已经出来了

我们先hook 列混淆函数看看, 我们使用的是 arm64 的so文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void hook20D2C(){
// 加载HookZz
IHookZz hookZz = HookZz.getInstance(emulator);

hookZz.wrap(module.base + 0x20D2C, new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
// 类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// 类似于Frida args[0]
System.out.println(ctx.getXLong(0));
System.out.println(ctx.getXLong(1));
System.out.println(ctx.getXLong(2));
};

@Override
// 类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
}
});
}

看看结果

img_13

可以看到 hook 成功了

我们尝试打印一下入参 state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void hook20D2C(){
// 加载HookZz
IHookZz hookZz = HookZz.getInstance(emulator);

hookZz.wrap(module.base + 0x20D2C, new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
// 类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// 类似于Frida args[0]
Inspector.inspect(ctx.getXPointer(0).getByteArray(0, 0x10), "Arg1");

// System.out.println(ctx.getXLong(0));
// Inspector.inspect(ctx.getPointerArg(0), "result");
// System.out.println(ctx.getXLong(1));
// System.out.println(ctx.getXLong(2));
};

@Override
// 类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
}
});
}

看看结果

img_14

那如何patch 呢,在最后一次列混淆的时候,能不能hook 的时候就patch 呢?
答曰: 可以!

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
public static int count = 0;
public void hook20D2C(){
// 加载HookZz
IHookZz hookZz = HookZz.getInstance(emulator);

hookZz.wrap(module.base + 0x20D2C, new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
// 类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// 类似于Frida args[0]
count += 1;
// Inspector.inspect(ctx.getXPointer(0).getByteArray(0, 0x10), "Arg1");


if(count % 9 == 0){
// ctx.getXPointer(0).setByte(randInt(0,16), (byte) randInt(0, 255));
ctx.getXPointer(0).setByte(0, (byte) 2);
}
};

@Override
// 类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
}
});
}

我们来跑一下, 看看将第一个字节改为 2 后, 结果变成了什么

img_15

可以看到 结果变成了 2ee457698cd1c4cc0da65a8591ba75bc

再来看看正常运行 不使用dfa 攻击, 正常的 AES ECB 的值是多少?
答曰: d4e457698cd1c4930da6cb8591f075bc, 可以看到 我们成功了,结果差几个字节而已.

我们来批量注入.

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);
Signer signer = new Signer();
signer.hook20D2C();
for(int i=0; i<200; i++){
signer.callNsign();
}
}

运行看看

img_16

可以看到 多次注入结果已经打印出来了, 把日志copy 到文件中, 写个脚本批量匹配 ret, 或者改代码, 把结果保存到文件中. 我们选择后者.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void callNsign(){
// arg1
DvmObject context = vm.resolveClass("com.example.aes.Application", vm.resolveClass("android/content/Context")).newObject(null);
// arg2
ByteArray barr2 = new ByteArray(vm, "1111222233334444".getBytes());
// arg3
ByteArray barr3 = new ByteArray(vm, "1234567890123456".getBytes());
// arg4
String ret = (String) (NativeLibHelper.callJniMethodObject(emulator, "openssl_encrypt", barr2, barr3).getValue());
System.out.println("ret " + ret);
String path = "output.txt";
try {
Files.write(Paths.get(path), (ret + "\n").getBytes() , StandardOpenOption.CREATE, StandardOpenOption.APPEND);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

然后我们在文件首行加入正确的密文. d4e457698cd1c4930da6cb8591f075bc

交给 phoenixAES 执行看看

1
2
3
4
5
import phoenixAES

# phoenixAES.crack_file('aes_128_dfa.txt', [], True, False, 3) # crack_file的第三个参数传入False,代表这是一个解密过程
# phoenixAES.crack_file('aes_dfa_result.txt', [], True, False, 3) # crack_file的第三个参数传入False,代表这是一个解密过程
phoenixAES.crack_file('output.txt', [], True, False, 3) # crack_file的第三个参数传入False,代表这是一个解密过程

img_17

呃, 好像结果并没有出来, why????

是因为太少了吗? 我们注入500次试试

img_18

果然 200次太少, 一辈子太长, 只求天长地久.

ok, AES ECB 已知某一轮密钥, 怎么推出其他密钥,甚至初始密钥?

答曰: Stark.

img_19

cyberchef 验证看看

img_20

yyds!!

记一次aes dfa 逆向,frida 注入故障

发表于 2025-05-08 | 分类于 逆向

分析

img_2

可以看到符号并没有抹除.

我们进入 AES_ECB_encrypt 看看

img_3

可以看到 sub_20B14 被调用了三次, 这显然是 轮密钥加 的操作.
第一轮 与 key0 做异或
第2到10轮 与 key1-9 做异或
第11轮 与 key10 做异或

进入到 sub_20BAC, 看看实现

img_4

再看看 byte_16CD5

img_5

这明显是一个查表的操作, 而且 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F 明显是AES 的 S盒, 专用于字节替换.

再来看看 sub_20C3C 的实现.

img_6

这看起来似乎是循环左移的操作.

那 sub_20D2C 大概率是 列混淆的操作了.

a1 大概率是 state.

我们试试在倒数第二轮修改state 的某个值.

img_7

可以看到 i== 10 就退出了, 我们期望 i==9 时候, 修改 state 的值.

这里介绍一个ida插件, 可以生成 frida 代码

ida 插件 gen_frida

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function hookwbShiftRows(){
var count = 0;
var base_addr = Module.findBaseAddress("libaes.so");
//var real_addr = base_addr.add(0x20C3C)
var real_addr = base_addr.add(0x20D2C)
Interceptor.attach(real_addr, {
onEnter: function (args) {
count += 1;
if(count%9 === 0){
//console.log(hexdump(args[0]))
args[0].add(randomNum(0,15)).writeS8(randomNum(0, 0xff));
//args[0].add(randomNum(0,15)).writeU8(randomNum(0, 0xff));
}
}
});
}

函数名没来得及改, hook循环左移, 但不影响, 我们实际hook的是列混淆函数

一共是九次列混淆, 最后一轮计算中并没有列混淆的操作. 所有当 i==9 时, 注入一个字节. 在哪里注入? 在arg[0] + offset 的位置注入无符号字节, 0-255之间

我们怎么来主动 call 呢?

答曰 : 从java 层或者 native层主动call
我这里从native层没有找到好的时机, 因为 aes的第一个入参是 &ctx, 这是个结构体. 所以我尝试从 java层多次call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java.perform(function () {
// 替换为你实际的类名
var TargetClass = Java.use("com.example.aes.MainActivity");

// 构造 byte[] 参数
function stringToByteArray(str) {
var bytes = Java.array('byte', Array.from(str, c => c.charCodeAt(0)));
return bytes;
}

var data = stringToByteArray("1111222233334444");
var key = stringToByteArray("1234567890123456");

for(var i=0;i<100;i++){
var result = TargetClass.openssl_encrypt(data, key);
// console.log("Encrypted result: " + result);
console.log(result);
}

});

下面给出完整的 dfa 注入错误的代码

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

function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}

function randomNum(minNum,maxNum){
if (arguments.length === 1) {
return parseInt(Math.random() * minNum + 1, 10);
} else if (arguments.length === 2) {
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
} else {
return 0;
}
}

function hookwbShiftRows(){
var count = 0;
var base_addr = Module.findBaseAddress("libaes.so");
//var real_addr = base_addr.add(0x20C3C)
var real_addr = base_addr.add(0x20D2C)
Interceptor.attach(real_addr, {
onEnter: function (args) {
count += 1;
if(count%9 === 0){
//console.log(hexdump(args[0]))
args[0].add(randomNum(0,15)).writeS8(randomNum(0, 0xff));
//args[0].add(randomNum(0,15)).writeU8(randomNum(0, 0xff));
}
}
});
}


function hook_0x491A0() {
let base_addr = Module.findBaseAddress("libaes.so");

Interceptor.attach(base_addr.add(0x20964), {
onEnter(args) {
console.log(`call 0x491A0 arg0:${args[0]} arg1:${args[1]}`);
}
});
}



function bufferToHex (buffer) {
return [...new Uint8Array (buffer)]
.map (b => b.toString (16).padStart (2, "0"))
.join ("");
}

function dfa(){


hookwbShiftRows();
Java.perform(function () {
// 替换为你实际的类名
var TargetClass = Java.use("com.example.aes.MainActivity");

// 构造 byte[] 参数
function stringToByteArray(str) {
var bytes = Java.array('byte', Array.from(str, c => c.charCodeAt(0)));
return bytes;
}

var data = stringToByteArray("1111222233334444");
var key = stringToByteArray("1234567890123456");

for(var i=0;i<100;i++){
var result = TargetClass.openssl_encrypt(data, key);
// console.log("Encrypted result: " + result);
console.log(result);
}

});

}

然后执行 frida 注入

img_8

将结果复制到文件 aes_dfa_result.txt 中, 第一行添加正确的加密的结果.

1
2
3
4
import phoenixAES

# phoenixAES.crack_file('aes_128_dfa.txt', [], True, False, 3) # crack_file的第三个参数传入False,代表这是一个解密过程
phoenixAES.crack_file('aes_dfa_result.txt', [], True, False, 3) # crack_file的第三个参数传入False,代表这是一个解密过程

跑一下 看看结果

img_9

可以看到 最后一轮也就是第11轮的密钥已经出来了, 197DD69D902A88D60447D21405FC374C

已知AES ECB 的某一轮密钥, 可以推导出其他任意一轮密钥,(共11轮密钥), 包括初始密钥, 也可以推导出.

我们使用 Stark 工具来推导出任意一轮密钥

1
2
$ git clone https://github.com/SideChannelMarvels/Stark.git
$ make -j8
1
2
# 从0开始计数. 10代表第11轮
./aes_keyschedule 197DD69D902A88D60447D21405FC374C 10

看看结果

img_10

cyberchef 看看

img_11

可以看到 AES ECB 的密钥已经出来了. 算法并没有魔改

而且这个样本使用的就是标准的 tiny-AES-c 的算法
感兴趣可以自己研究研究

1
git clone https://github.com/kokke/tiny-AES-c.git

案例

点我下载 案例.apk

源码下载

点我下载 apk源码

聊聊frida 检测

发表于 2025-05-06 | 分类于 逆向

常见检测点

检测 gmain 和 gum-js-loop

frida 在注入时, 会在目标进程下创建两个新线程,名为 gmain 和 gum-js-loop.
通过检测线程名,可以判断当前进程是否被frida注入. 具体实现方法 可以通过判断 /proc/self/task/tid/status 文件, status文件的第一行是线程名.
遍历所有的线程名, 如果发现 gmain 和 gum-js-loop, 则可认定该进程被注入.
下面给出检测代码

检测 gmain 和 gum-js-loop
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
static const char *APPNAME = "DetectFrida";
static const char *FRIDA_THREAD_GUM_JS_LOOP = "gum-js-loop";
static const char *FRIDA_THREAD_GMAIN = "gmain";
static const char *FRIDA_NAMEDPIPE_LINJECTOR = "linjector";
static const char *PROC_MAPS = "/proc/self/maps";
static const char *PROC_STATUS = "/proc/self/task/%s/status";
static const char *PROC_FD = "/proc/self/fd";
static const char *PROC_TASK = "/proc/self/task";

__attribute__((always_inline))
static inline void detect_frida_threads() {

DIR *dir = opendir(PROC_TASK);

if (dir != NULL) {
struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
char filePath[MAX_LENGTH] = "";

if (0 == my_strcmp(entry->d_name, ".") || 0 == my_strcmp(entry->d_name, "..")) {
continue;
}
snprintf(filePath, sizeof(filePath), PROC_STATUS, entry->d_name);

int fd = my_openat(AT_FDCWD, filePath, O_RDONLY | O_CLOEXEC, 0);
if (fd != 0) {
char buf[MAX_LENGTH] = "";
read_one_line(fd, buf, MAX_LENGTH);
if (my_strstr(buf, FRIDA_THREAD_GUM_JS_LOOP) ||
my_strstr(buf, FRIDA_THREAD_GMAIN)) {
//Kill the thread. This freezes the app. Check if it is an anticpated behaviour
//int tid = my_atoi(entry->d_name);
//int ret = my_tgkill(getpid(), tid, SIGSTOP);
__android_log_print(ANDROID_LOG_WARN, APPNAME,
"Frida specific thread found. Act now!!!");
}
my_close(fd);
}

}
closedir(dir);

}

}

检测 frida 特征命名管道 “linjector”

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

static const char *APPNAME = "DetectFrida";
static const char *FRIDA_THREAD_GUM_JS_LOOP = "gum-js-loop";
static const char *FRIDA_THREAD_GMAIN = "gmain";
static const char *FRIDA_NAMEDPIPE_LINJECTOR = "linjector";
static const char *PROC_MAPS = "/proc/self/maps";
static const char *PROC_STATUS = "/proc/self/task/%s/status";
static const char *PROC_FD = "/proc/self/fd";
static const char *PROC_TASK = "/proc/self/task";

__attribute__((always_inline))
static inline void detect_frida_namedpipe() {

DIR *dir = opendir(PROC_FD);
if (dir != NULL) {
struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
struct stat filestat;
char buf[MAX_LENGTH] = "";
char filePath[MAX_LENGTH] = "";
snprintf(filePath, sizeof(filePath), "/proc/self/fd/%s", entry->d_name);

lstat(filePath, &filestat);

if ((filestat.st_mode & S_IFMT) == S_IFLNK) {
//TODO: Another way is to check if filepath belongs to a path not related to system or the app
my_readlinkat(AT_FDCWD, filePath, buf, MAX_LENGTH);
if (NULL != my_strstr(buf, FRIDA_NAMEDPIPE_LINJECTOR)) {
__android_log_print(ANDROID_LOG_WARN, APPNAME,
"Frida specific named pipe found. Act now!!!");
}
}

}
}
closedir(dir);
}

对比内存中的可执行段(.text 段等)与磁盘上的 ELF 文件内容,识别是否被 Frida 等工具篡改注入

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
//Structure to hold the details of executable section of library
typedef struct stExecSection {
int execSectionCount;
unsigned long offset[2];
unsigned long memsize[2];
unsigned long checksum[2];
unsigned long startAddrinMem;
} execSection;


#define NUM_LIBS 2

//Include more libs as per your need, but beware of the performance bottleneck especially
//when the size of the libraries are > few MBs
static const char *libstocheck[NUM_LIBS] = {"libnative-lib.so", LIBC};
static execSection *elfSectionArr[NUM_LIBS] = {NULL};

__attribute__((always_inline))
static inline bool
scan_executable_segments(char *map, execSection *pElfSectArr, const char *libraryName) {
unsigned long start, end;
char buf[MAX_LINE] = "";
char path[MAX_LENGTH] = "";
char tmp[100] = "";

sscanf(map, "%lx-%lx %s %s %s %s %s", &start, &end, buf, tmp, tmp, tmp, path);
//__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Map [%s]", map);

if (buf[2] == 'x') {
if (buf[0] == 'r') {
uint8_t *buffer = NULL;

buffer = (uint8_t *) start;
for (int i = 0; i < pElfSectArr->execSectionCount; i++) {
if (start + pElfSectArr->offset[i] + pElfSectArr->memsize[i] > end) {
if (pElfSectArr->startAddrinMem != 0) {
buffer = (uint8_t *) pElfSectArr->startAddrinMem;
pElfSectArr->startAddrinMem = 0;
break;
}
}
}
for (int i = 0; i < pElfSectArr->execSectionCount; i++) {
unsigned long output = checksum(buffer + pElfSectArr->offset[i],
pElfSectArr->memsize[i]);
// __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Checksum:[%ld][%ld]", output,
// pElfSectArr->checksum[i]);

if (output != pElfSectArr->checksum[i]) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME,
"Executable Section Manipulated, "
"maybe due to Frida or other hooking framework."
"Act Now!!!");
}
}

} else {

char ch[10] = "", ch1[10] = "";
__system_property_get("ro.build.version.release", ch);
__system_property_get("ro.system.build.version.release", ch1);
int version = my_atoi(ch);
int version1 = my_atoi(ch1);
if (version < 10 || version1 < 10) {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Suspicious to get XOM in "
"version < Android10");
} else {
if (0 == my_strncmp(libraryName, LIBC, my_strlen(LIBC))) {
//If it is not readable, then most likely it is not manipulated by Frida
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "LIBC Executable Section"
" not readable! ");

} else {
__android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Suspicious to get XOM "
"for non-system library on "
"Android 10 and above");
}
}
}
return true;
} else {
if (buf[0] == 'r') {
pElfSectArr->startAddrinMem = start;
}
}
return false;
}


__attribute__((always_inline))
static inline void detect_frida_memdiskcompare() {
int fd = 0;
char map[MAX_LINE];

if ((fd = my_openat(AT_FDCWD, PROC_MAPS, O_RDONLY | O_CLOEXEC, 0)) != 0) {

while ((read_one_line(fd, map, MAX_LINE)) > 0) {
for (int i = 0; i < NUM_LIBS; i++) {
if (my_strstr(map, libstocheck[i]) != NULL) {
if (true == scan_executable_segments(map, elfSectionArr[i], libstocheck[i])) {
break;
}
}
}
}
} else {
__android_log_print(ANDROID_LOG_WARN, APPNAME,
"Error opening /proc/self/maps. That's usually a bad sign.");

}
my_close(fd);

}

来看看一些绕过 frida检测的项目 strong-frida

strong-frida

这里只patch 了针对 gmain 和 loop-js 还有命名管道的检测.

这里放一个 frida check 的apk, 可自行验证

strong-frida

点我下载 Frida_check.apk

这里延申一种定位加密位置的办法, 很好用

试试看 hook StringBuilder StringBuffer 的 toString , 然后打印调用堆栈.

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
Java.perform(function x() {
//定位StringBuilder,StringBuffer类
const stringbuilder = Java.use("java.lang.StringBuilder");
const stringbuffer = Java.use("java.lang.StringBuffer");

//定位方法
const toString = "toString";


// 使用log类和Exception类产生堆栈
var jAndroidLog = Java.use("android.util.Log");
var jException = Java.use("java.lang.Exception");

stringbuilder[toString].implementation = function () {
//执行原逻辑
const result = this[toString]();

if (result.indexOf("aaaaa") != -1) {
// 打印返回的字符串内容
console.log(result);
console.log(jAndroidLog.getStackTraceString(jException.$new()));
}
return result;
};

stringbuffer[toString].implementation = function () {
//执行原逻辑
const result = this[toString]();
if (result.indexOf("aaaaa") != -1) {
// 打印返回的字符串内容
console.log(result);
console.log(jAndroidLog.getStackTraceString(jException.$new()));
}
return result;
}

});

样例分析

如果一个样本存在 anti-frida, 要怎么定位关键监测点呢
先试试看 hook 库的加载函数(在 Android 较低版本比如 6.0 上是dlopen,较高版本上是android_dlopen_ext)

1
2
3
4
5
6
7
8
9
10
11
12
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext != null){
Interceptor.attach(android_dlopen_ext,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
},
onLeave: function(retval){
Thread.sleep(5);
}
});
}

每次加载完一个so文件后, 就休眠一段时间, 如果程序退出了, 那么凶手可能就是最后一个加载的so文件.

针对 libmsaoaidsec.so 的分析, 可以看看之前发过的文章, 使用 stackplz 分析 libmsaoaidsec.so, 很方便.

其实也可以简单绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hookDlopen() {
var p_android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if (p_android_dlopen_ext != null) {
// void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo)
var android_dlopen_ext = new NativeFunction(p_android_dlopen_ext, "pointer", ["pointer", "int", "pointer"]);

Interceptor.replace(p_android_dlopen_ext, new NativeCallback(function (filename, flag, extinfo) {
if (filename.readCString().indexOf("libmsaoaidsec.so") != -1) {
return ptr(-1);
} else {
return android_dlopen_ext(filename, flag, extinfo);
}

}, "pointer", ["pointer", "int", "pointer"]));
}
}

hookDlopen();

或者 明明要加载 libmsaoaidsec.so,让程序再加载一个空 或者加载一个已经加载过的so文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hook_dlopen(soName = '') {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if(path.indexOf('libmsaoaidsec.so') >= 0){
ptr(pathptr).writeUtf8String("");
}
console.log('path: ',path)
}
}
});
}
hook_dlopen()

2025 五一假期记录

发表于 2025-05-06 | 分类于 生活日常

路上偶遇

目前在北京工作, 五一假期回了趟老家荆门, 买的是从哈尔滨到武昌的T184车票,快到武汉的时候碰到一个特别阳光活泼爱笑的女生,很可爱, 多次要上次去招呼,但最后没有勇气,最终错付.
太久没有这样紧张的感觉了,甚至感觉到胃部都有些痛, 上次有这种感觉可能还是高二找学妹去要qq, 她们一行人应该还是学生, 去武昌旅游, 祝她们玩的开心

活动

约了高中同学游泳, 下一步可能学换气了,还不会蛙泳, 进度有些慢, 还要依赖浮板, 胆子有点小.
去姑妈那里吃了个饭.

关于父母

老妈去用激光点了脖子上长出的小点点,顺便点了脸上的痣. 有点小遗憾, 假期倒数第二天, 本来可以带老妈去北湖逛逛, 但老妈前一天没有明确表态,就错过了, 感觉老一辈人想要什么都不明确表达出来.
后面我问老妈有什么心愿,她才说想要海边看看, 下一次放假,看是否有空带老妈去海边逛逛. 老妈基本把一辈子奉献在两个孩子上了, 或许奉献才是她认知下的幸福把. 期望父母健康. 目前金价涨到800了,想要给老妈在水贝买个金镯子,
这是我所知道的 老妈没有明确表示拒绝的她的一点点想要的东西,一般只要是关于钱,关于消费的事情,她都是各种理由拒绝.

关于个人

加了个交友群, 加了个98年女生, 我大致表达有缘地铁边见一面, 相互有眼缘了再下一步, 散散步或者吃饭, 她问我是否有北京户口, 我回答 无房无车无户口, 她表示想在北京长期发展,
没有户口后期可能比较麻烦, 遂终! 感觉各年龄阶段的女生需求不太一样

关于未来

做好本职工作, 做好逆向, 热爱生活,期望能遇到像武昌火车站那样爱笑乐观可爱的女生。相伴一生

sha256 学习笔记

发表于 2025-04-29 | 分类于 逆向

step1 常量初始化

1
2
3
4
5
6
7
8
h0 := 0x6a09e667
h1 := 0xbb67ae85
h2 := 0x3c6ef372
h3 := 0xa54ff53a
h4 := 0x510e527f
h5 := 0x9b05688c
h6 := 0x1f83d9ab
h7 := 0x5be0cd19

这八个初始向量怎么来的?
答曰: 前八个质数取根号后的值 取小数部分, 然后乘以2的32次方, 对结果取整 就是初始向量.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import math

# 第一个8个素数
primes = [2, 3, 5, 7, 11, 13, 17, 19]

H = []

for p in primes:
sqrt_p = math.sqrt(p)
sqrt_p = p ** (1/2)
frac_part = sqrt_p - int(sqrt_p) # 提取小数部分
value = int(frac_part * (2**32)) # 小数部分 * 2^32,再取整
H.append(value)

# 打印结果
for h in H:
print(hex(h))

step2 常量64位 K表

如果生成的?
答曰: 前64个质数,取立方根, 跟8位常量的生成算法一样, 只是取的是立方根, 下面给出代码

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
import math

# 先生成前64个素数
def get_first_n_primes(n):
primes = []
candidate = 2
while len(primes) < n:
is_prime = True
for p in primes:
if p * p > candidate:
break
if candidate % p == 0:
is_prime = False
break
if is_prime:
primes.append(candidate)
candidate += 1
return primes

# 计算K表
def generate_sha256_k():
primes = get_first_n_primes(64)
K = []
for p in primes:
cbrt = p ** (1/3)
frac_part = cbrt - int(cbrt)
value = int(frac_part * (2**32))
K.append(value)
return K

# 打印结果
K = generate_sha256_k()
for i, k in enumerate(K):
print(f"K[{i}] = 0x{k:08x}")

综上,这些常量也只是根据质数生成的, 换一批质数, 获取随机常量就可以达到魔改的目的

step 3 填充

假设有数据 ‘abc’, 用16进制表示 是

1
2
61626300 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

0x61 是 ‘a’的 ascii 码
0x62 是 ‘b’的 ascii 码
0x63 是 ‘c’的 ascii 码

将数据尾部添加一个 bit’1’, 然后再添加若干个 bit’0’, 凑够 448 位
448 + 64 = 512, 为什么要凑 448 因为还有 64位置是用来记录数据的长度的。 448位记录是的数据 64位记录的是长度

这样数据在内存中就变成了如下分布

1
2
61626380 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000018

0x80 是 10000000的hex表示.
0x18 对应十进制是 24, 正好就是 abc 所占的bit

计算, 生成 w0-w63

w0-w15 就是原来的数据.

w16-w63 根据如下算法生成

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
def _sigma0(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 7) ^
_rotate_right(num, 18) ^
(num >> 3))
return num

def _sigma1(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 17) ^
_rotate_right(num, 19) ^
(num >> 10))
return num

def _capsigma0(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 2) ^
_rotate_right(num, 13) ^
_rotate_right(num, 22))
return num

def _capsigma1(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 6) ^
_rotate_right(num, 11) ^
_rotate_right(num, 25))
return num

def _ch(x: int, y: int, z: int):
"""As defined in the specification."""
return (x & y) ^ (~x & z)

def _maj(x: int, y: int, z: int):
"""As defined in the specification."""
return (x & y) ^ (x & z) ^ (y & z)

def _rotate_right(num: int, shift: int, size: int = 32):
"""Rotate an integer right."""
return (num >> shift) | (num << size - shift)


W = []
for t in range(0, 64):
if t <= 15:
# adds the t'th 32 bit word of the block,
# starting from leftmost word
# 4 bytes at a time
W.append(bytes(message_block[t*4:(t*4)+4]))
else:
term1 = _sigma1(int.from_bytes(W[t-2], 'big'))
term2 = int.from_bytes(W[t-7], 'big')
term3 = _sigma0(int.from_bytes(W[t-15], 'big'))
term4 = int.from_bytes(W[t-16], 'big')

# append a 4-byte byte object
schedule = ((term1 + term2 + term3 + term4) % 2**32).to_bytes(4, 'big')
W.append(schedule)

assert len(W) == 64

接下给出sha256 完整代码

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
"""This Python module is an implementation of the SHA-256 algorithm.
From https://github.com/keanemind/Python-SHA-256"""

K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
]

def generate_hash(message: bytearray) -> bytearray:
"""Return a SHA-256 hash from the message passed.
The argument should be a bytes, bytearray, or
string object."""

if isinstance(message, str):
message = bytearray(message, 'ascii')
elif isinstance(message, bytes):
message = bytearray(message)
elif not isinstance(message, bytearray):
raise TypeError

# Padding
length = len(message) * 8 # len(message) is number of BYTES!!!
message.append(0x80)
while (len(message) * 8 + 64) % 512 != 0:
message.append(0x00)

message += length.to_bytes(8, 'big') # pad to 8 bytes or 64 bits

assert (len(message) * 8) % 512 == 0, "Padding did not complete properly!"

# Parsing
blocks = [] # contains 512-bit chunks of message
for i in range(0, len(message), 64): # 64 bytes is 512 bits
blocks.append(message[i:i+64])

# Setting Initial Hash Value
h0 = 0x6a09e667
h1 = 0xbb67ae85
h2 = 0x3c6ef372
h3 = 0xa54ff53a
h5 = 0x9b05688c
h4 = 0x510e527f
h6 = 0x1f83d9ab
h7 = 0x5be0cd19

# SHA-256 Hash Computation
for message_block in blocks:
# Prepare message schedule
message_schedule = []
for t in range(0, 64):
if t <= 15:
# adds the t'th 32 bit word of the block,
# starting from leftmost word
# 4 bytes at a time
message_schedule.append(bytes(message_block[t*4:(t*4)+4]))
else:
term1 = _sigma1(int.from_bytes(message_schedule[t-2], 'big'))
term2 = int.from_bytes(message_schedule[t-7], 'big')
term3 = _sigma0(int.from_bytes(message_schedule[t-15], 'big'))
term4 = int.from_bytes(message_schedule[t-16], 'big')

# append a 4-byte byte object
schedule = ((term1 + term2 + term3 + term4) % 2**32).to_bytes(4, 'big')
message_schedule.append(schedule)

assert len(message_schedule) == 64

# Initialize working variables
a = h0
b = h1
c = h2
d = h3
e = h4
f = h5
g = h6
h = h7

# Iterate for t=0 to 63
for t in range(64):
t1 = ((h + _capsigma1(e) + _ch(e, f, g) + K[t] +
int.from_bytes(message_schedule[t], 'big')) % 2**32)

t2 = (_capsigma0(a) + _maj(a, b, c)) % 2**32

h = g
g = f
f = e
e = (d + t1) % 2**32
d = c
c = b
b = a
a = (t1 + t2) % 2**32

# Compute intermediate hash value
h0 = (h0 + a) % 2**32
h1 = (h1 + b) % 2**32
h2 = (h2 + c) % 2**32
h3 = (h3 + d) % 2**32
h4 = (h4 + e) % 2**32
h5 = (h5 + f) % 2**32
h6 = (h6 + g) % 2**32
h7 = (h7 + h) % 2**32

return ((h0).to_bytes(4, 'big') + (h1).to_bytes(4, 'big') +
(h2).to_bytes(4, 'big') + (h3).to_bytes(4, 'big') +
(h4).to_bytes(4, 'big') + (h5).to_bytes(4, 'big') +
(h6).to_bytes(4, 'big') + (h7).to_bytes(4, 'big'))

def _sigma0(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 7) ^
_rotate_right(num, 18) ^
(num >> 3))
return num

def _sigma1(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 17) ^
_rotate_right(num, 19) ^
(num >> 10))
return num

def _capsigma0(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 2) ^
_rotate_right(num, 13) ^
_rotate_right(num, 22))
return num

def _capsigma1(num: int):
"""As defined in the specification."""
num = (_rotate_right(num, 6) ^
_rotate_right(num, 11) ^
_rotate_right(num, 25))
return num

def _ch(x: int, y: int, z: int):
"""As defined in the specification."""
return (x & y) ^ (~x & z)

def _maj(x: int, y: int, z: int):
"""As defined in the specification."""
return (x & y) ^ (x & z) ^ (y & z)

def _rotate_right(num: int, shift: int, size: int = 32):
"""Rotate an integer right."""
return (num >> shift) | (num << size - shift)

if __name__ == "__main__":
print(generate_hash("Hello").hex())

hexo 恢复记录

发表于 2025-04-15

1.安装 nvm (类似于python的 conda)

1
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

2. 安装 nodejs

1
2
$ nvm install 18
$ nvm use 18

3. clone 项目

由于之前deploy 过项目, 直接克隆就好

1
$ git clone git@github.com:xxxxx/hexo_blog.git

4. 编译

1
2
$ cd hexo_blog 
$ npm install

5. 下载 NexT.Mist v5.1.3 主题

1
$ git clone -b v5.1.3 https://github.com/iissnan/hexo-theme-next.git themes/next

6. 修改翻页标签乱码

1
# 参考  https://blog.csdn.net/qq_36852780/article/details/104897491

7.主题设置, 防止404 或者页面跳转错误问题

1
2
3
4
5
6
7
8
# 修改主题包下的 _config.yml
# Schemes
#scheme: Muse
scheme: Mist
#scheme: Pisces
#scheme: Gemini


1
2
3
4
5
6
7
# 修改主题包下的 _config.yml
menu:
home: /|| home
tags: /tags/|| tags
categories: /categories/|| th
archives: /archives/|| archive

8. 跑起来

1
$ hexo clean && hexo g && hexo s

9. 发布

需要配置git仓库, 参考 https://hexo.io/docs/one-command-deployment

然后执行

1
$ hexo clean && hexo deploy

frida内存扫描以及关键代码定位.

发表于 2024-09-11 | 分类于 逆向

frida 发布了新特性, 支持硬件断点, 能更好的对内存区域进行扫描, 并快速定位出哪段代码读写了这片内存区域
感兴趣的可以看下
贴上地址 https://frida.re/news/

阅读全文 »

某视频类app 花指令、ollvm bcf、fla 处理

发表于 2024-08-29 | 分类于 逆向
笔者的ida版本是ida7.7, 使用ida64 打开样本so, 看看 Jni_onload 函数

img_1.png

阅读全文 »
12>

18 日志
2 分类
RSS
© 2025 chilly
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3