wasm转c调用与封装至dll案例
wasm转c调用与封装至dll案例
- 准备工作
- 初级 猿人学练习题
- 中级 崔大网习题
- 高级 某视频网站
准备工作
相关文档:
1.某德地图矢量瓦片逆向(快速wasm逆向),执行wasm2c翻译出来的c代码一
2.执行wasm2c翻译出来的c代码二
相关工具
- wabt工具包
- gcc编译器
在文章 某网站字幕加密的wasm分析 中均提到过
因为本篇文章主要讲wasm转c的使用,所以js层的逻辑会跳过
初级 猿人学练习题
新学习的知识:
- 导出函数的调用
猿人学练习 15 题 https://match.yuanrenxue.com/match/15
首先下载这个wasm到本地,然后使用wasm2c工具转为.c和.h文件
wasm2c yuanrenxue.wasm -o yuanrenxue.c
这时目录下就多了两个文件yuanrenxue.c和yuanrenxue.h
使用CLion创建一个C的控制台项目,把yuanrenxue.c和yuanrenxue.h复制进去,并且把wbat工具包目录下的wasm-rt.h、wasm-rt-impl.c、wasm-rt-impl.h三个文件复制进去
然后自己新建一个main.c,根据上面的文章知道,需要新初始化wasm,所以创建一个初始化函数
void init_wasm(){init_func_types();init_globals();init_memory();init_table();init_exports();
}
然后通过调试js,可以发现其调用的是导出函数的encode函数,所以创建一个用于加密的函数
u32 encode(u32 t1, u32 t2){return w2c_encode(t1, t2);
}
最后创建一个main函数作为测试
int main() {init_wasm();u32 t1 = 819334766;u32 t2 = 819334746;int out = (int)encode(t1, t2);printf("%d\n", out);return 0;
}
写好测试文件如下
然后使用gcc编译为exe文件
gcc -o yuanrenxue main.c wasm-rt-impl.c
此时目录下可以得到yuanrenxue.exe,在命令行下运行,可以得到结果,与网页中计算的是一致的
接下来制作动态链接库,声明C中的导出函数
extern void init_wasm(void);
extern u32 encode(u32, u32);
然后使用gcc编译为dll
gcc -shared -o yuanrenxue.dll main.c wasm-rt-impl.c
此时可以得到一个yuanrenxue.dll,可以使用python的ctypes库进行加载与调用
import ctypesdef main():dll = ctypes.windll.LoadLibrary('yuanrenxue.dll')print(dll)dll.init_wasm()t = dll.encode(819334766, 819334746)print(t)if __name__ == '__main__':main()
尝试调用dll得到结果
但是没有成功,报错了。这是因为用来编译的gcc是32位的,编译出来的dll只能在32位下使用。64位的电脑必须使用64位的gcc进行编译,那么使用64位的gcc重新编译dll
"D:/MinGW64/bin/gcc" -shared -o yuanrenxue.dll main.c wasm-rt-impl.c
非常好,成功得到一致的结果
中级 崔大网习题
新学习的知识:
- 处理导入函数
- 处理字符串参数和返回值
练习 15 题 https://spa15.scrape.center/
下载文件,然后转c,复制到新项目一顿操作和上面初级的一样
#include <stdio.h>
#include <stdlib.h>
#include "spa15.c"int main() {return 0;
}
创建空的main.c,仅仅导入了spa15.c文件,直接编译
这里提示文件里面有一个导入函数还没有定义,所以要去到spa15.c来补充函数的定义。根据前面的相关文章知道,如果这个导入函数没有用到,那么可以使它等于NULL,那么在spa15.c的最后面加上
void (*Z_wasi_snapshot_preview1Z_proc_exitZ_vi)(u32) = NULL;
这时发现编译通过了,这时可以补充初始化函数和加密函数了
通过调试js,发现调用的是导出函数encrypt,参数是两个字符串,返回值也是一个字符串,这个和初级的题目就不一样了
根据调试js知道,字符串参数需要先申请wasm的内存空间,然后把字符串放置到指定的偏移,然后当使用完毕后,需要手动释放内存。测试函数如下
#include <stdio.h>
#include <stdlib.h>
#include "spa15.c"extern void init_wasm(void);
extern char* encode(char*, char*);void init_wasm(){init_func_types();init_globals();init_memory();init_table();init_exports();
}char* encode(char* a1, char* a2){u32 str_len1 = strlen(a1) + 1;u32 ptr1 = w2c_stackAlloc(str_len1);memcpy(w2c_memory.data + ptr1, a1, str_len1);u32 str_len2 = strlen(a2) + 1;u32 ptr2 = w2c_stackAlloc(str_len2);memcpy(w2c_memory.data + ptr2, a2, str_len2);u32 out_ptr = w2c_encrypt(ptr1, ptr2);char* out_str = (char *)malloc(128);memcpy(out_str, w2c_memory.data + out_ptr, 128);w2c_stackRestore(ptr1);w2c_stackRestore(ptr2);return out_str;
}int main() {init_wasm();char* a1 = "/api/movie";printf("%s\n", a1);char* a2 = "1638462743";printf("%s\n", a2);char* c = encode(a1, a2);printf("%s\n", c);free(c);return 0;
}
尝试编译为exe测试
"D:/MinGW64/bin/gcc" -o spa15 main.c wasm-rt-impl.c
非常好,出现的结果和网站上的一致。接下来编译为dll
"D:/MinGW64/bin/gcc" -shared -o spa15.dll main.c wasm-rt-impl.c
python中使用ctypes调用函数的时候,默认的参数和返回值都是int类型,但是这个函数是字符串,所以还需要在调用函数前修改参数列表的数据类型以及返回值的数据类型
import ctypesdef main():dll = ctypes.windll.LoadLibrary('spa15.dll')print(dll)dll.init_wasm()dll.encode.argtypes = [ctypes.c_char_p, ctypes.c_char_p]dll.encode.restype = ctypes.c_char_ptoken = dll.encode(ctypes.c_char_p(b"/api/movie"), ctypes.c_char_p(b"1638462743"))print(token.decode())if __name__ == '__main__':main()
调用成功,结果也是正确的
高级 某视频网站
新学习的知识:
- 处理导入内存和导入表
- 复现导入函数逻辑
- 非导出函数调用
#include <stdio.h>
#include <stdlib.h>
#include "v1102.c"int main() {return 0;
}
导入v1102.c后,直接编译,会出现下面报错
可以看到,里面不单止有导入函数,还有导入了内存和表
导入函数和中级的一样,如果没有用到就直接给NULL
(有人可能会问怎么知道哪个函数用了,哪个没有用呢?答案是需要自己调试出来的)
处理导入内存和表相对麻烦一点,首先需要定义两个静态的全局变量
static wasm_rt_memory_t w2c_memory;
static wasm_rt_table_t w2c___indirect_function_table;
然后在init_memory函数的一开始添加一行初始化内存的代码
后面的1024数值可以在js代码中查看到,也可以在wasm中查看到。然后在init_table函数的第二行添加一行初始化表的代码
后面的4数值一样是从js或者wasm中查看,此时申明导入的内存和表
wasm_rt_memory_t (*Z_aZ_memory) = &w2c_memory;
wasm_rt_table_t (*Z_aZ_table) = &w2c___indirect_function_table;
aZ_aZ_ii函数是需要复现算法的,需要根据js的代码逻辑,改为C的代码,整体导入的代码如下
static wasm_rt_memory_t w2c_memory;
static wasm_rt_table_t w2c___indirect_function_table;u32 aZ_aZ_ii(u32 a1){char out_str[24];memcpy(out_str, w2c_memory.data + a1, 24);if(out_str[0] == 'l'){return 1;}else{return ((int)strlen(out_str)) - 10;}
}wasm_rt_memory_t (*Z_aZ_memory) = &w2c_memory;
wasm_rt_table_t (*Z_aZ_table) = &w2c___indirect_function_table;u32 (*Z_aZ_aZ_ii)(u32) = *aZ_aZ_ii;
u32 (*Z_aZ_bZ_ii)(u32) = NULL;
u32 (*Z_aZ_cZ_iiii)(u32, u32, u32) = NULL;
void (*Z_aZ_dZ_vi)(u32) = NULL;
添加完成后,编译可以通过。接下来就一样了,编写初始化函数和需要的自定义函数。这里又遇到一个问题,需要的核心函数并不是导出函数。那么在直接调用wasm的情况下,是没有办法调用非导出函数的。但是在C里面,所有的函数都被定义为静态的全局函数,所有在C中可以随心所欲的调用所有函数。其他的东西和中级的一样,代码如下
#include <stdio.h>
#include <stdlib.h>
#include "v1102.c"extern void init_wasm(void);
extern char* decode_key(char*, char*, int);void init_wasm(){init_func_types();init_globals();init_memory();init_table();init_exports();
}char* decode_key(char* token, char* key, int seed){int token_len = (int)strlen(token);u32 ptr_token = w2c_j( token_len + 1);memcpy(w2c_memory.data + ptr_token, token, token_len + 1);int key_len = (int)strlen(key);u32 ptr_key = w2c_j(key_len + 1);memcpy(w2c_memory.data + ptr_key, key, key_len + 1);u32 out_ptr = w2c_f48(ptr_key, key_len, seed, ptr_token, token_len);char* out_str = (char *)malloc(32);memcpy(out_str, w2c_memory.data + out_ptr, 32);w2c_h(ptr_token);w2c_h(ptr_key);return out_str;
}int main() {init_wasm();u32 seed = 246;char* token = "0";char* key = "\xe5\x9d\x17\xd1\xc2\xf8\"\xdc\x11\x84{\xc9q\x03p\x96_\xa8n\x1f\xdd\x99\xacoP\xde\x84%{.\xd0\xb8";char* out_str = decode_key(token, key, seed);printf("%s\n", out_str);return 0;
}
编译到exe测试
"D:/MinGW64/bin/gcc" -o v1102 main.c wasm-rt-impl.c
调用非导出函数成功,得到结果,编译为dll测试
"D:/MinGW64/bin/gcc" -shared -o v1102.dll main.c wasm-rt-impl.c
调用成功,得到的结果可以使用,非常好。
但是这个得到的dll有点大,接近200kb。那么用gcc编译dll的时候可以使用优化器进行优化编译
"D:/MinGW64/bin/gcc" -shared -Os -o v1102.dll main.c wasm-rt-impl.c
这时只剩下110kb了,体积少了差不多一半,一样可以得到正确的结果,完美结束。
更多内容欢迎加入我的星球
wasm转c调用与封装至dll案例相关推荐
- VisionPro——在脚本中调用自己封装的DLL
VisionPro--在脚本中调用自己封装的DLL 前言 一.具体配置与代码 前言 做硬件测试时,用到了vp的c#脚本,由于偷懒导出数据表想直接用以前封装好的函数库,在测试时遇到些问题,在此做些记录. ...
- 【C++】QT调用VS封装的dll(以科大讯飞离线命令词识别SDK为例)
QT调用VS封装的dll(以科大讯飞离线命令词识别SDK为例) 1.说明: 跨平台调用dll出现各种坑,谨以此文避坑. 参考博文:https://www.cnblogs.com/seer/p/4789 ...
- java 封装dll_java调用C#封装的DLL文件
紫衣仙女 先写一个java类,Java代码:1234567891011121314package com.ypoj.jni; public class TestJNI { pu ...
- 【转】将QT开发的界面程序封装成DLL,在VC中成功调用
最近手头的一个项目需要做一个QT界面,并且封装成DLL,然后再动态调用DLL给出的接口函数,使封装在DLL内部的QT界面跑起来,在网上查了很多资料,今天终于成功了,经验不敢独享,因为CSDN给了我很多 ...
- Matlab函数封装为DLL供Cpp调用
Matlab函数封装为DLL供Cpp调用 文章目录 Matlab函数封装为DLL供Cpp调用 Ⅰ目标 Ⅱ 学习 Ⅲ 实现 1. Matlab 函数封装为DLL 2. vs2015中C++调用Matla ...
- QT调用C#写的Dll
QT调用C#写的Dll 参见: https://blog.csdn.net/weixin_42420155/article/details/81060945 C#写的dll是没有dllMain入口函数 ...
- C#调用非托管C++DLL:直接调用法
在实际软件开发过程中,由于公司使用了多种语言开发,在C#中可能需要实现某个功能,而该功能可能用其他语言已经实现了,那么我们可以调用其他语言写好的模块吗?还有就是,由于C#开发好的项目,我们可以利用re ...
- java调用c写的dll jna_jna调用c编写的dll
我们团队目前开发的产品是用java语言编写的,大家都知道,java编写的代码随便都可以被反编译,导致别人可能会看到你"裸奔"的样子.所以,为了避免这种安全隐患,团队最终商定,将部分 ...
- QT调用C++写的Dll
C#写的dll是没有dllMain入口函数的,是一种中间语言,需要.Net运行时进行做本地化工作,因此如果要调用C#写的dll,需要依赖.Net运行时,然而Qt中还无法直接调用.Net运行时,最好的方 ...
最新文章
- legend3---laravel验证码使用
- curl 使用 ~/.netrc
- 存储器的保护(二)——《x86汇编语言:从实模式到保护模式》读书笔记19
- pyCharm pyplot.show()不显示图表的解决
- pdf批量添加图章_1分钟学会制作电子公章,三步搞定,轻松在PDF文件中添加公章图片...
- Week 1——Machine learning by Pro.Andrew Ng (cousera)——notebook
- JNB, JBE, JGE, JLE 指令的转移条件
- 115道Java面试题及答案分享,java程序员赶紧收好
- 枚举方法在swtch中的用法
- AppOps命令分析
- MIS和MES的区别
- SpringMVC源码分析迷你书
- Xinetd服务的安装与配置【转载】
- python实现zigzag_Zigzag Iterator的Pythonic方式?
- matlab在线_正版MATLAB向中国人民大学全校师生免费开放!
- 不同类型的 BPM 软件与客户
- 深漂中年程序员回忆录-南下找工作(二)
- 使用frp配置内网穿透
- 网站优化中高手更新网站文章的窍门
- Adobe软件安装错误127替代方案