shellcode
下载下来一共四个文件,
其中看到main.bat脚本是接受我们的输入后,以最小化方式启动part1.exe
这里去简单了解了一下bat脚本,开始的@echo off表示关闭执行本指令及其它的指令的回显;set /p表示后面用用户的输入来赋值;start /min表示以最小化窗口的方式启动一个程序,相应的也就有start /max以最大化窗口的方式启动;taskkill /im表示所要关闭的进程的以程序名给出,另外具体详细的用法,直接在命令行窗口使用taskkill /?就清楚了。
但是从bat脚本来看,他是怎么传递我们的输入的呢。
直接启动part1.exe来看,没有什么输出。然后ida中发现是go语言写的程序,但是用以往的恢复符号的脚本用不了,这和国赛初赛那个go语言程序一样,可以使用免费版的ida7.6,自带恢复符号表,效果很好。
从函数名及汇编代码可以看出,这个程序应该是启动一个服务:
再在010editor中看了part2.bin文件,从机器码很明显能看出,就是一个函数的开头:
最后在看part3.exe,一切就很清晰了,它加载了part.bin中shellcode然后执行。
直接动态跟踪进入shellcode,找到很多和网络编程相关的api,及发现它在本地的8080端口请求读文件(正是part1.exe启动的服务):
1 | int sub_3A0005() |
浏览器访问一下本地8080端口看看:确实。
剩下继续跟踪调试就好,请求读了一串字符串,然后用它解密出一个关键加密函数,从其中也知道程序是如何获取我们在bat脚本中输入的FLAG了:通过获取在启动当前进程下的FLAG变量获取。
1 | GetEnvironmentVariable是一个从调用该函数的进程的环境变量中返回指定的变量名值的函数,主要参数有lpName、lpBuffer等。 |
最后就是rc4流密码加密,得到密钥序列异或回去即可。
1 | 0x64, 0x2E, 0x90, 0x34, 0x41, 0xD8, 0x24, 0xCB, 0x52, 0x2E, 0xFB, 0x39, 0x3E, 0x91, 0x07, 0x0E, 0x96, 0xF6, 0x3C, 0x09, 0x9C, 0x21, 0x92, 0x21, 0xB2, 0xCC, 0x9F, 0x51, 0x48, 0x63, 0x4C, 0x8F, 0x72, 0x5D, 0xBF, 0x6C, 0x51, 0x76] s = [ |
replace
第一次做程序中调用lua语言代码的题,这个题本身不难,但在总结题目时,一些环境上面遇到很多问题,记录一下。
我发现这个题是调用了lua是从ida打开的提示信息看到的,这也是出题人编译题目是没有注意这一点,其次从题目中字符串区域上下文找多翻翻也是可以发现的,Luas表示lua版本是5.3
还找到了base64码表,从引用找到一个对码表逆序和base64加密函数,在base64加密函数下断后发现程序不是断下来,猜测是输入字符串长度不对,试了几次长度32,36都不是。
然后既然我上面已经找到了程序加载的lua字节码,直接从这个入手,idapython提取出数据,找工具反编译。
开始找到luadec这个工具,编译环境问题真是花了主要的时间,,这里我去折腾了。工具地址
首先编译这个lua源码,缺少各种依赖吧,印象最深得的缺少readline.h这个库(因为后面我要编译32位的lua找这个库的32位找半天),我是在ubuntu下,直接安装就是:
1 | sudo apt-get install libreadline-dev |
然后到luadec目录下去编译luadec:make LUAVER=5.3,当时又是很多问题,但找到问题,搜索安装上缺少项就能轻松解决。
编译好了,开始反编译:./luadec ans.luac > ans.lua,问题来了:
从提示信息看应该是位数问题,目标lua是32位的,而我的luadec是64位的。
linux环境是64位的,默认编译的就是64位的,那怎么编译32位的呢。这是编译C代码,用的gcc,记得gcc是有个-m32选项,强制编译32位的程序(前提是我们要安装好32位程序所要依赖的各种库文件)。
1 | sudo apt-get install build-essential module-assistant |
安装后,随便写了一个C文件,来编译测试了一下,gcc -m32 1.c -o 1,可行。
然后去找Makefile文件,增加编译选项。因为对Makefile不熟悉,这里开始又疑惑了,,为什么没有gcc命令,那程序是怎么编译的!
后面才发现,其实在是第一个Makefile中指定了到src文件下进行make,所以找到src文件下的Makefile,增加-m32
然后编译,出现问题:
1 | 当搜索用于 /usr/lib/x86_64-linux-gnu/libreadline.a 时跳过不兼容的 -lreadline |
可以看到,编译时跑去找64位的readline库了,而因为不兼容所以跳过,就提示找不到。
问题是知道了,但找这个32位的库真是要命,搜索根本没有。。最后是在一篇其它问题里发现有这样一个名字:lib32readline6-dev,
https://blog.csdn.net/zhbpd/article/details/41805737
安装发现果然是这个,高兴了一小会:
1 | sudo apt-get install lib32readline6-dev |
再到luadec下去编译luadec,同样要修改Makefile,增加gcc的选项-m32(这里我第一次编译忘了修改,又出现位数不兼容,Undefined reference to ‘__divdi3’的问题,好在及时发现,真是粗心大意,,)
终于成功,再次反编译,问题:
1 | cannot find blockend > 170 , pc = 169, f->sizecode = 170 |
然后尝试其它选项,如打印出函数调用结构:./luadec -pn ans2.luac
尝试只打印0序号函数,因为其它函数都是它的子结构:./luadec -f 0 ans2.luac > ans2.lua
还行,整体上大概都反编译出了,但是感觉比较难看,我又去找了unluac来看看效果,它就是一个jar,下载和使用起来很方便:
usage:如果解码中出现 \ddd
的形式,说明源码中有中文,这时注意加上:–rawstring
1 | java -jar unluac_2021_06_10.jar --rawstring ans.luac > ans.lua |
然后得到的结果,确实要比luadec得到的好一些:
1 | local L0_1, L1_1, L2_1, L3_1, L4_1, L5_1, L6_1, L7_1, L8_1, L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1 |
代码其实可读性还是比较高的,就变量名不好看和调用一个方法时时分开写的。
开始判断输入长度,要为38位,再调用了enlib库中的2个函数,其实就是之前我ida中找到的关于base64那2个函数,最后进行一个rc4加密,密钥RC4KEY,这里怕rc4是魔改过的,我直接把代码改了一下,找一个在线运行lua脚本的脚本,运行输出52长度的异或序列。
再在notepad++中使用正则(L….=)替换密文赋值的变量为逗号,方便提取出密文。
最后异或回去,发现不是可打印字符,那显然是错了,因为是base64加密结果。
哪里错了呢,回到程序分析,因为没有符号表,这里记录使用bindiff恢复符号的方法。
首先下载bindiff,https://www.zynamics.com/software.html .msi文件是Windows Installer的数据包
安装后,在\BinDiff\Plugins\IDA Pro路径下的dll文件复制到ida安装路径的plugins目录下即可。
打开ida,从输出窗口看是否成功加载:
然后下载一个lua源码包,编译出lua解释器。这个就是在上面编译luadec中,编译的第一步(64位和32位都有说),在lua-5.3目录下:make linux后,可以增加一步:
1 | make local |
这样后,我们的lua-5.3目录下会多一个include文件:里面的bin文件是lua解释器于luac编译器;include文件是我们在C代码中要调用lua代码所需要的头文件。
根据我们的要分析的文件的位数来选择编译对应位数的lua文件,将选择的lua文件放入ida中分析,然后退出保存idb文件。
再把我们要分析的文件拖入ida,按快捷键:ctrl+6
选择我们之前保存的idb文件,可以得到一些对比结果
然后再次快捷键:ctrl+6
两个最小要求一般选择0.5上下:
回到ida函数窗口,可以发现已经识别出了很多函数:
但是对于本题分析要用到的函数,却没有,我也编译了几个版本的lua来看,都没有发现恢复很好的,就直接分析了。
在字符串区域找到了lua脚本中出现的RC4KEY,且下面的Good!!是很可疑的。
从RC4KEY引用定位:用下断点调试辅助分析。
可以发现上面的代码就是比较从lua脚本中获取的字符串是否等于RC4KEY,是的话就用Good!!来覆盖。
所以说我们的rc4加密的key最后是Good!!了。
本地用这个密钥跑一下异或序列,然后再换表base64解码即可。
到这里,题目完了。
最后说一下,上面我是在ubuntu上弄了lua的编译64位与32位环境,弄完我又跑去在centos弄了下,因为开始是在这里弄得,没有成功,现在回去填坑。
因为centos是RedHat系列,它的包管理工具是yum,所以和在ubuntu上有一定的区别。
1 | 一般来说著名的linux系统基本上分两大类: |
开始还是32位的程序要用的依赖:https://blog.csdn.net/kongshuai19900505/article/details/82775688
1 | sudo yum install libgcc*.i686* |
然后还是来到了ubuntu遇到的问题,readline库的安装,64位的还好,直接用下面的命令:
1 | sudo yum install readline-devel |
这样可以编译64位的了。
还是同样的问题,32位的readline的包名字是什么呢,因为这个是yum,所以不是lib32readline6-dev了。
找了半天,无果,最后是猜测是通过后缀来区别的:给它加上.i686试试,
1 | sudo yum install readline-devel.i686 |
真的!!激动小一会。😂。
这其实也是看到64位已经安装的提示有了这样的猜测:
而为什么要加.686呢?
i386对应的是32位系统、而i686是i386的一个子集,i686仅对应P6及以上级别的CPU,i386则广泛适用于80386以上的各种CPU;x86_64主要是64位系统。
经过上面的折腾也熟悉了下apt-get自动清理无用包的命令:
ubuntu下:
1 | sudo apt-get automove #但有一定风险 |
centos下:
1 | yum clean all #清空yum缓存 |