我也想要一份游戏逆向分析技术详解的jquery基础教程详解...

1682人阅读
逆向分析(1)
网络安全-那些事(1)
从本篇起,逆向工厂带大家从程序起源讲起,领略计算机程序逆向技术,了解程序的运行机制,逆向通用技术手段和软件保护技术,更加深入地去探索逆向的魅力。
一、程序如何诞生?
1951年4月开始在英国牛津郡哈维尔原子能研究基地正式投入使用的英国数字计算机“哈维尔·德卡特伦”,是当时世界上仅有的十几台电脑之一。图中两人手持的“纸带”即是早期的程序,纸带通过是否穿孔记录1或0,而这些正好对应电子器件的开关状态,这便是机器码,是一种早期计算机程序的存储形式。
计算机程序是用来实现某特定目标功能,所以需要将人类思维转换为计算机可识别的语言,从人类语言到电子器件开关的闭合,这中间的媒介便是“编程语言”。
“编程语言”大致分为三类:
1、机器语言,又称机器码、原生码,电脑CPU可直接解读,因该语言与运行平台密切相关,故通用性很差,上面提到的利用卡带记录的便属于该类语言;
2、汇编语言,是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在不同的设备中,汇编语言对应着不同的机器语言指令集, 运行时按照设备对应的机器码指令进行转换,所以汇编语言可移植性也较差;
3、高级语言,与前两种语言相比,该类语言高度抽象封装,语法结构更接近人类语言,逻辑也与人类思维逻辑相似,因此具有较高的可读性和编程效率。但是高级语言与汇编语言相比,因编译生成的辅助代码较多,使运行速度相对“较慢”。 java,c,c++,C#,pascal,python,lisp,prolog,FoxPro,易语言等等 均属于高级语言。
学会编程语言各种基本语义语法后,就可以实战了,而实战场所由IDE提供。IDE(集成开发环境Integrated Development Environment)是用于提供程序开发环境的应用程序,目前IDE的种类繁多,不再敖述,只要自己用得顺手、开发效率高、你开心就好。
通过IDE可快速生成程序,根据程序的生成和运行过程,程序大致可分为两类:编译型程序和解释型程序。
编译型程序:程序在执行前编译成机器语言文件,运行时不需要重新翻译,直接供机器运行,该类程序执行效率高,依赖编译器,跨平台性差,如C、C++、Delphi等;
解释型程序:程序在用编程语言编写后,不需要编译,以文本方式存储原始代码,在运行时,通过对应的解释器解释成机器码后再运行,如Basic语言,执行时逐条读取解释每个语句,然后再执行。由此可见解释型语言每执行一句就要翻译一次,效率比较低,但是相比较编译型程序来说,优势在于跨平台性好。
Q : Java属于编译型语言OR解释型语言?
Java首先将源代码通过编译器编译成.class类型文件(字节码),这是java自定义的一种类型,只能由JAVA虚拟机(JVM)识别。程序运行时JVM从.class文件中读一行解释执行一行。另外JAVA为实现跨平台,不同操作系统对应不同的JVM。从这个过程来看JAVA程序前半部分经过了编译,而后半部分又经过解析才能运行,可以说是一种混合型程序,由于该类程序运行依赖虚拟机,一些地方称其为“虚拟机语言”。下图展现各语言之间关系。
硬件-&机器语言-&汇编语言-&系统语言(C和C++)-&解释型语言(python)和虚拟机语言(java),语言的封装程度越来越高,也更加抽象,贴近于人类思维,即“造车前不用再考虑车轮怎么造”。同时,层次越高意味着程序在执行时经历的转化步骤越多,毕竟都要转换为机器语言才能被硬件直接运行,这也是一些高级语言无法应用在效率要求较苛刻场景的原因之一。
Java为了对运行效率进行优化,提出“JIT (Just-In-Time Compiliation)”优化技术,中文为“即时编译”。JVM会分析Java应用程序的函数调用并且达到内部一些阀值后将这些函数编译为本地更高效的机器码,当执行中遇到这类函数,直接执行编译好的机器码,从而避免频繁翻译执行的耗时。
重点看看C\C++语言生成程序的过程及程序是以怎样的形态存储。
上图为c语言程序的生成过程,主要经过编译、链接两大过程。
编译是指编译器将源代码进行词法和语法的分析,将高级语言指令转换为汇编代码。主要包含3个步骤:
1、预处理。正式编译前,根据已放置在文件中的预处理指令来修改源文件的内容,包含宏定义指令,条件编译指令,头文件包含指令,特殊符号替换等。
2、编译、优化。编译程序通过词法分析和语法分析,将其翻译成等价的中间代码表示或汇编代码。
3、目标代码生成。将上面生成的汇编代码译成目标机器指令的过程。目标文件中所存放着与源程序等效的目标的机器语言代码。
链接是指将有关的目标文件彼此相连接生成可加载、可执行的目标文件,其核心工作是符号表解析和重定位。链接按照工作模式分静态和动态链接两类。
静态链接:链接器将函数的代码从其所在地(目标文件或静态链接库中)拷贝到最终的可执行程序中,整个过程在程序生成时完成。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码,静态链接则是把相关代码拷贝到源码相关位置处参与程序的生成。
动态链接:动态链接库在编译链接时只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。程序执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间,根据可执行程序中记录的信息找到相应的函数地址并调用执行。
经过编译链接后,程序生成,windows程序则都已PE文件形式存储。
PE文件全称Portable Executable,意为可移植可执行文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件。 PE文件以段的形式存储代码和相关资源数据,其中数据段和代码段是必不可少的两个段。
Windows NT 预定义的段分别为
.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。这些段并不是都是必须的,另外也可以根据需要定义更多的段,常见的一些加壳程序则拥有自己命名的段。
在应用程序中最常出现的段有以下6种:
1、执行代码段,.text命名;
2、数据段,.data、.rdata 命名;
3、资源段,.rsrc命名;
4、导出表,.edata命名;
5、导入表,.idata命名;
6、调试信息段,.debug命名。
下图为一个标准的PE文件结构。
到此为止,程序就诞生了,如果你对文件形态足够了解,就完全可以向网上的某些大牛一样,纯手工打造一个PE文件。
二、程序如何运行
程序诞生后,我们就可以运行了,也就是双击程序后的事儿(本节重点描述windows平台程序)。需要说明的是,上面产生的程序文件是存储在硬盘(外存)里的二进制数据,当你双击程序后,windows系统会根据后缀名进行注册表查找相应的启动程序,这里我们编译出的是以exe后缀的可执行程序,则系统对程序进行运行。
Q:系统如何运行可执行程序?
系统并非在硬盘上直接运行程序,而是将其装载进内存里,包括其中的代码段、数据段等。
Q:为什么在这会多此一举,把程序复制到内存再执行呢?
内存直接由CPU控制,享受与CPU通信的最优带宽,然而硬盘是通过主板上的桥接芯片与CPU相连,所以速度比较慢。再加上传统机械式硬盘靠电机带动盘片转动来读写数据,磁头寻道等机械操作耗费时间,而内存条通过电路来读写数据,显然电机的转速肯定没有电的传输速度快。后来的固态硬盘则大大提升了读写速度,但是由于控制方式依旧不同于内存,读写速度任然慢于内存。
为了程序运行速率,任何程序在运行时,都是有一个叫做“装载器”的程序先将硬盘上的数据复制到内存,然后才让CPU来处理,这个过程就是程序的装载。装载器根据程序的PE头中的各种信息,进行堆栈的申请和代码数据的映射装载,在完成所有的初始化工作后,程序从入口点地址进入,开始执行代码段的第一条指令。
程序从入口点开始顺序执行,CPU直接与内存中的程序打交道,读取内存中的数据进行处理,并将结果保存到内存,除非代码段中还有保存数据到硬盘的代码,否则程序全程都不会在硬盘中存储任何数据。这就好比我们打开文档编辑器去编译文档,不管输入多少内容,在我们点击“保存”前,硬盘上的程序文件都没有变动,输入的数据都只是存储在内存上,如果此时很不幸断电了,内存上的数据会立刻丢失。为了应对这种尴尬局面,一些编辑软件会定期自动保存新数据至硬盘上,以防意外丢失数据的情况发生。
既然程序在运行时需要加载到内存中才能运行,那么问题来了,对于目前体积越来越庞大的游戏来说,岂不是要把40~50G(可见使命召唤系列)的数据全塞进内存里。在某猫上搜索某品牌电脑,按价格排序后,某款3w RMB的移动工作站的内存也只是32G,这显然不满足一下子装载一款游戏的需求。而查看该游戏的运行配置需求,内存需求也只是几个G而已,这是怎么回事呢?
原来,操作系统为解决此问题:当程序运行需要的空间大于内存容量时,会将内存中暂时不用的数据写回硬盘;需要时再从硬盘中读取,并将另外一部分不用的数据写入硬盘。这样,硬盘中部分空间会用于存储内存中暂时不用的数据,这一部分空间就叫做虚拟内存(Virtual Memory)。其中内存交换、内存管理等详细过程,感兴趣的同学可以查阅操作系统相关书籍。
一些同学看到这,就单纯的认为,调整虚拟内存空间即可变向提高内存空间,从而提升运行速度。硬盘的读写速度远远慢于内存,所以虚拟内存和内存频繁进行数据交换会浪费很多时间,严重影响计算机的运行速度。所以同学们还是要努力学习,早日当上高富帅白富美,换高配置电脑吧。
三、逆向目的和原理
简要了解计算机程序基础知识后,我们进入【逆向工厂】的正题——逆向。
Q:为什么要逆向?
1、破解正版软件的授权
由于一些软件采用商业化运营模式,并不开源,同时需要付费使用。为此这些软件采用各种保护技术对使用做了限制,而一些想享受免费的童鞋则对这些保护技术发起进攻,其中的主要技术便是逆向,通过逆向梳理出保护技术的运行机制,从而寻找突破口。
2、挑战自我、学习提高
crackme是一些公开给别人尝试破解的小程序,制作 crackme 的人可能是程序员,想测试一下自己的软件保护技术,也可能是一位 cracker,想挑战一下其它 cracker 的破解实力,也可能是一些正在学习破解的人,自己编一些小程序给自己破,不管是什么目的,都是通过crackme提高了自身能力。另外, 一些互联网安全公司也会在面试中采取这种形式对应聘者进行测试。
3、挖掘漏洞与安全性检测
一些安全性要求较高的行业,为确保所用软件的安全,而又无法获取源码时,也需逆向还原软件的运行过程,确保软件的安全可靠。另外,挖洞高手在挖掘漏洞时,经常采用逆向手段,寻找可能存在的溢出点。病毒分析师通过逆向,分析病毒的运行机制,提取特征。
4、还原非开源项目
当你想模仿某优秀软件实现某功能时,发现该软件并未开源,而又很难从其他渠道获取该软件的具体技术细节,那么逆向也许会帮你敲开思想的大门。
Q:既然逆向这么神通广大,可以解决很多问题,那么它的原理机制是什么?
“逆向”顾名思义,就是与将源码变为可执行程序的顺序相反,将编译链接好的程序反过来恢复成“代码级别”。这里之所以用到“代码级别”一词,是因源代码编译是“不可逆”过程,无法从编译后的程序逆推出源代码。
“逆向”通常通过工具软件对程序进行反编译,将二进制程序反编译成汇编代码,甚至可以将一些程序恢复成更为高级的伪代码状态。C\C++程序在经过编译链接后,程序为机器码,直接可供CPU使用,对于这类程序我们使用IDA、OD等逆向程序,只能将其恢复成汇编代码状态,然后通过读汇编代码来解读程序的运行过程机制,显然这对于新手来说,直接阅读汇编代码门槛较高,所以一些逆向工具提供插件可以将一些函数恢复成伪代码级别。
相比C\C++这一类编译运行类程序,依靠java虚拟机、.NET等运行的程序,由于所生成的字节码(供虚拟机解释运行)仍然具有高度抽象性,所以对这类程序的逆向得到的伪代码可读性更强,有时甚至接近与源代码。但是在生成字节码的过程中,变量名、函数名是丢失的,所以逆向出的伪代码中这些名称也是随机命名的,从而给代码的阅读制造的一定障碍。而对于这类易反编译的程序,为了保护软件不被逆向,通常采用代码混淆技术,打乱其中的命名,加入干扰代码来设置各种障碍。
至此,我们把程序恢复成了可读代码,如果你仅仅依靠阅读这些代码来梳理程序运行过程,这叫做“静态调试”。与此对应的“动态调试”则是让程序运行起来,更加直观的观察程序的运行过程。经常编写程序的同学在debug时常常用到“断点”,而在动态调试中,断点起着很大的作用,否则程序将不会暂停下来让你慢慢观察各寄存器状态。
Q:“断点”是如何工作的?
x86系列处理器从8086开始就提供了一条专门用来支持调试的指令,即INT 3。简单地说,这条指令的目的就是使CPU中断(break)到调试器,以供调试者对执行现场进行各种分析。我们可以在想要观察的指令处设置一个断点,则程序会运行到该处后自动停下来;“单步调试”则是每条语句后面都会有INT3指令来阻断程序的运行,而这些INT3是对用户透明的,逆向工具并未将这些指令显示出来。
四、反汇编的多样性
现在大多数程序是利用高级语言如C,C++,Delphi等进行编写 ,然后再经过编译链接,生成可被计算机系统直接执行的文件。不同的操作系统,不同的编程语言,反汇编出的代码大相庭径。反汇编工具如何选择?汇编代码如何分析?如何调试修改代码?这些问题都会让刚入门的新童鞋困惑。
下面我们简单对比c++和c#程序反汇编后得到的代码:
图1是c++程序反汇编结果,图2为.net程序反汇编结果,两者功能都只是打印一句话。C++以push指令将字符串压入栈中,而.net以ldstr指令将字符串压入栈中,调用打印函数结束后,.net反汇编代码直接以ret指令返回结束,而c++反汇编代码先平衡完栈,再执行retn指令返回结束。
由此可见,在反汇编过程中,我们确认好程序的编写语言和运行环境,才可选择适当的工具来反汇编程序。在分析反汇编代码时,如果熟悉高级语言的开发、运行过程及其反汇编指令,那更是事半功倍。
五、常用的软件分析工具
对于软件逆向分析,分为静态分析和动态分析,常用的软件如下:
静态分析工具
IDA Pro(Interactive Disassembler Professional )
IDA Pro是总部位于比利时列日市(Liège)的Hex-Rayd公司的一款产品。IDA 的主要目标之一,在于呈现尽可能接近源代码的代码,而且通过派生的变量和函数名称来尽其所能地注释生成的反汇编代码,适用于三大主流操作 系统:Microsoft Windows.Mac OS X 和 Linux。IDA Pro提供了许多强大功能,例如函数的交叉引用查看、函数执行流程图及伪代码等,并且也有一定的动态调试功能。同时,IDA pro可以在windows、linux、ios下进行二进制程序的动态调试和动态附加,支持查看程序运行内存空间,设置内存断点和硬件断点。
IDA Pro是许多软件安全专家和黑客所青睐的“神兵利器”。
c32asm 是款非常好用的反汇编程序,具有反汇编模式和十六进制编辑模式,能跟踪exe文件的断点,也可直接修改软件内部代码 ,提供输入表、输出表、参考字符、跳转、调用、PE文件分析结果等显示 ,提供汇编语句逐字节分析功能,有助于分析花指令等干扰代码。
Win32dasm可以将应用程序静态反编译为WIN 32汇编代码,利用Win32dasm我们可以对程序进行静态分析,帮助快速找到程序的破解突破口。笔者下载的 Win32Dasm还可以附加到正在运行的进程,对进程进行动态调试,但如果原程序经过了加密变换处理或着是被EXE压缩工具压缩过,那么用Win32dasm对程序进行反汇编就没有任何意义了。
VB Decompiler pro
VB Decompiler pro是一个用来反编译VB编写的程序的工具。VB Decompiler反编译成功后,能够修改VB窗体的属性,查看函数过程等 ,VB Decompiler Pro 能反编译Visual Basic 5.0/6.0的p-code形式的EXE, DLL 或 OCX文件。对native code形式的EXE, DLL或OCX文件,VB Decompiler Pro 也能给出反编译线索。
还有对.net程序和delphi程序的静态反汇编分析工具,在以后的章节中会使用到,到时再详细讲解。
动态分析工具
Ollydbg运行在windows平台上,是 Ring 3级调试器,可以对程序进行动态调试和附加调试,支持对线程的调试同时还支持插件扩展功能, 它会分析函数过程、循环语句、选择语句、表[tables]、常量、代码中的字符串、欺骗性指令、API调用、函数中参数的数目,import表等等 ;支持调试标准动态链接库(Dlls),目前已知 OllyDbg 可以识别 2300 多个 C 和 Windows API 中的常用函数及其使用的参数,是 Ring3级功能最强大的一款动态调试工具。
Windbg是Microsoft公司免费调试器调试集合中的GUI的调试器,支持Source和Assembly两种模式的调试。Windbg不仅可以调试应用程序,还可以 对内核进行调试。结合Microsoft的Symbol Server,可以获取系统符号文件,便于应用程序和内核的调试。Windbg支持的平台包括X86、IA64、AMD64。Windbg 安装空间小,具有图形操作界面,但其最强大的地方是有丰富的调试指令。
其它对.net,delphi等程序的动态调试工具在以后的章节中介绍。
系统监视工具:
(免费软件,网络监视和包分析类软件)
Outpost Firewall (共享软件,使用hook技术的Windows防火墙)
ProcExp (免费软件,强大的进程分析软件)
FileMon (免费软件,强大的文件读写监视软件)
RegMon (免费软件,强大的注册表读写监视软件)
反保护工具:
LordPE (Win32 PE文件修改,转存工具)
ImportREC (Win32 PE文件结构修复软件)
AIl versions ASPack unpacker (免费软件,ASPack压缩壳脱壳工具)
UnPECompact(免费软件,PECompact压缩壳脱壳工具)
UPX(自由软件,UPX压缩壳加壳和脱壳工具)
Hedit (共享软件,16进制编辑器)
PEiD (免费较件,软件信息和编写语言分析工具)
以上只是常用的一些程序分析工具,还有很多工具这里没有提到,有兴趣的读者可以根据自身需求查找下载。
六、从hello world说起
为了让大家直观地了解逆向的过程,我们就从大家最初学习编程时的hello world程序开始讲解:
#include &stdio.h&
void main()
printf("hello world!\n");
这是我们编写的打印hello world程序,是不是看起来很亲切,接下来将编译好的hello
world程序用IDA反汇编,生成的代码如下图:
第一行main函数名前面的__cdecl,是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除 。还有__fastcall与__stdcall,三者都是调用约定(Calling convention),它决定以下内容:
1、函数参数的压栈顺序
2、由调用者还是被调用者把参数弹出栈
3、产生函数修饰名的方法
offset Format是将参数压入栈,在这里就是讲要打印的“hello world!\n”压入栈,供printf函数使用,在反汇编程序代码中,如果调用的函数有参数,都是先将函数的参数先用push指令压入栈中,例如:add(int a,int b),调用add函数前,先将参数a和b压入栈,根据 __cdecl调用规则,先push b,再push a,最后再调用add函数。
ds:printf就是调用printf函数打印“hello world“字符。
esp, 4是平衡栈,平衡掉刚才压入的函数参数。
eax, eax将eax寄存器清零。
retn 返回,程序执行结束。
这就是hello world程序的逆向代码分析,只是举一个简单的例子,真正要逆向分析一个较大较复杂的程序还是有一定难度,需要更多的知识与经验。
七、Crackme
crackme(通常简称CM)是用来测试程序设计人员的逆向工程技能的小程序。
KeygenMe、ReverseMe、UnpackMe,KeygenMe是要求别人做出程序对应的 keygen (序号产生器)。
ReverseMe 要求别人把它的算法做出逆向分析。
UnpackMe 是则是要求别人把它成功脱壳 。
分析这些程序都能提高个人的程序分析能力,这些程序都有各自侧重的知识点。
下面就以一个验证序列号的crackme小程序作为例子进行破解,得到正确的序列号。
直接运行程序是这样的
开始破解程序,首先用IDA打开文件
在函数(Function name)窗口中看见CWinApp,CCmdTarget更类,熟悉的同学已经知道该程序使用MFC编写,结合自己的开发经验,就能猜到获取编辑框中的内容用的函数是GetDlgItemText(),定位到调用该函数的位置0×。在之前有三个指令,
在调用GetDlgItemText()之前有三个push指令,
//字符串最大长度
.text:0040154B
edx, [ebp+String]
.text:0040154E
//字符串缓存区
.text:0040154F
//指向输入框控件
ecx, [ebp+var_20] ;
注意到刚才弹框的提示内容“Incorrect try again!!”,可以在IDA字符串窗口中找到,定位到使用该字符串的位置
细心的同学已经发现了,在上面loc_401585代码段处有字符串比较(lstrcmpA),比较完成后有两个分支,一个提示输入正确“Correct way to go!!”,另一个提示输入错误 “Incorrect try again!!”,结合上面获取文本输入框内容的代码段信息可以判断,lpString2和lpString1中有一个存储正确的验证码,另一个存储输入的内容,接下来我们用两种方法让我们的验证码通过验证。
获取正确的验证码
在0040158D
ds:lstrcmpA处设置断点,点击Debugger-&Start Process或按F9开始动态调试,在程序输入框中随便输入一串字符,实验中输入的是‘1qaz2wsx’,然后点击“Check”控件,程序停在我们设置的断点处,然后查看寄存器ecx和edx中的值,所示如下:
如图所示,ecx寄存器存放的是lpString2:‘’,edx寄存器存放的是lpString1:‘1qaz2wsx’,获得正确验证码”’”’,接下来在程序中试验一下:
结果正确!
修改二进制代码
修改PE文件,使输入的内容显示正确。在上一小节,程序比较完lpString1和lpString2有两个分支,一个是正确输入的提示框,另一个是错误输入提示框。修改代码跳转,只要跳转到弹出“Correct way to go!!”代码段就可以了,结合代码,当两个字符串不同时会执行jnz
loc_4015AD指令,跳转到loc_4015AD代码段,将jnz指令改为jz,可在两个字符串不同时跳转到“Correct way to go!!”代码段。jnz的十六进制码为75,jz的十六进制码为74,只需将可执行程序中的75改为74就可以。
通过IDA Pro查看十六进制文件窗口找到该跳转指令
用Hedit打开程序,找到该跳转指令
在二进制的文件中该跳转指令在0×处,而不是IDA显示的0×,发生了什么?这涉及到PE文件内存映射方面的基础知识,童鞋们可查阅相关资料。
将跳转指令75修改为74,保存修改后运行,随意输入一段字符串看运行结果:
结果正确!那么,如果输入原来程序的验证码‘’,结果会是什么?为什么会是这样?
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:81236次
积分:1482
积分:1482
排名:千里之外
原创:49篇
转载:33篇
评论:13条
(10)(15)(10)(20)(7)(2)(5)(11)(1)(11)【图文】软件逆向分析介绍_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
评价文档:
软件逆向分析介绍
上传于|0|0|文档简介
&&软件逆向分析介绍
大小:441.50KB
登录百度文库,专享文档复制特权,财富值每天免费拿!
你可能喜欢手把手教你逆向分析 Android 程序
很多人写文章,喜欢把什么行业现状啊,研究现状啊什么的写了一大通,感觉好像在写毕业论文似的,我这不废话,先直接上几个图,感受一下。
第一张图是在把代码注入到地图里面,启动首页的时候弹出个浮窗,下载网络的图片,苍老师你们不会不认识吧?
第二张图是微信运动步数作弊,6不6?
ok,那我们从头说起
的反编译,相信大家都应该有所了解,apktool、JEB 等工具
我们先看一下 Apk 文件的结构吧,如下图:
1.META-INF:签名文件(这个是如何生成的后面会提到)
2.res:资源文件,里面的 xml 格式文件在编译过程中由文本格式转化为二进制的 AXML 文件格式
3.AndroidManifest.xml:android 配置文件,编译过程依然被转换为 AXML 格式
4.classes.dex:java 代码编译后产生的类似字节码的文件(dalvik 字节码)
5.resources.arsc:具有 id 值资源的索引表(asserts 文件夹中的资源不会生成索引)
6.其他文件:可由开发者自己添加,诸如 assets 等,或者 lib(native so 代码)等目录
(Android 编译打包过程分析参看:)
apk的核心逻辑主要在 classes.dex 中,破解和二次打包也基本上对这个文件做手脚,所以对这个文件的保护也尤为重要。
vc+06qOpPC9wPg0KPHA+ztLDx8rXz8jTwyBhcGt0b29sILmkvt+3tLHg0uujumphdmEgLWphciBhcGt0b29sLmphciBkIC1mIHh4eC5hcGsgb3V0RGlyKFBTOm91dERpciCyu9C0u+HU2rWxx7DEv8K8yuSz9ik8YnIgLz4NCre0seDS67rztcTEv8K8veG5ucjnz8KjujxiciAvPg0KPGltZyBhbHQ9"" src="/uploadfile/Collfiles/82.png" title="\" />
这里,res 里的 xml 和 manifset.xml 都已经是解出后的 xml 了,不是 axml 格式了,res 目录里的 values 目录下的 public.xml 可以看到资源对应的 id
如果命令 java -jar apktool.jar d -f再加入 -r 代表资源文件不反编译,上图的目录中将依然有resources.arsc,xml 都是 axml 格式的,也找不到 public.xml
其实我们主要关注的是 smali 这个目录,里面是按照 android 程序编写的时候 java 文件的目录格式生成的,但是里面的文件并不是 java 格式的,而是 smali 格式的,类似 MainActivity.smali。
那么什么是 smali 文件呢?
1.Smali 是 Android 的 Dalvik 虚拟机所使用的一种 dex 格式的中间语言
2.可以理解为,C 语言和汇编语言的编译与反编译,把 smali 理解为一种汇编语言
我们可以打开一个 smali 文件看看,我们可以使用 notepad++ 打开,然后定一下 smali 语法的高亮显示
将下面内容保存到 C:\Users\用户名\AppData\Roaming\Notepad++下,文件名为 userDefineLang.xml
&code&&notepadplus&
&userlang name=&smali& ext=&smali&&
&settings&
&global caseignored=&no&&
&/global&&/settings&
&keywordlists&
&keywords name=&Delimiters&&&/keywords&
&keywords name=&Folder+&&&/keywords&
&keywords name=&Folder-&&&/keywords&
&keywords name=&Operators&&' ! & ( ) , ; : @ [ ] { }&/keywords&
&keywords name=&Comment&&0#&/keywords&
&keywords name=&Words1&&move move/from16 move/16 move-wide move-wide/from16 move-wide/16 move-object move-object/from16 move-object/16 move-result move-result-wide move-result-object move-exception return-void return return-wide return-object const/4 const/16 const const/high16 const-wide/16 const-wide/32 const-wide const-wide/high16 const-string const-string/jumbo const-class monitor-enter monitor-exit check-cast instance-of array-length new-instance new-array filled-new-array filled-new-array/range fill-array-data throw goto goto/16 goto/32 packed-switch sparse-switch cmpl-float cmpg-float cmpl-double cmpg-double cmp-long if-eq if-ne if-lt if-ge if-gt if-le if-eqz if-nez if-ltz if-gez if-gtz if-lez aget aget-wide aget-object
aget-boolean aget-byte aget-char aget-short aget-short aput aput-wide aput-object aput-boolean aput-byte aput-char aput-short iget iget-wide iget-object iget-boolean iget-char iget-short iput iput-wide iput-object iput-boolean iput-byte iput-char iput-short sget sget-wide sgetobject sget-boolean sget-byte sget-char sget-short sput sput-wide sput-object sput-boolean sput-byte sput-char sput-short invoke-virtual invoke-super invoke-direct invoke-static invoke-interface invoke-virtual/range invoke-super/range invoke-direct/range invoke-static/range invoke-interface/range neg-int not-int neg-long neg-float neg-double int-tolong int-tofloat int-to-double long-to-int long-to-float long-to-double float-to-int float-to-long double-to-double double-to-int double-to-long double-to-float int-to-byte int-to-char int-to-short add-int sub-int mul-int div-int rem-int and-int or-int xor-int shl-int shr-int ushr-int add-long sub-long mul-long div-long rem-long and-long or-long xor-long shl-long shr-long ushr-long add-float sub-float mul-float div-float rem-float add-double sub-double mul-double div-double rem-double add-int/2addr sub-int/2addr mul-int/2addr div-int/2addr rem-int/2addr and-int/2addr or-int/2addr xor-int/2addr shl-int/2addr shr-int/2addr usnhr-int/2addr add-long/2addr sub-long/2addr mul-long/2addr div-long/2addr rem-long/2addr and-long/2addr or-long/2addr xor-long/2addr shl-long/2addr shr-long/2addr ushr-long/2addr add-float/2addr sub-float/2addr mul-float/2addr div-float/2addr rem-float/2addr add-double/2addr mul-double/2addr div-double/2addr rem-double/2addr add-int/lit16 rsub-int mul-int/lit16 div-int/lit16 and-int.lit16 or-int/lit16 xor-int/lit16 and-int/lit8 mul-int/lit8 div-int/lit8&/keywords&
&keywords name=&Words2&&.method .annotation .end
.line .prologue .implements .super .class .source
.locals .parameter .field .local .restart&/keywords&
&keywords name=&Words3&&public annotation method protected static final field private synthetic local&/keywords&
&keywords name=&Words4&&Z V I F&/keywords&
&/keywordlists&
&wordsstyle name=&DEFAULT& styleid=&11& fgcolor=&000000& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&FOLDEROPEN& styleid=&12& fgcolor=&FF0000& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&FOLDERCLOSE& styleid=&13& fgcolor=&FF0000& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&KEYWORD1& styleid=&5& fgcolor=&FF8040& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&1& fontsize=&10&&
&wordsstyle name=&KEYWORD2& styleid=&6& fgcolor=&91A62D& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&2& fontsize=&10&&
&wordsstyle name=&KEYWORD3& styleid=&7& fgcolor=&004080& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&0& fontsize=&10&&
&wordsstyle name=&KEYWORD4& styleid=&8& fgcolor=&FF0000& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&0& fontsize=&10&&
&wordsstyle name=&COMMENT& styleid=&1& fgcolor=&FF8080& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&2& fontsize=&10&&
&wordsstyle name=&COMMENT LINE& styleid=&2& fgcolor=&008000& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&2& fontsize=&10&&
&wordsstyle name=&NUMBER& styleid=&4& fgcolor=&D9006C& bgcolor=&FFFFFF& fontname=&Consolas& fontstyle=&0& fontsize=&10&&
&wordsstyle name=&OPERATOR& styleid=&10& fgcolor=&008040& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&DELIMINER1& styleid=&14& fgcolor=&AF2BFF& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&DELIMINER2& styleid=&15& fgcolor=&AF2BFF& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&wordsstyle name=&DELIMINER3& styleid=&16& fgcolor=&000000& bgcolor=&FFFFFF& fontname=&& fontstyle=&0&&
&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/wordsstyle&&/styles&
&/userlang&
&/notepadplus&
打开 MainActivity.smali 文件,头三行代码大致如下:
.class public Lcom/example/hacktest/MainA
.super Landroid/app/A
.source &MainActivity.java&
smali 语法这里就不介绍了,自己查资料就好 smali 文件语法参考
这里举个例子,我们写个程序,一个 edittext 一个 button,当 edit 里面输入正确的文字的时候,点击 button 才会弹出正确的 toast。
我们看一下,反编译后的关键代码
可以看到这是一个参数为 string,返回值为 boolean 名叫 check 的函数,当输入为&11&的时候才返回 true。
我们可以把第一个红框位置的 if-eqz 改成 if-nez,这样你输入除了11的任何字符都会返回 true。
或者把第二个红框位置的 0x1,改成 0x0,(0代表 true),这样这个函数不管输入什么都返回 true。
OK,这样我们就改完了,我们重新编译:java -jar apktool.jar b -f outDir xxx.apk
(PS:xxx.apk 可以不写,会在 outDir 里生成 dist 目录,编译好的 Apk 在这里面)
签名:可以网上下载工具 autoSign,使用方法略。
安装 Apk 后验证,通过。
但是事情并不总是如我们所愿,有些 Apk 会做一些盗版检测机制,就是为了防止二次重打包
以手机暴风影音为例,当你按照上述步骤反编译,重新编译,签名之后,进入 APP 会出现这个页面,无法正常使用
因为你并没有这个 APP 的正版签名文件(关于签名相关的东西,在后面我再仔细讲)
那么这个原理是什么呢,我们大胆猜测一下,无非就是和上一个例子类似的 check 函数,两个值的对比,那么这个值一定是签名。
我们先通过方法拿到正版手机暴风影音的签名 md5,然后在反编译后的代码中搜索一下这个值。
然而并没有搜到,再换个思路,我们搜索获取签名的这个函数,从而定位关键代码,获取应用签名的 java 代码类似是:
PackageInfo pi = context.getPackageManager.().getPackageInfo(packname,packageManager.GET_SIGNATURES);
对应的smali代码类似是:
Landroid/content/pm/PackageI-&signatures:[Landroid/content/pm/Signature
我们再搜用这段代码搜索,在 StormUtils2.smali 里面找到了,发现在函数 getSignInfo 里面,继续跟踪到 checkPiracy 函数。
看到这个函数发现就和上例中的 check 函数类似了,改一下返回值为 true 就好了。
我们再仔细看看这个函数,发现关键的签名 md5 值被拆开存放了,所以我们才没有搜到,这也是防范搜索的一个举措吧(虽然我觉得并没什么用)
const-string/jumbo v3, &dbbf60f096b326003&
const-string/jumbo v0, &c388a350d1578d5&
好的,修改后,我们再重新编译、签名,验证通过。
(PS:关于签名检测的除了 java 层的,可能还有再 so 里面校验的和服务器验证的方式,在 so 里的用 IDA 打开 so 跟踪修改,服务器验证的抓包查看,再模拟发包重放攻击就好了,这里就不具体介绍了)
2. Android 的签名保护机制到底是什么?
android 禁止更新安装签名不一致的 apk,如果我们修改了 apk 又用别的签名文件签名,肯定是不一致的。
我们从签名工具 autoSign 分析,看一下 sign.bat 文件内容:
Echo Auto-sign Created By Dave Da illest 1
Echo Update.zip is now being signed and will be renamed to update_signed.zip
java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
Echo Signing Complete
看一下 java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk 这行的意义:
以testkey.x509.pem 这个公钥文件和 testkey.pk8 这个私钥文件对 update.apk 进行签名,签名后保存为 update_signed.apk
我们可以看到签名前和签名后比较,签名后的文件中多了一个文件夹&META-INF&,里面有三个文件 MANIFEST.MF 、 CERT.SF 、 CERT.RSA
我们通过 jd-gui 工具打开 signapk.jar,找到 main 函数,通过这个函数跟踪代码
1.addDigestsToManifest 这个函数,遍历 apk 中所有文件,对非文件夹非签名文件的文件逐个生成 SHA1 数字签名信息,再 base64 编码
然后再写入 MANIFEST.MF 文件中,生成文件如下:
Manifest-Version: 1.0
Created-By: 1.0 (Android)
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: AfPh3OJoypH966MludSW6f1RHg4=
Name: AndroidManifest.xml
SHA1-Digest: NaPhUBH5WO7uGk/CfRu/SHsCvW0=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: RRxOSvpmhVfCwiprVV/wZlaqQpw=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: Nq8q3HeTluE5JNCBpVvNy3BXtJI=
Name: res/layout/activity_main.xml
SHA1-Digest: kxwMyILwF2K+n9ziNhcQqcCGWIU=
Name: resources.arsc
SHA1-Digest: q7Ystu6WoSWih53RGKXtE3LeTdc=
Name: classes.dex
SHA1-Digest: Ao1WOs5PXMxsWTDsjSijS2tfnHo=
Name: res/drawable-xxhdpi/ic_launcher.png
SHA1-Digest: GVIfdEOBv4gEny2T1jDhGGsZOBo=
SHA1 生成的摘要信息,如果你修改了某个文件,apk 安装校验时,取到的该文件的摘要与 MANIFEST.MF 中对应的摘要不同,则安装不成功
2.接下来对之前生成的 manifest 使用 SHA1withRSA 算法, 用私钥签名,writeSignatureFile 这个函数,最后生成 CERT.SF 文件,如下:
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: pNZ9UXN9GMqTgqAwKD6uEN6aD34=
Name: res/drawable-xhdpi/ic_launcher.png
SHA1-Digest: cIga++hy5wqjHl9IHSfbg8tqCug=
Name: AndroidManifest.xml
SHA1-Digest: oRzzLkwuvxC78suvJcAEvTqcjSA=
Name: res/drawable-mdpi/ic_launcher.png
SHA1-Digest: VY7kOF8E3rn8EUTvQC/DcBEN6kQ=
Name: res/drawable-hdpi/ic_launcher.png
SHA1-Digest: stS7pUucSY0GgAVoESyO3Y7SanU=
Name: res/layout/activity_main.xml
SHA1-Digest: Yr3img6SqiKB+1kwcg/Fga2fwcc=
Name: resources.arsc
SHA1-Digest: j1g8I4fI9dM9hAFKEtS9dHsqo5E=
Name: classes.dex
SHA1-Digest: Sci9MmGXNGnZ1d04rCrEEV7MWn4=
Name: res/drawable-xxhdpi/ic_launcher.png
SHA1-Digest: KKqaLh/DVvFp+v1KoaDw7xETvrI=
用私钥通过 RSA 算法对 manifest 里的摘要信息进行,安装的时候只能通过公钥解密,解密之后才能获得正确的摘要,再对比
3.最后就是如何生成 CERT.RSA,打开这个文件看到的是乱码,说明整个文件都被编码加密了,而且这个文件和公钥有关
从中看出他是通过 PKCS7 将整个文件加密了
总结:1.签名只是对完整性和签名发布机构的校验机制 2.不能阻止 apk 被修改,只是签名无法保持一致 3.不同私钥对应着不同的公钥,实质上不同的公钥就代表了不同的签名
3. Android 系统如何获取签名
我们从获取下面一段代码开始分析:
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES );
Signature[] signs = packageInfo.
md5 = getMD5Str(signs[ 0].toByteArray());
context.getPackageManager()其实拿到的是ApplicationPackageManager
看一下 context 的实现类 contextImpl 里的 getPackageManager()
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageM
IPackageManager pm = ActivityThread.getPackageManager ();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
可以看到 ActivityThread 中方法 getPackageManager 获取 IPackageManager
继续看 ActivityThread 的代码:
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v(&PackageManager&, &returning cur default = & + sPackageManager);
return sPackageM
IBinder b = ServiceManager.getService(&package&);
//Slog.v(&PackageManager&, &default service binder = & + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v(&PackageManager&, &default service = & + sPackageManager);
return sPackageM
看源码知道是通过 ServiceManager.getService(&package&);获取 PackageManagerService 并得到 IBinder对象,然后通过 asInterface 函数取得接口类 IPackageManager 实例
然后作为参数构造 ApplicationPackageManager
再看 ApplicationPackageManager 这个类里的方法:
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId());
if (pi != null) {
} catch (RemoteException e) {
throw new RuntimeException( &Package manager has died& , e);
throw new NameNotFoundException(packageName);
这里的mPM就是上面构造函数传进来的 IPackageManager,就可以调用 PackageManagerService 的方法了
下图是 PackagerManager 静态类结构图:
ok,看完上图的结构图,继续跟代码 ,看到这里是 mPM 继续调用的getPackageInfo(代理模式),通过进程通信到 PackageManagerService 中执行响应操作
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
if (!sUserManager.exists(userId))
enforceCrossUserPermission(Binder.getCallingUid (), userId, false, &get package info&);
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
if (DEBUG_PACKAGE_INFO)
Log.v (TAG, &getPackageInfo & + packageName + &: & + p);
if (p != null) {
return generatePackageInfo(p, flags, userId);
if((flags & PackageManager. GET_UNINSTALLED_PACKAGES ) != 0) {
return generatePackageInfoFromSettingsLPw(packageName, flags, userId);
这里 mPackages 是 hashMap,其调用put方法的时机是在 scanPackageLI 方法中,而 scanPackageLI 的调用地方是在程序安装和替换函数中,还有就是 scanDirLi 中,代码略。
scanDirLi 是用来扫描一些系统目录的的,在 PackageManagerService 的构造函数中调用的
File dataDir = Environment. getDataDirectory();
mAppDataDir = new File(dataDir, &data&);
mAppInstallDir = new File(dataDir, &app&);
mAppLibInstallDir = new File(dataDir, &app-lib& );
mAsecInternalPath = new File(dataDir, &app-asec& ).getPath();
mUserAppDataDir = new File(dataDir, &user&);
mDrmAppPrivateInstallDir = new File(dataDir, &app-private& );
上面是扫描的位置&&&&&&&
PackageManagerService 处理各种应用的安装、卸载、管理等工作,开机时由 systemServer 启动此服务。就是说之前安装过的应用或者系统应用信息都会在开机扫描过程中存到 mPackages 这个 hashMap 中。开机后用户的安装操作也会同样存到这个 hashMap 里面。
继续看 getPackageInfo,调用的 generatePackageInfo , 里面调用的是 PackageParser 中的 generatePackageInfo,继续跟
这个函数的代码比较长,只贴出部分关键代码:
if ((flags&PackageManager. GET_SIGNATURES ) != 0) {
int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
if (N & 0) {
pi.signatures = new Signature[N];
System.arraycopy (p.mSignatures, 0, pi. signatures, 0 , N);
这段代码之前主要是做一些复制的操作,就是 new 一个 PackageInfo,然后把 PackageParser.Package 对象中的一些内容复制到这个 PackageInfo 中
从这段代码可以看出来,最终得到的 signatures 信息就是中 p(PackageParser.Package )中的成员 mSignatures 中得来的。
好了,现在就是看这个PackageParser.Package是从哪来的了,通过跟踪代码,installPackageLI 和 scanPackageLI 中的 final PackageParser.Package pkg = pp.parsePackag (tmpPackageFile, null, mMetrics, parseFlags);这行代码只是生成了 pkg,并没有赋值里面的 mSignatures,继续跟踪,找到函数 collectCertificatesLI,找到 pp.collectCertificates(pkg , parseFlags)
PS:最终又进行了一系列的跟代码,找到了 JarVerifier.java 这个类的 readCertificates 这个就是用来读取.RSA 文件的,最终我们看到的里面的代码是通过 loadCertificates 取得的 certs 赋值给了 pkg.mSignatures
至此,获取签名的所有逻辑就算是简单的过一遍了,以下是简略流程图:(发现 ppt 画图比 Windows 画图工具好用多了,哈哈)
在跟代码的过程中也找到了签名对比的函数 compareSignatures ,有空自己看看就好了。
OK,绕了这么久我们终于找到源头了,获取签名就是在 META-INF 中寻找,并解析。试想一下,如果我们修改了这个函数,让他解析原来正版的 META-INF 中的 CERT.RSA 文件,这样就可以伪造为真正的签名了。
那么我们就想到了 HOOK,&hook 的原来简单来说就是,找到原函数和新函数的指针位置,然后兑换内容,将新函数替代原函数。
关于 hook 呢,网上也有很多框架可以使用,比如:
1.Cydia substrate :
2.Xposed :
网上也有很多教程可以看看
乌云(wooyun)上有一篇很有意思的教程,就是利用 hook 进行微信运动作弊,原帖地址:
下图就是我用了上面的方法产生的效果,还差点被微信部门的人请去喝茶。
这里用的是 Xposed 框架,原理就是 hook 了手机的计步传感器的队列函数,然后把步数的返回值每步乘1000返回,前提是,你的手机硬件本身有计步传感器功能,这里微信运动里面列出了支持的手机列表:。好像最高就是98800了,可能是微信做了步数限制吧
我这里用的是小米4联通版,发现虽然是1000基数的加,但是好像隔了很久才变化,估计又是 MIUI 做了一些省电策略,传感器的采集做了对齐吧?
Xposed 框架,很多玩机爱好者,会拿它修改一些主题,字体之类的,或者系统界面,定制自己想要的系统插件等等。然而,也有缺点,需要手机root,而且这个框架,还有可能让手机变砖,还有的系统可能对这个框架支持的不好,或者不支持。XDA 里面也有很多大神把 Xposed 对某些机型做了适配,大神一般都是说,如果手机变砖他们不负责,哈哈。
正式由于这些框架的诸多不便,root 等等的问题,于是就有了一些非 root 的 hook 的黑科技,比如阿里巴巴的开源框架 Dexposed()其也是根据 Xposed 框架修改而来的,不过看 github 上他们也好久没更新了。
也有像其他个人写的和这种比较类似的框架,这里就不介绍了。但是这类框架的缺点就是,只能在该进程下hook,不能全局 hook,即只对这个进程的应用起作用,不能对另一个应用起作用,优点是可以 hook 自定义函数也能hook 系统函数,并且不用 root 和重启。阿里用这个框架来打在线热补丁。
那对于 APP 内部签名校验的就不用再搜相应的代码了,直接 hook 就一步到位了,android.app.ApplicationPackageManager 这个类的 getPackageInfo 这个方法直接把正确的签名返回就好了,接下来我们就需要把 hook 的代码注入到某个 APP 里就好了。
4. 关于如何注入?
开篇的时候有个图片就是我在腾讯地图里面注入了一个苍老师的图片其实就是,自己写了个 imageloader,用来下载网络图片,再写个 activity 或者 dialog 来承载这个 imageview,然后编译,再反编译,取出相应的smali等文件,比如贴到已经反编译好的腾讯地图的里面,把开启这个苍老师图片下载的启动代码放到合适位置,最后再把腾讯地图重新打包签名,就ok 了。
hook 代码也是同理注入,验证一下,成功(我这块写的比较粗略,代码比较多,只说思路了)那么这种代码注入和 hook 相结合的方式能干什么呢,我们也不妨搞出点事情来。同样我们还是进行微信运动作弊的事情,其实很多运动类的软件都可以把自己的数据同步到微信运动里,比如小米手环,乐动力,悦动圈等等。
那我们就先拿其中一个开刀吧:
经过一系列的跟踪代码定位,最终定位到了这个类 cn.ledongli.ldl.cppwrapper.DailyStats 里的 f 方法(f 是因为代码混淆了)然后我们注入并 hook 方法,让它返回66666,ok,我们看到了如下效果:
然后我们在应用里面登陆微信账号,和对接到微信运动的功能,发现不好用,是因为,微信里面做了对应用的签名校验,应用的签名已经变了
所以我们只能破解微信了(闷声作大死),同样注入 hook 代码,让微信获取应用的签名的时候取得正确签名,关键代码:
if( packageName.equals( &cn.ledongli.ldl&)){
if ( result instanceof PackageInfo) {
PackageInfo info = (PackageInfo)
info. signatures[0] = new Signature( myHexLedongli);
param.setResult( info);
再把这个盗版的微信重新打包签名,重新进行应用的同步数据操作,再进微信运动看看,是不是已经66666了。至此作弊完成。
♂♂♂♂♂♂♂♂♂♂我是画风不同的分割线♂♂♂♂♂♂♂♂♂♂♂♂
说了这么多的,也该聊聊防破解的了
google 最早给的就是代码混淆的方案,其实一般的混淆只是降低了代码的可读性,让你对反编译出来的函数命名等不知道什么意思,不过解读出来只是时间问题。后来还有资源混淆的,但是意义不大。
后来有了核心代码用 C 实现,写成 SO,加花指令的办法,这个办法确实会阻止一大部分人的继续破解,但是对于经常做逆向的工程师来说也不是什么难题。
其实做这么多大多数软件的初衷就是不想软件被盗版,然后被注入乱七八糟的广告,或者被盗取信息等,后来就有了盗版检测机制。比如:JAVA 层的签名校验,NDK 层校验,分段存放签名 Hash 串,服务器校验等等,但是这些方法我都在上面说了破解方法
现在国内的一般应用市场都有对 APP 签名的检测,在你下载的时候会告诉你这个 APP 是不是盗版的,从而让用户区分出来。但是应用市场自己本身又被盗版了怎么办呢?
再后来,就有了像360加固保和腾讯的乐固等产品,so 做了加密,真正的 dex 也藏起来了,不过个人觉得,就算真正的 dex 也需要变成 odex 了,root 的手机取到 odex,再转回 dex,就能拿到真正的 dex(虽然我没试过,但是我觉得可能是一个思路),所以这个方法就更难破解了
虽然加固产品很厉害,但是也会有他的缺陷,Android 系统不断的更新升级,也许就换了某些模式等等,比如 ART 刚出来的时候,加固保加固后的 Apk,在 ART 模式运行下就会 Crash。这些加固产品要不断的适配各种型号的手机,CPU 类型,运行模式等等,所以很多 APP 为了考虑兼容性,他们也不会轻易去加固自己的产品。
♂♂♂♂♂♂♂♂♂♂我是画风不同的分割线♂♂♂♂♂♂♂♂♂♂♂♂
关于逆向破解 Android 应用,我觉得耐心很重要吧,代码跟来跟去确实很枯燥,总结几点小技巧吧
1.信息反馈:通过界面的一些弹出信息,界面特点寻找突破点
2.特征函数:比如搜 Toast,Log,getSignature 等
3.代码注入:把 toast 或者 log 函数注入到程序中,跟踪位置
4.打印堆栈:插入 new Exception(&定位&).printStackTrace();
5.网络抓包:通过抓包得到的关键字段,在代码中定位
写在后面:
这篇文章整理了有一段时间了,觉得还是应该写出来,也不是什么高深的技术文章,就是个人总结的一点心得而已。
关于破解应用很多人可能会去破解别人的应用注入广告来获取利益,也有可能盗取别人的信息。
不过我们作为有节操的开发工程师,应该本着瑞雪的精神看待技术,学习技术,而不是乱♂搞。但是我们也应该知道,我们的应用有可能会被别人怎么搞&&
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'}

我要回帖

更多关于 jquery基础教程详解 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信