-
-
[原创]使用bindiff恢复lua符号(调用lua的程序逆向)
-
发表于: 2021-7-20 14:04 10923
-
使用bindiff恢复lua符号(调用lua的程序逆向)
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能
对于逆向一个会调用lua的程序,程序中会存在很多调用lua的标准函数,例如:luaL_newstate,lua_getglobal,luaL_openlibs,lua_tonumber等
一:C和lua相互调用的分析
lua官方文档:http://www.lua.org/manual/5.4/
一:add.c调用add.lua
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 | / / add.c / / 你需要include这几个lua头文件 #include <stdio.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" lua_State * L; int luaadd( int x, int y) { int sum ; / * 函数名 * / lua_getglobal(L, "add" ); / * 参数入栈 * / lua_pushnumber(L, x); / * 参数入栈 * / lua_pushnumber(L, y); / * 开始调用函数,有 2 个参数, 1 个返回值 * / lua_call(L, 2 , 1 ); / * 取出返回值 * / sum = ( int )lua_tonumber(L, - 1 ); / * 清除返回值的栈 * / lua_pop(L, 1 ); return sum ; } int main( int argc, char * argv[]) { int sum ; L = luaL_newstate(); / * 创建lua状态机 * / luaL_openlibs(L); / * 打开Lua状态机中所有Lua标准库 * / / * 加载lua脚本 * / luaL_dofile(L, "add.lua" ); / * 调用C函数,这个里面会调用lua函数 * / sum = luaadd( 99 , 10 ); printf( "The sum is %d \n" , sum ); / * 清除Lua * / lua_close(L); return 0 ; } |
1 2 3 4 | / / add.lua function add(x,y) return x + y end |
add.lua放在add.c的同路径下
二:lua调C
1 2 | 1. 通过在C中注册函数给lua调用(常) 2. 封装成c动态链接库,在lua中require |
1.通过在C中注册函数给lua调用(常)
lua提供了lua_register函数注册C函数给lua端调用
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 | / / hello.c #include <stdio.h> #include <string.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" static int l_SayHello(lua_State * L) { const char * d = luaL_checkstring(L, 1 ); / / 获取参数,字符串类型 int len = strlen(d); char str [ 100 ] = "hello " ; strcat( str , d); lua_pushstring(L, str ); / * 返回给lua的值压栈 * / return 1 ; } int main( int argc, char * argv[]) { lua_State * L = luaL_newstate(); / * 创建lua状态机 * / luaL_openlibs(L); / * 打开Lua状态机中所有Lua标准库 * / lua_register(L, "SayHello" , l_SayHello); / / 注册C函数到lua const char * testfunc = "print(SayHello('lijia'))" ; / / lua中调用c函数 if (luaL_dostring(L, testfunc)) / / 执行Lua命令。 printf( "Failed to invoke.\n" ); / * 清除Lua * / lua_close(L); return 0 ; } |
gcc -o hello hello.c -llua编译执行
./hello
2.调用C动态链接库
创建一个mylib.c的文件,然后我们把它编译成动态链接库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #include <stdio.h> #include <math.h> #include <stdarg.h> #include <stdlib.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> / * 所有注册给Lua的C函数具有 * "typedef int (*lua_CFunction) (lua_State *L);" 的原型。 * / static int l_sin(lua_State * L) { / / 如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。 double d = luaL_checknumber(L, 1 ); lua_pushnumber(L, sin(d)); / * push result * / / * 这里可以看出,C可以返回给Lua多个结果, * 通过多次调用lua_push * (),之后 return 返回结果的数量。 * / return 1 ; / * number of results * / } / * 需要一个 "luaL_Reg" 类型的结构体,其中每一个元素对应一个提供给Lua的函数。 * 每一个元素中包含此函数在Lua中的名字,以及该函数在C库中的函数指针。 * 最后一个元素为“哨兵元素”(两个 "NULL" ),用于告诉Lua没有其他的函数需要注册。 * / static const struct luaL_Reg mylib[] = { { "mysin" , l_sin}, {NULL, NULL} }; / * 此函数为C库中的“特殊函数”。 * 通过调用它注册所有C库中的函数,并将它们存储在适当的位置。 * 此函数的命名规则应遵循: * 1 、使用 "luaopen_" 作为前缀。 * 2 、前缀之后的名字将作为 "require" 的参数。 * / extern int luaopen_mylib(lua_State * L) { / * void luaL_newlib (lua_State * L, const luaL_Reg l[]); * 创建一个新的 "table" ,并将 "l" 中所列出的函数注册为 "table" 的域。 * / luaL_newlib(L, mylib); return 1 ; } |
使用gcc -o mylib.so -fPIC -shared mylib.c -llua -ldl编译成so
然后创建一个lua文件,把我们编译出来的c库引入进来
1 2 3 4 5 | - - [[ 这里 "require" 的参数对应C库中 "luaopen_mylib()" 中的 "mylib" 。 C库就放在 "a.lua" 的同级目录, "require" 可以找到。]] local mylib = require "mylib" - - 结果与上面的例子中相同,但是这里是通过调用C库中的函数实现。 print (mylib.mysin( 3.14 / 2 )) - - > 0.99999968293183 |
执行a.lua文件,后报错,说Lua存在多个虚拟机
因为lua默认编译的是静态链接库,这样会导致链接多个VM冲突。
那么我们自己再编译个lua解释器动态链接一下。
1 2 3 4 5 6 7 8 9 10 11 12 | / / mylua.c #include <stdio.h> #include "lua.h" #include "lualib.h" #include "lauxlib.h" int main() { lua_State * L = luaL_newstate(); luaL_openlibs(L); if (luaL_loadfile(L, "a.lua" ) || lua_pcall(L, 0 , 0 , 0 )) { printf( "%s" , lua_tostring(L, - 1 )); } } |
gcc -o mylua mylua.c -llua -ldl -lm -Wall
这样就能编译出mylua可执行文件
在命令行./mylua执行,成功打印出0.99999968293183
二:编译lua
首先我们获得lua的源码包
1 | curl - R - O [http: / / www.lua.org / ftp / lua - 5.3 . 4.tar .gz](http: / / www.lua.org / ftp / lua - 5.3 . 0.tar .gz) |
然后解压
1 | tar zxf lua - 5.3 . 4.tar .gz |
cd lua-5.3.4
sudo make linux test
(执行这条命令时若报错fatal error: readline/readline.h: No such file or directory,是缺少readline相关的库,执行sudo apt-get install libreadline-dev装好即可)
sudo make install
全部成功后
1 2 3 4 5 6 7 8 9 10 11 12 | ub20@ubuntu:~$ cd / usr / local / bin ub20@ubuntu: / usr / local / bin $ ls arm_now freeze_graph pip2 saved_model_cli distro lua pip2. 7 tensorboard f2py luac pip3 toco f2py2 markdown_py pip3. 5 toco_from_protos f2py2. 7 pip render wheel ub20@ubuntu: / usr / local / bin $ file lua lua: ELF 64 - bit LSB shared object , x86 - 64 , version 1 (SYSV), dynamically linked, interpreter / lib64 / ld - linux - x86 - 64.so . 2 , BuildID[sha1] = a401cd154b2c1efae00a3390e0f287bacf1187b8, for GNU / Linux 3.2 . 0 , not stripped |
将这个lua文件复制到主机即可
三:bindiff恢复lua符号
先使用IDA载入我们上面编译好的lua
可以看到函数名很完整
退出保存得到lua.i64文件
然后将我们要分析的程序重新拖入IDA,ctrl+6使用bindiff
Diff Database(记住两个i64文件的路径都不能有中文)
载入成功后再次ctrl+6
结果
四:(实践)2021津门杯easyRe
调用lua的程序逆向
找到main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int __cdecl main( int argc, const char * * argv, const char * * envp) { int i; / / [rsp + 4h ] [rbp - 1Ch ] char * input ; / / [rsp + 10h ] [rbp - 10h ] _DWORD * v6; / / [rsp + 18h ] [rbp - 8h ] input = (char * )malloc( 0x21uLL ); printf( "input the a str:" ); __isoc99_scanf( "%s" , input ); if ( (unsigned int )strlen( input ) ! = 32 ) { printf( "oh,no!" ); exit( 0 ); } v6 = encrypt(~( 53 * ( 2 * input [ 6 ] + input [ 15 ] + 3 * input [ 29 ])) & 0xFFF , (__int64) input ); for ( i = 0 ; i < = 31 ; + + i ) { if ( dword_63A380[i] ! = v6[i] ) { printf( "oh,no!" ); exit( 0 ); } } puts( "Success!" ); return 0 ; } |
输入长度为32的字符串,然后从中截取三个字符来计算得到encrypt函数的参数1,并将字符串作为参数2传入函数
使用bindiff修复符号
效果如下:
可以很准确的识别出lua的函数
对照lua文档链接:http://www.lua.org/manual/5.4/
分析encrypt函数:(请查看注释)
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 | void * __fastcall encrypt(unsigned int a1, __int64 a2) { _BYTE * functionname; / / rax double lua_func_parameter; / / xmm0_8 __int64 v4; / / rcx __int64 v5; / / r8 __int64 v6; / / r9 int i; / / [rsp + 20h ] [rbp - 70h ] int j; / / [rsp + 24h ] [rbp - 6Ch ] int k; / / [rsp + 28h ] [rbp - 68h ] FILE * fp; / / [rsp + 38h ] [rbp - 58h ] __int64 size; / / [rsp + 40h ] [rbp - 50h ] void * ptr; / / [rsp + 48h ] [rbp - 48h ] const char * decode_file; / / [rsp + 50h ] [rbp - 40h ] __int64 L; / / [rsp + 58h ] [rbp - 38h ] void * s; / / [rsp + 70h ] [rbp - 20h ] void * final; / / [rsp + 78h ] [rbp - 18h ] setvbuf(stdout, 0LL , 2 , 0LL ); fp = fopen( "my.lua" , "rb" ); if ( !fp ) exit( 0 ); fseek(fp, 0LL , 2 ); size = ftell(fp); ptr = malloc(size); rewind(fp); fread(ptr, 1uLL , size, fp); / / 读取文件 decode_file = xorfunc((__int64)ptr, size); / / 对文件进行异或解密 L = luaL_newstate(); / / luaL_newstate创建lua状态机 luaopen_base(L); / / luaopen_base调用基本库 luaL_openlibs(L); / / / * 打开之前luaopen_base调用的lua状态机中所有Lua标准库 * / if ( !L ) exit( 0 ); if ( (unsigned int )luaL_loadstring(L, decode_file) ) / / Loads a string as a Lua chunk. exit( 0 ); s = malloc( 132uLL ); memset(s, 0 , 132uLL ); for ( i = 0 ; i < = 32 ; + + i ) / / 根据传入的a1值,循环 33 次生成一个序列 { * ((_DWORD * )s + i) = ( 0x1ED0675 * (unsigned __int64)a1 + 0x6C1 ) % 254 ; a1 = * ((_DWORD * )s + i); } final = malloc( 0x100uLL ); memset(final, 0 , 0x100uLL ); for ( j = 0 ; j < = 31 ; + + j ) { for ( k = 0 ; k < = 32 ; + + k ) { * ((_DWORD * )final + j + k) + = * (char * )(j + a2) ^ * ((_DWORD * )s + k); lua_pcallk(L, 0 , 0 , 0 , 0LL , 0LL ); / / Calls a function ( or a callable object ) in protected mode functionname = xorfunc((__int64)off_63A360, 7 ); / / 对这个字符串进行异或解密 lua_getglobal(L, (__int64)functionname); / / lua_getglobal 要调用的lua的函数名 lua_func_parameter = (double) * (( int * )final + j + k); lua_pushnumber(L, lua_func_parameter); / / 参数入栈 lua_pcallk(L, 1 , 1 , 0 , 0LL , 0LL ); lua_tonumberx(L, 0xFFFFFFFFLL , 0LL , v4, v5, v6); / / lua_tonumberx 取出返回值 * ((_DWORD * )final + j + k) = ( int )lua_func_parameter; } } return final; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | _BYTE * __fastcall xorfunc(__int64 a1, int a2) { _BYTE * result; / / rax int i; / / [rsp + 14h ] [rbp - 2Ch ] int v4[ 6 ]; / / [rsp + 20h ] [rbp - 20h ] unsigned __int64 v5; / / [rsp + 38h ] [rbp - 8h ] v5 = __readfsqword( 0x28u ); result = malloc(a2); v4[ 0 ] = 2 ; v4[ 1 ] = 3 ; v4[ 2 ] = 5 ; if ( a2 ) { for ( i = 0 ; i < a2; + + i ) result[i] = * (_BYTE * )(i + a1) ^ LOBYTE(v4[i % 3 ]); } return result; } |
本来想动态调试,让程序自己载入my.lua进行解密,但是拖入winhex查看,发现OEP(e_entry)被修改过,直接./re1,会提示非法指令
只能静态实现它对文件的异或解密了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # _*_ coding: utf-8 _*_ # editor: SYJ # function: Reversed By SYJ # describe: fp = open ( "D:\\CTF\\COMPETITIONS\\2021jingmen\\REVERSE\\easyRe\\my.lua" , "rb" ) data = fp.read() xor_arr = [ 2 , 3 , 5 ] def xor_func(data): for i in range ( len (data)): print ( chr ((data[i] ^ xor_arr[i % 3 ]) & 0xff ), end = '') print ( "下面是调用xorfuncd对文件解密的结果" ) xor_func(data) print ( "\n下面是循环中调用xorfunc对off_63A360位置的字符串cgfffce解密的结果" ) xor2 = [ 0x63 , 0x67 , 0x66 , 0x66 , 0x66 , 0x63 , 0x65 ] xor_func(xor2) |
运行可以得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 下面是调用xorfuncd对文件解密的结果 function BitXOR(a,b) local p,c = 1 , 0 while a> 0 and b> 0 do local ra,rb = a % 2 ,b % 2 if ra~ = rb then c = c + p end a,b,p = (a - ra) / 2 ,(b - rb) / 2 ,p * 2 end if a<b then a = b end while a> 0 do local ra = a % 2 if ra> 0 then c = c + p end a,p = (a - ra) / 2 ,p * 2 end return c end function adcdefg(j) return BitXOR( 5977654 ,j) end 下面是循环中调用xorfunc对字符串cgfffce解密的结果 adcdefg |
分析这段lua:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function BitXOR(a,b) local p,c = 1 , 0 while a> 0 and b> 0 do local ra,rb = a % 2 ,b % 2 # 取最后一bit if ra~ = rb then c = c + p end # 如果最后一bit不相等,c = c+p a,b,p = (a - ra) / 2 ,(b - rb) / 2 ,p * 2 # a等于除开最后一bit后还有多少个2,b等于除开最后一bit后还有多少个2,p=p*2,二进制进位 end if a<b then a = b end # 如果上面那个循环执行完了还在未判断完,就让a里面存放a和b里面大的那个数 while a> 0 do local ra = a % 2 if ra> 0 then c = c + p end # a的最后一bit如果为1,就和b对应的bit(因为b小于a,这个bit必为0)异或,等于1,要二进制进位 a,p = (a - ra) / 2 ,p * 2 # a等于除开最后一bit后还有多少个2,p二进制进位 end return c end function adcdefg(j) return BitXOR( 5977654 ,j) # 所以这个函数就是一个bit一个bit的进行判断,得到最后异或的值 end |
其实就是实现的按bit异或,跟函数名BitXOR照应,所以function abcdefg(j)就是5977654 ^ j
然后encrypt加密后返回,在main函数中与内存中的一段数据dword_63A380进行比较
1 2 3 4 5 6 7 8 9 10 | seg023: 000000000063A380 ; int dword_63A380[ 32 ] seg023: 000000000063A380 dword_63A380 dd 5B368Bh , 136h , 5B37CBh , 0E0Ch seg023: 000000000063A380 ; DATA XREF: main + D3↑r seg023: 000000000063A380 dd 5B396Ah , 39h , 5B32A5h , 0CBAh seg023: 000000000063A380 dd 5B2683h , 0CB9h , 5B3777h , 0E47h seg023: 000000000063A380 dd 5B33BDh , 1F49h , 5B34A8h , 4D1h seg023: 000000000063A380 dd 5B27E7h , 0D76h , 5B3BDFh , 2EFh seg023: 000000000063A380 dd 5B3CD3h , 1F6Eh , 5B3D90h , 3DAh seg023: 000000000063A380 dd 5B3184h , 524h , 5B330Eh , 1A10h seg023: 000000000063A380 dd 5B2E84h , 0D3h , 5B3DA1h , 2F7Bh |
使用写出最后的解密脚本
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 | # decrypt部分 from z3 import * dest_enc = [ 0x005B368B , 0x00000136 , 0x005B37CB , 0x00000E0C , 0x005B396A , 0x00000039 , 0x005B32A5 , 0x00000CBA , 0x005B2683 , 0x00000CB9 , 0x005B3777 , 0x00000E47 , 0x005B33BD , 0x00001F49 , 0x005B34A8 , 0x000004D1 , 0x005B27E7 , 0x00000D76 , 0x005B3BDF , 0x000002EF , 0x005B3CD3 , 0x00001F6E , 0x005B3D90 , 0x000003DA , 0x005B3184 , 0x00000524 , 0x005B330E , 0x00001A10 , 0x005B2E84 , 0x000000D3 , 0x005B3DA1 , 0x00002F7B ] for seed in range ( 0xfff ): xor_data = [] for i in range ( 33 ): # 循环33次生成序列 r = ( 0x1ED0675 * seed + 0x6c1 ) % 0xfe xor_data.append(r) seed = r s = Solver() flag = [BitVec(( 'x%d' % i), 8 ) for i in range ( 32 )] xor_result = [ 0 for i in range ( 64 )] for i in range ( 32 ): for j in range ( 33 ): a = flag[i] ^ xor_data[j] xor_result[i + j] + = a xor_result[i + j] = (xor_result[i + j] ^ 5977654 ) for i in range ( 0 , 32 ): s.add(flag[i] < = 127 ) s.add(flag[i] > = 32 ) s.add(xor_result[i] = = dest_enc[i]) if s.check() = = sat: model = s.model() str = [ chr (model[flag[i]].as_long().real) for i in range ( 32 )] print ("".join( str )) exit() # flag{cfcd208495d565ef66e7dff9f98764da} |
参考:
https://www.cnblogs.com/lijiajia/p/8284328.html
https://www.cnblogs.com/huangjianxin/p/10581174.html
https://www.runoob.com/lua/lua-environment.html
https://blog.csdn.net/sln_1550/article/details/116614228