Aurore💦 我不是Huai人的
关注数: 21 粉丝数: 164 发帖数: 5,240 关注贴吧数: 21
数据分析师的前景到底怎样? 鉴于近几年这大数据行业越来越火,不少小伙伴就想转行成为数据分师,那数据分析师就业前景怎么样?零基础能成为数据分析师吗? 1、数据分析师就业前景怎么样?行业人才饱和度如何? 首先,想通过成为数据分析师赢得一份稳定的工作,是可行的。之所以这样说,主要还是根据这个行业发展的现状决定的。 一方面,数据分析师这个岗位,是IT行业中的新兴岗位,正处于行业红利期,人才缺口巨大。目前,重视数据分析的企业越来越多,招聘需求量越来越大;同时,由于数据分析行业在我国起步较晚,因此专业人才供给量严重不足。据数联寻英发布的《大数据人才报告》称:目前我国专业的数据分析师仅46万,在未来3到5年内,这类人才的缺口将高达150万。对于职场新人和转行小白来说,这无疑是进入数据分析行业的绝佳时期。 另一方面,数据分析行业是IT行业的分支,薪资待遇丰厚。据此前报道,美国的数据分析师平均年薪高达17.5 万美元,而国内顶尖互联网公司,数据分析师的薪酬要比同一级别的其他职位高20%至30%,且颇受企业重视。即使你是刚入行的数据分析师,也能拿到7K~13K的高薪。 2、转行数据分析行业,应届生成功率高不高呢? 听完数据分析行业的就业情况,我才很你肯定要问:应届生对于转行数据分析几乎是零基础,从事这个行业的工作,能成功吗?成功率高不高? 通过查看各大招聘网站,企业在招聘初级数据分析岗位时,往往对“专业限制”、“经验年限”等要求都限制较少。从这一点上,可以看出,如果你能掌握一定的数据分析技能,具备数据分析能力,就能找到数据分析相关的工作任职。 由此可见,无论大学期间,你所学习的是何种专业,都不影响你转行从事数据分析行业。 写在最后 在互联网技术不断升级的今天,数据获取变的越来越容易,数据分析师也逐渐成为了各大企业追逐的宠儿,数据分析师就业前景也会越来越好。
代码保护-- 几款加壳工具 Virbox Protector(商用) 分带授权的版本和独立壳。带授权的版本加壳后需要绑定许可,许可控制软件能否用,加壳保护安全。独立版的话就只是对代码做加壳,防止代码反编译。 碎片代码执行、外壳加密、混淆、数据加密。 服务商提供了较为完善的文档以及加密方式,提供了较为充分的产品管理平台,以及云端网络加密,并且对于开发者免费使用。 使用评价: 简单下载使用了一下,提供的功能很多,并且管理平台较好。比较推荐这一个软件。 DRMsoft EncryptEXE(有破解版) 加密模式: 非绑定模式 ---- 加密后的文件不绑定用户电脑,但用户需要一个开启密码才可以打开 绑定模式 ---- 一机一码授权,加密后的文件不同用户电脑需要不同的开启密码 无密码模式 ---- 加密后的文件无需要开启密码即可运行,仅对原始文件做加密保护 一码通模式 ---- 采用相同秘钥和产品编号加密的不同文件,在同台电脑上只需认证一次 特点: 可以设置加密后文件的运行次数和有效期; 可以设置加密文件运行过程中锁定用户键盘; 可以设置加密文件运行中禁用鼠标右键; 可以设置用户提示语,在用户打开之前显示给用户; 可以禁止拷贝、编辑、打印; 可以禁止虚拟机运行; 可以设置加密后的文件只能从命令行打开运行,以便只有你自己的程序可以调用他; 可以禁用打印机; 可以检测用户电脑是否开启远程桌面服务并终止运行 EXECryptor(有破解版) 有支持代码和资源的压缩;压缩功能主要用来减少带宽占用和加快下载速度。支持多种文件格式,在保护代码中插入不同的处理器指令或代码片段,并使用其它指令替换,这些指令的运算结果都是相同的。是在 CPU 指令层面上混乱代码而不是在应用层上。 网络评价兼容性不是很好,但是加密安全性表现良好。 VProject(有破解版) 原创虚拟机保护引擎、随机指令集、随机填充代码、代码乱序执行、外壳保护、反内存转储存、区段合并、资源加密、反调试、防修改。使用拟机乱序引擎,可以阻止绝大多数人逆向分析。使用SDK,进行重点加密。所以基本上不影响程序运行效率自带授权系统,正常用户管理系统,黑名单,加密SDK,授权API等实用功能,分析使用Vprotect保护后的程序,将不仅仅是一项技术活,同时也会成为高强度的体力活。 Axprotect(商用) 加密方式: 在运行时自动解密和重新加密软件的技术,为软件提供自动防护。该技术目前支持多种操作平台,诸如Windows,Linux,Mac OS,.NET 及Java应用程序等等。所有的用户收到的是相同的被保护软件版本。可以针对不同用户购买的不同功能模块或者产品类型,个性化的生成对应的许可信息。您有权决定客户以何种方式接受许可,或者将许可存放至CmDongle加密狗中发放给客户,或者通过CmAct许可文件进行发放。可以对客户分配相应的许可类型,例如本地单用户许可、网络并发许可、时间限制型许可(适用于软件测试版)。 特点: Protection 保护工具 用于实现高安全强度的保护软件,防止盗版及逆向工程 Licensing 授权工具 用于实现便捷、安全的软件授权,包括创建灵活的授权模式、整合软件的业务流程,以及采用企业现行的办公后台系统对软件所有的生命周期进行完整的管理。 Security 安全工具 监控软件最终使用者,防止来自第三方的恶意篡改及攻击。 Vmproject(商用软件、长期更新) 虚拟机保护机制,安全系数较高,破解难度大 VMProtect允许对可执行文件(EXE、SCR)、动态链接库(DLL,OCX,BPL)和驱动程序(SYS)进行保护。VMProtect允许对32位和64位应用、库和驱动进行保护。MProtec保护的文件可以在几乎任何版本Windows OS上运行,自Windows 95开始。32和64位版本都被支持,VMProtect兼容DEP和UAC。VMProtect允许生成和验证序列号。手动和自动生成都被支持。序列号可以是有限的时间或日期和硬件锁定的,而免费升级期间也可以被限制。 VMProtect确保它实际上无法运行没有一个序列号代码。 网络评价: 加密的安全级别很高,破解难度很大,但是加密数据多,可能会影响系统的性能。
01 编译简介 在学习逆向之前,我觉得很有必要了解一下编译原理。编译是将源代码转换成目标代码的过程及动作,通常是将高级语言转变成汇编语言或机器语言。 在这里,首先简单介绍一下高级语言、汇编语言以及机器语言,然后再介绍APK的编译过程,为后续逆向打下基础。 当然,编译涉及到的知识远远不止这些,这里我只是结合APK的编译过程,选择性地介绍一些需要的知识而已。避免介绍过多的编译知识,反而导致大家无法理解的情况发生。 如果有兴趣,可以去查阅相关书籍,了解更多关于编译的知识。 02 编译详解 编译 编译通常涉及到三种语言:高级语言、汇编语言、机器语言。 首先大家要明确的是,计算机只能理解二进制,它并不理解用C、Java、Python等高级语言写的源代码。在我们眼里,这是相加,这是调用函数;但在计算机眼里,这就是屎。 就和之前《网络层 | 网际协议IP(1)》介绍的”点分十进制“一样,高级语言只是对人类友好,方便人类阅读而已,如果也想让计算机理解,就必须将高级语言解释成计算机能理解的机器语言,这就是编译的作用。 机器语言:二进制指令集,计算机能直接识别和执行,也被称为机器码。比如0101代表加,1010代表减等等。它与硬件结构相关,不同种类的计算机,其机器语言是不相通的。所以按某种计算机机器指令编写的程序不能在另一种计算机上执行。 由于机器语言是由一系列0和1的构成的指令代码,对人类来说,可读性差且容易出错,于是便产生了汇编语言。 汇编语言:机器语言的符号化,所以又称为符号语言。用助记符来表示机器代码,比如机器语言0101写成汇编语言就是add,这样就比机器语言更加便于记忆,也更形象。 汇编语言是面向机器的,是机器语言的符号化,因此和机器语言一样,不同的计算机有着不同的汇编语言,它的通用性和可移植性也很差。汇编代码需通过汇编器转译成机器代码才能执行。 由于汇编语言依赖于硬件,且助记符量大难记,于是人们又发明了更便于使用的所谓高级语言。 高级语言:像Java、C、C++等都是高级语言,高度封装。相对汇编语言来说,更接近自然语言和数学公式,有较高的可读性,且和计算机硬件结构关系不大,可移植性高。 一般我们都是使用高级语言编写代码。高级语言写的源代码会被编译器编译成目标代码,目标代码通常是汇编代码或者机器代码。如果编译产出的是汇编代码,则用汇编器进一步将汇编代码汇编成机器代码。 绝大部分情况,编译的“故事发展”路线大致可以简化成如下两条: 源代码——》机器代码 源代码——》汇编代码——》机器代码 但是APK走的是另外一种路线。 源代码——》字节码——》机器代码 字节码,是编译产生的中间代码,需要通过特定的虚拟机将字节码转译成物理主机的机器代码。对虚拟机来说,字节码就是虚拟机的机器码,但不是物理主机的机器码。 这样做有什么好处?不同机器,其机器代码都是不一样的。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标语言。而将源代码编译成字节码后,只需要安装特定的软件和软件环境,就可以将其转译成机器代码。只需要编译一次,而不需要考虑平台的问题,平台的差异全部交给软件去处理。Java就是采用这种方式,因此可以”一次编译,到处运行“。 举个例子,生产红、蓝、绿三种颜色的纸张。如果采用寻常的编译方式,则需要三个车床分别生产;而采用字节码的编译方式,我只需要一个生产白色纸张的车床,然后交给第三方去染色就行了。这个例子应该很形象吧? APK编译 目前android应用开发主流语言是Java,还有近几年被官宣的Kotlin,当然还支持其他语言,这里我们只讲Java。 首先给出APK编译打包的流程图。这里我将APK的编译过程简化成如下: 源文件——》.class文件——》.dex文件——》打包成APK——》签名 首先是Java源文件编译成.class文件,这里大家应该是没有疑问的。因为这是Java的编译方式,Android应用是用Java写的,理所当然要经过这一步。 虽然Android应用是用Java开发,但它并没有采用JVM,而是采用了Dalvik虚拟机。 什么?JVM和Dalvik虚拟机是什么? 上文提到字节码需要特定的虚拟机去转译成机器码,JVM就是Java Virtual Machine,即Java虚拟机,用来将Java编译后的.class字节码文件转译成机器码。 而Dalvik虚拟机是用于Android平台的虚拟机,但它支持的是.dex字节码文件(这种格式更适合Android平台运行),因此需要将.class文件转换成.dex文件。dex即Dalvik Executable,Dalvik可执行文件。 将源代码转译成.dex格式后,然后同其他文件(图片、配置文件等)一起打包成APK。到这一步,APK文件已经生成了。 最后,Android应用程序需要签名才能在Android设备上安装。所以还需要对生成的APK文件签名。 当然,APK编译打包不止这五个步骤,也不止这么点细节,但我觉得了解这五个步骤差不多足够了,如果小伙伴们有需要的话,我也会适当补充的
逆向工程:扫雷辅助的研究——0秒实现一键自动扫雷 前言 为了给学生讲解游戏辅助的原理,临时起意写了一个扫雷游戏的辅助程序,可真正实现0秒一键自动扫雷等的功能。纵观网上及冀云前辈那本书上的文章,发现已有的资料中存在有这么几个问题:1、无法实现0秒扫雷,都是至少需要1秒才能完成任务;2、雷区究竟是在游戏初始化时就设置好了,还是要等玩家点击了第一处雷区方块才进行设置?3、已有的资料中实现一键扫雷或者地雷自动标记,都是针对于特定的雷区,比如只针对高级模式,换一个模式就不能用了,更别提自定义雷区了。而我采用的方法则完美解决了上述所有问题。 我们本次研究的对象是在Windows系统中有着悠久历史的扫雷游戏。本次研究欲达成的目标有三个,一个是实现让时间停止走动,即达到0秒完成扫雷任务;二是实现在雷区中自动标注地雷分布;三是实现一键扫雷。所使用的工具有Visual Studio 2013、x64dbg以及Cheat Engine 6.3。 时间停止的实现 我们知道,当进入扫雷游戏,点击雷区方块开始扫雷的时候,雷区右上角的时间就会开始走动,直到玩家踩到地雷或者将地雷全部标出,时间才会停止,而游戏中的“扫雷英雄榜”也正是依据玩家扫雷耗时的长短进行排名的,而如果有某个玩家能够以0秒就完成扫雷任务,那无疑就会排在第一位。但正常来说这是一件不可能的事情,因此就需要以技术的手段实现我们的目标。 这里我们首先需要找出究竟是哪块内存区域保存有这个时间。启动扫雷和CE,将CE挂载到扫雷进程中(winmine.exe):然后我们需要进行第一次的数值搜索,目前时间是0,那么我们就先搜索0,进行First Scan:这里会搜索出很多的结果,我们看不出来哪个地址保存有时间数值,因此不妨开始游戏让时间走动,以进行下一次的搜索。但是考虑到时间一旦走动,我们很难让时间停止进行精确数值的搜索,因此我们这里不妨在Scan Type这里设置一下,比如选择Smaller than,然后在Value里面填进5,意思是搜索小于5的数值。此时我们开始游戏,在5秒前回到CE进行第二轮的搜索。利用这样的思想,就可以找到保存时间的地址了:可以看到,这个地址是0x0100579,我们双击这个结果,在下方出现的数据中单击右键,选择Find out what accesses this address,就可以查看是哪条语句访问了这个地址,换句话说,也就是查看时间是由哪条语句控制的:于是可以看到如下结果:可以发现,前两条语句明显与时间相关,因为它首先是将时间与0x3E7相比较,也就是十进制的999,意思是如果时间未超过999秒这个上限,就继续向下执行。第二条的inc语句是一个自增,控制时间的增加。因此如果想让时间停止增加,就可以将这条自增语句删掉,或者nop掉。所谓的nop掉,意思是将inc语句修改为nop语句,也就是什么都不执行,采用这样的方式可以有效删掉原始语句的功能,且不会影响程序其它逻辑功能的实现。nop的汇编代码是0x90。因此我们需要把位于0x01002FF5位置的FF 05 9C 57 00 01这六个字节的代码全都修改为0x90。可利用如下代码实现: #include <stdio.h> #include <windows.h> int main() { DWORD Pid; HANDLE hProcess = 0; DWORD timeAddress = 0x0100579C; DWORD incAddress = 0x01002FF5; WORD Time = 0; char inc[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; DWORD result1; DWORD result2; HWND hWnd = FindWindow(NULL, L"扫雷"); if (hWnd != 0){ GetWindowThreadProcessId(hWnd, &Pid); hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (hProcess == 0){ printf("Open process failed."); return 0; } result1 = WriteProcessMemory(hProcess, (LPVOID)timeAddress, &Time, 2, 0); result2 = WriteProcessMemory(hProcess, (LPVOID)incAddress, &inc, 6, 0); if (result1 == 0){ printf("Time modify failed."); return 0; } else if (result2 == 0) { printf("Inc modify failed."); return 0; } else { printf("Modify Success."); } CloseHandle(hProcess); } else { printf("Failed"); } return 0; } 考虑到扫雷开始以后,就会直接从1秒开始计数,因此我除了要将inc语句nop掉以外,还需要将保存时间的内存数据修改为0。这样,当我们运行游戏后,随意点击一个方块开始扫雷,然后启动辅助,就能够实现时间停止,并且计数器归0的操作了:于是就实现了0秒达成扫雷任务。可是采用这样的方式,我们只能在扫雷游戏开始之后才能使用辅助,而不能在游戏界面刚开始弹出的情况下使用,因为这样一来虽然可以nop掉inc函数从而实现时间停止,但是时间会停留在1秒的位置,只有再次使用辅助,修改内存中的时间将其置零,才能够真正达到0秒扫雷的效果。也就是说,当我们第一次点击雷区方块的时候,其实还有一处语句实现了当第一次按下雷区时,将时间置1的操作,所以我们有必要将这条语句找出来,将其修改掉。这里我们可以通过x64dbg来将其找出。 运行x64dbg,将扫雷程序加载进来,然后按下F9运行程序,直至扫雷界面弹出。接下来我们在之前发现的用于保存秒数的0x0100579C地址位置下一个硬件写入断点:然后我们回到扫雷,随便在雷区一个位置点击鼠标左键,就可以看到扫雷被断下来了:由截图中可以看到,内存区域0x0100579C已经变成了1,正待执行的指令是位于0x01003836位置的call winmane.10028B5,刚刚执行完的指令是位于0x01003830位置的inc,也就是说,正是这个inc语句,响应了我们的首次鼠标点击(左键按下)的操作,将时间置1。 对于这个语句,我们可以采用同样的思路,也就是将其nop掉,来彻底实现计数器不运行的效果。修改之前的代码如下: #include <stdio.h> #include <windows.h> int main() { DWORD Pid; HANDLE hProcess = 0; DWORD incAddress1 = 0x01002FF5; DWORD incAddress2 = 0x01003830; char inc[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; DWORD result1; DWORD result2; HWND hWnd = FindWindow(NULL, L"扫雷"); if (hWnd != 0){ GetWindowThreadProcessId(hWnd, &Pid); hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (hProcess == 0){ printf("Open process failed."); return 0; } result1 = WriteProcessMemory(hProcess, (LPVOID)incAddress1, &inc, 6, 0); result2 = WriteProcessMemory(hProcess, (LPVOID)incAddress2, &inc, 6, 0); if (result1 == 0 || result2 == 0) { printf("Inc modify failed."); return 0; } else { printf("Inc modify Success."); } CloseHandle(hProcess); } else { printf("Failed"); } return 0; } 这样就可以实现在雷区初始化完毕后,直接将计数器永远置零的操作了 自动标注地雷分布的实现 我们这里接着之前的讨论,在保存有秒数的内存位置下断点以后,点击雷区中的一个方块,扫雷游戏就被断下来了:结合这张图有件事需要说明的是,对于扫雷游戏而言,当我们在雷区方块中按下鼠标左键时,黄色笑脸的嘴会变成“o”型,并且雷区方块也会呈按下的状态,直至松开鼠标左键,即发生鼠标左键抬起的操作时,该方块会被打开,同时黄色小人也会变回笑脸的样子。知道这点很重要,因为我们目前正处于鼠标左键按下的响应代码里面。因此我们现在不妨单步执行,看看什么时候扫雷界面会发生变化。 可以发现,当我们执行完位于0x010038B1位置的call语句后,雷区我们按下的位置出现了一个1:说明刚才执行的那个call语句,就是用来计算雷区的函数,而用于生成雷区的函数,应该不会在它之后的。所以不妨在0x0100384F的位置下一个断点,然后重新载入扫雷游戏。这里我们不妨打开第1行的第3个雷区方块,然后单步走,查看雷区情况。我们发现,位于0x01003860位置开始的两个赋值语句,是将地址0x01005118以及0x0100511C位置的数据赋给eax和ecx,所以我们不妨看一下这两个地址里面的内容:可以看到,一个保存的是03,一个是01,初步怀疑,这两个地址保存的是我们鼠标所点击的雷区坐标。为了进一步确认这个猜想,不妨重新载入扫雷,这次我们选择的是第2列的第4行,查看一下相关位置的内存情况:可以看到我们的猜想是正确的。总结一下,我们应该对数据敏感,应该故意输入一些特定的数据,以方便我们调试查找,每遇到与内存操作相关的指令,不妨进入到该内存空间看看保存了什么东西,然后通过大胆的猜想与细心的推理,往往能够得到重要的线索。 继续单步执行,来到0x0100389B的位置:由刚才的发现我们知道,我们所点击的行数会保存在ECX寄存器里面,而列数会保存在EAX寄存器里面。由这部分反汇编代码可以看到,这里将ECX也就是行数赋给了EDX,然后利用逻辑左移的方式移了5位,得到了0x80的结果。由目前的信息来看,我们不知道为什么要这么计算,因此先不管它。但是当前执行的指令却用到了EDX,也就是将EDX与EAX相加然后再加上0x1005340,将运算结果当作内存地址,取出这个地址中的内容。由于说EDX和EAX的内容是通过我们的输入所得到的,因此可以知道,这里的基地址是0x1005340,而EDX+EAX的值很可能是偏移。我们不妨看一下内存0x1005340中的内容:这里首先可以看到,位于0x010053C2的位置,也就是EDX+EAX+0x1005340的地址处的数据是绿色的00,然后整片内存区域以0F为主,另外10似乎是围出了一个矩形区域。在这个矩形区域中,除了10和0F之外,还有10个8F,这与初级扫雷的默认雷数一致。如果我们再往上看16个字节的数据,又会发现一些新的有意思的数据:可以看到,从0x01005330开始,这里的一行绿色数据包含有0x0A、0x09以及0x09这三个数值,很明显这三个数据正是当前雷区的地雷数量以及宽、高等信息。这里我们怀疑0x10用于表示边界,那我们不妨将这块数据复制出来,利用文本工具编辑整理一下,得到如下结果:然后我们点击继续运行(F9)来看一下:再看一下雷区情况:进行提取整理:对比发现,雷区中的数字是以ASCII码的形式呈现的,由此便验证了我们之前的所有猜想。利用上述分析思想,如果玩家用鼠标右键标注了旗子,则标注旗子的地方在内存中会以0x8E进行标记。大家也可以自行尝试不同雷区在内存中的编排情况,原理与上述初级雷区是完全一致的,这里不再赘述。 现在我们已经知道雷区的起始位置在0x01005340,那么它的结束位置在哪里呢?也就是说,程序会在内存中开辟多大空间来保存雷区的数据?为了找出答案,我们可以看看扫雷游戏的雷区极限大小是多少。这里可以选择菜单栏中的“游戏”->“自定义”,可尝试将高度和宽度都设置为99,然后查看雷区情况:可以发现,尽管我们设置的是99,但是游戏却自动给我们将这两个数值变为了24和30,也就是说这就是雷区的极限值。然后再查看一下内存中的雷区情况:因为我们已经知道0x10代表边界,而最后一个0x10即边界点位于0x0100567F,于是就可以知道,包含有边界的雷区的分布会在0x01005340到0x0100567F之间,这之间共有832字节的数据信息,一会在程序中会使用到这个数值。 然后还有个小问题不妨也研究一下,那就是雷区的分布究竟是在窗口初始化的时候就已经规划好的呢,还是说是在我们随意点击一个雷区方块的时候才分配的呢?我们依旧载入初级难度,先看一下初始化后的内存情况:可以发现,当前雷区中的第一个格子就是地雷,可是当我们点击雷区第一个方块以后,内存情况如下:可以看到,第一个格子的数据变成了41,表明以这个格子为中心的3×3范围内有一个地雷,而这个地雷的位置就在它的右边,这与初始化时候的分布完全不一样。于是也就说明了,程序初始化的时候确实会进行雷区的分布,但是在我们随意点开一个雷区方块以后,又会进行雷区的重新分配。所以,为了获取真实雷区的情况,必须要先点开一个雷区方块才可以。但是经过测试,有一种情况下,雷区不会重新分配。也就是在雷区初始化以后,我们将雷区中任意一个方块标记成一个地雷的话(点击鼠标右键),雷区就不会重新分配了。关于这点,也可以通过上述方法进行验证,这里不再演示。 综上,我们如果想将雷区中的地雷全部标上小旗,原理就是首先获取整个雷区的数据,将其中的0x8F(地雷)替换为0x8E(小旗),然后写入内存,刷新界面即可。完整程序如下: #include <stdio.h> #include <windows.h> int main() { DWORD Pid = 0; HANDLE hProcess = 0; // 控制时间自增的指令地址 DWORD incAddress1 = 0x01002FF5; DWORD incAddress2 = 0x01003830; // 将时间自增NOP掉的6字节指令 char inc[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; DWORD result1,result2; PBYTE pByte = NULL; DWORD dwHeight = 0, dwWidth = 0; // 获取扫雷游戏对应的窗口句柄 HWND hWnd = FindWindow(NULL, L"扫雷"); if (hWnd != 0){ // 获取扫雷进程ID GetWindowThreadProcessId(hWnd, &Pid); // 打开扫雷游戏获取其句柄 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (hProcess == 0){ printf("Open winmine process failed."); return 0; } // NOP掉时间自增的语句 result1 = WriteProcessMemory(hProcess, (LPVOID)incAddress1, &inc, 6, 0); result2 = WriteProcessMemory(hProcess, (LPVOID)incAddress2, &inc, 6, 0); if (result1 == 0 || result2 == 0){ printf("Inc modify failed."); return 0; } else{ printf("Inc modify Success."); } // 存放雷区的起始地址 DWORD dwBoomAddr = 0x01005340; // 雷区的最大值(包含边界) DWORD dwSize = 832; pByte = (PBYTE)malloc(dwSize); DWORD dwTmpAddr = 0; // 读取整个雷区的数据 ReadProcessMemory(hProcess, (LPVOID)dwBoomAddr, pByte, dwSize, 0); BYTE bClear = 0x8E; int i = 0; int n = dwSize; while (i < dwSize) { if (pByte[i] == 0x8F) { dwTmpAddr = 0x01005340 + i; WriteProcessMemory(hProcess, (LPVOID)dwTmpAddr, &bClear, sizeof(BYTE), 0); n--; } i++; } // 刷新扫雷的客户区 RECT rt; GetClientRect(hWnd, &rt); InvalidateRect(hWnd, &rt, TRUE); free(pByte); CloseHandle(hProcess); } else { printf("Get hWnd failed."); } return 0; } 利用这个程序,我们在打开扫雷游戏之后,不论雷区的大小是怎样的,运行我们的辅助程序,就可以0秒实现地雷的自动标注了:一键扫雷功能的实现 由上述分析可见,扫雷程序并没有因为我们标记出了所有的地雷而判定我们任务完成。这是因为我们仅仅是将雷区数据显示出来了,而没有真正“激活”扫雷程序,可以理解为,扫雷程序并没有检测到我们鼠标在雷区的点击操作,因此我们目前这种表面化的功夫并不算数。因此,为了实现一键扫雷,我们需要在雷区模拟鼠标的点击,也就是鼠标左键按下与抬起的操作。让程序模拟鼠标点击所有的非雷区域,这样就可以完成一键扫雷了。 由于我们的Windows应用程序基本都是基于消息机制的,因此我们可以通过利用SendMessage()这个API函数,通过指定的窗口句柄将消息发送给指定的窗口,也就是在获取到扫雷的窗口句柄后,就可以利用这个函数向该窗口发送鼠标按键消息,从而实现模拟鼠标的操作了。该函数的定义如下: LRESULT SendMessage( HWND hWnd, // handle to the destination window UINT Msg, // message WPARAM wParam, // first message parameter LPARAM lParam // second message parameter ); 在这个函数中,第1个参数hWnd是要接收消息的窗口句柄,这里是我们之前所获取到的扫雷窗口句柄;第2个参数Msg是要发送消息的消息类型,这里因为我们要模拟鼠标的按键操作,因此使用WM_LBUTTONDOWN模拟鼠标左键的按下操作,使用WM_LBUTTONUP模拟松开鼠标左键的操作;第3和第4个参数是消息的两个附加参数,其中第3个参数我们这里使用MK_LBUTTON,表明是鼠标左键的操作,第4个参数是鼠标按下的坐标,也就是x轴和y轴的位置坐标。关于这个坐标,MSDN的说明如下(http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fwindows%2Fdesktop%2Finputdev%2Fwm-lbuttondown&urlrefer=243824329d25780c7c6f20bf0db40826): The coordinate is relative to the upper-left corner of the client area. 说明该坐标是基于客户区的左上角,但是这个客户区是怎么定义的呢:如上图所示,究竟A点是客户区的左上角,还是说B点才是呢?如果A点为坐标原点,那么第一块雷区的坐标就应为(AC,CE),如果B点为坐标原点,那么第一块雷区的坐标就应为(BD,DE)。经过实际测试,MSDN中所谓的客户区,其实是以B点作为起点的位置,即原点坐标(0,0),而雷区中心即E点的坐标为(16,61),每个雷区小方块的大小为16×16,于是可以知道,这里需要循环计算出雷区每一个小方块的坐标,这个坐标与保存有雷区的二维数组下标紧密相关。假设这个二维数组是mine[y1][x1],其中y1表示的是雷区有多少行,x1表示雷区的列数,那么每个雷区方块的坐标为: x = x1 * 16 + 16; y = y1 * 16 + 61; 在获得了坐标以后,就可以通过如下语句来模拟鼠标的点击操作了: SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y)); SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y)); 需要说明的是,SendMessage函数的第四个参数只能接收一个参数,但是我们的坐标是两个数值,因此需要将两个数值合并成一个数值,也就是使用MAKELONG函数(http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fwindows%2Fdesktop%2Fwinmsg%2Fwindow-macros&urlrefer=bc8b3240adde55d1983472792ef07a8e),将两个16位数据组合成一个32位的数据。因此接下来的工作就是获取雷区的二维数组,从而得到x1和y1的值。 经过之前的分析我们知道,扫雷游戏的雷区长宽数据会保存在雷区上方,即内存0x01005330的位置,可用如下代码获取雷区大小的数据: DWORD dwInfo = 0x01005330; DWORD dwHeight = 0, dwWidth = 0; ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 4), &dwWidth, sizeof(DWORD), 0); ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 8), &dwHeight, sizeof(DWORD), 0); 这样,雷区的高度就保存在了dwHeight里面,宽度保存在了dwWidth里面。我们之前已经通过代码获取了整个雷区的数据,保存在了数组pByte[]里面,但是这里面是整个雷区的数据,包含有832个字节,而我们现在只需要dwHeight×dwWidth这么多的数据,也就是只与雷区相关的数据,这样一来,雷区的边界0x10以及多余的0x0F都应该删去,如下图所示:上图是初级雷区的内存分配图,可以发现,其实不论是哪一种雷区,其最开始一定是雷区的最上一条边界的数据,包含有dwWidth+2这么多的0x10,其中的这个2是边界的起点和终点。因此我们在进行雷区转换的时候,首先应该跳过这第一条边,也就是如果发现两个连续的0x10则认为它是雷区的上边,则会跳过。接下来可能会读取到0x0F,则继续往下读,直到读取到下一个0x10为止,说明已经到了雷区的第一行,此时依旧应该跳过当前的这个0x10,从下一个字节的数据开始读取,读取dwWidth这么多的字节,然后再后移dwWidth+2这么多的字节,以此循环,直到已经读完了dwHeight这么多行为止,说明整个雷区的有效数据已经获取完毕。代码如下: int h = dwHeight; int count = 0; PBYTE pTmpByte = NULL; pTmpByte = (PBYTE)malloc(dwHeight*dwWidth); while (i < dwSize) { if (pByte[i] == 0x10 && pByte[i + 1] == 0x10) { i = i + dwWidth + 2; continue; } else if (pByte[i] == 0x10) { for (j = 1; j <= dwWidth; j++) { pTmpByte[count] = pByte[i + j]; count++; } i = i + dwWidth + 2; continue; h--; if (h == 0) break; } i++; } 获取到雷区数据以后,它是以一维数组的形式保存在pTmpByte[]里面的。下面我们需要得到该数组中每一个数据在二维数组模式下的下标,即x1和y1值。其中y1是行下标,表明是第几行,行数从0开始;x1是列下标,表明是第几列,列数也是从0开始的。该数组中的数据量为dwHeight×dwWidth,可以采用循环的方式逐个读取数组中的数据然后计算出其二维下标值。假设这个一维数组下标为i,则行下标可以用i除以dwWidth然后取整数商的方式获得,列下标可以用i模dwWidth的方式,也就是通过取余运算获得。因此包含有鼠标点击的完整代码如下: int x1 = 0, y1 = 0; int x = 0, y = 0; for (i = 0; i < dwHeight*dwWidth; i++){ if (pTmpByte[i] != 0x8F) { x1 = i % dwWidth; y1 = i / dwWidth; x = x1 * 16 + 16; y = y1 * 16 + 61; SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y)); SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y)); } } 综合以上,整个0秒一键扫雷功能的完整代码如下: #include <stdio.h> #include <windows.h> int main() { DWORD Pid = 0; HANDLE hProcess = 0; // 控制时间自增的指令地址 DWORD incAddress1 = 0x01002FF5; DWORD incAddress2 = 0x01003830; // 将时间自增NOP掉的6字节指令 char inc[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; DWORD result1, result2; // 获取扫雷游戏对应的窗口句柄 HWND hWnd = FindWindow(NULL, L"扫雷"); if (hWnd != 0){ // 获取扫雷进程ID GetWindowThreadProcessId(hWnd, &Pid); // 打开扫雷游戏获取其句柄 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (hProcess == 0){ printf("Open winmine process failed."); return 0; } // NOP掉时间自增的语句 result1 = WriteProcessMemory(hProcess, (LPVOID)incAddress1, &inc, 6, 0); result2 = WriteProcessMemory(hProcess, (LPVOID)incAddress2, &inc, 6, 0); if (result1 == 0 || result2 == 0){ printf("Inc modify failed."); return 0; } // 存放雷区的起始地址 DWORD dwBoomAddr = 0x01005340; // 雷区的最大值(包含边界) DWORD dwSize = 832; PBYTE pByte = NULL; pByte = (PBYTE)malloc(dwSize); // 读取整个雷区的数据 ReadProcessMemory(hProcess, (LPVOID)dwBoomAddr, pByte, dwSize, 0); int i = 0; int j = 0; int n = dwSize; // 读取雷区的长和宽 DWORD dwInfo = 0x01005330; DWORD dwHeight = 0, dwWidth = 0; ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 4), &dwWidth, sizeof(DWORD), 0); ReadProcessMemory(hProcess, (LPVOID)(dwInfo + 8), &dwHeight, sizeof(DWORD), 0); int h = dwHeight; int count = 0; // 雷区转换,去掉雷区多余的数据 PBYTE pTmpByte = NULL; pTmpByte = (PBYTE)malloc(dwHeight*dwWidth); while (i < dwSize) { if (pByte[i] == 0x10 && pByte[i + 1] == 0x10) { i = i + dwWidth + 2; continue; } else if (pByte[i] == 0x10) { for (j = 1; j <= dwWidth; j++) { pTmpByte[count] = pByte[i + j]; count++; } i = i + dwWidth + 2; continue; h--; if (h == 0) break; } i++; } // 获取雷区方块的坐标,然后模拟鼠标进行点击 int x1 = 0, y1 = 0; int x = 0, y = 0; for (i = 0; i < dwHeight*dwWidth; i++){ if (pTmpByte[i] != 0x8F) { x1 = i % dwWidth; y1 = i / dwWidth; x = x1 * 16 + 16; y = y1 * 16 + 61; SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y)); SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y)); } } free(pByte); CloseHandle(hProcess); } else { printf("Get hWnd failed."); } return 0; } 编译链接生成exe文件,我们首先运行扫雷程序,然后再运行这个辅助,不论是哪个级别的扫雷,或者是自定义的扫雷,都可以实现0秒一键扫雷的效果:小结 扫雷尽管是一款简单的游戏,其辅助程序也仅仅才一百行,但是整个分析的过程却也是综合采用了不少的逆向分析以及Windows编程知识,其中的很多思想其实可以应用在很多的方面,可以用于处理很多复杂的情况。而这些思想在我未来的研究中也会继续使用,去解决更多有趣的问题。
逆向工程:简易游戏辅助的实现 本文所研究的游戏选自于【日】爱甲健二编著的《有趣的二进制》,本文中游戏介绍部分的内容,也是源于该书。 射击游戏规则介绍 我们首先来看一下本次的示例游戏,运行shooting文件夹中的shooting.exe即可:这个游戏的规则如下: ● 空格键:射击 ● ←键:向左移动 ● →键:向右移动 ● ↑键:填充能量(以当前得分为上限) ● ↓键:时间停止(消费能量) 用左右键移动,用空格键射击,这些操作和一般的射击游戏一模一样。通过按上下键可以使用能够让时间停止的特殊能力。其中↑键用来填充能量,↓键则用来消费能量并让时间停止。能量的上限是当前得分,因此随着游戏的进行,能够填充的能量也会增加。击中敌人可以增加得分,被敌人击中则减少得分。得分越高,敌人越强,子弹的追踪性能也会提高。 大家可以先玩玩看,一般来说能玩到500~1000分,不过,当超过1000分之后,游戏的难度就会大大增加,要想达到2000分可以说是相当困难的。 修改内存数据就能得高分 得2000分虽然难,但其实我们只要修改内存中的数据就可以轻松实现了。为了修改内存数据,我们这里要用到编写游戏辅助最常用的一款工具“Cheat Engine” 。在运行游戏的同时,打开“Cheat Engine”,然后从进程列表中选择shooting.exe:接下来我们尝试让分数“SCORE”发生变化,比如击中对手一次,我们的分数就会变成“29”,此时将29输入“Cheat Engine”中Value下面的输入条内,点击“New Scan”:可以看到这里找到了1290条结果,但究竟哪个才是我们要找的“SCORE”呢?目前还不好判断,因此我们需要再让分数发生变化,比如再击中对手一次。此时我们的分数变成了58分,回到CE,将刚才的 29修改为58,然后点击“Next Scan”,此时就只剩下一个结果了:我们可以先记住它的地址(Address),即0x0019FD48,一会编程需要用到。这里我们可以直接通过CE修改分数,也就是双击这个结果,它会显示在CE下方的列表框中,然后双击下面列表框中Value下面的58,在新弹出的对话框中进行修改:将其修改成2000,点击OK即可。此时回到游戏,我们就可以发现分数已经变成2000了。 编写游戏辅助 利用上述方法可以对游戏的内存进行修改,但是每次仅能修改一处数值,并且重启游戏以后,还需要重新将CE与游戏挂载,重新搜索内存数据,这就很不方便了,因此我们这里研究一下如何编写一个简易的游戏辅助,这样每当我们想修改的时候,只要运行游戏辅助就可以了,并且还能按照自己的喜好进行编辑和修改。 本程序在Visual Studio 2013环境中编程实现,使用C++语言。首先启动VS2013,我们新建一个工程文件:这里我们选择的是Win32控制台应用,注意在创建设置对话框里面,我们选择创建一个空的工程:创建成功以后,我们为这个工程新增一个cpp文件,在VS窗口的右侧找到Solution Explorer,右键点击Source Files,选择添加一个新的项目:接下来选择添加cpp文件即可:cpp文件创建成功以后,就需要编写代码了。对于本程序,修改分数为2000的完整代码如下: #include <stdio.h> #include <windows.h> int main() { DWORD Pid; HANDLE hProcess = 0; DWORD Address = 0x0019FD48; DWORD Score = 2000; DWORD result; HWND hWnd = FindWindow(NULL, L"shooting"); if (hWnd != 0) { GetWindowThreadProcessId(hWnd, &Pid); hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid); if (hProcess == 0) { printf("Open process failed."); return 0; } result = WriteProcessMemory(hProcess, (LPVOID)Address, &Score, 4, 0); if (result == 0) { printf("Modify failed."); } else { printf("Modify Success."); } } else { printf("Failed"); } return 0; } 对于上述程序,有两个地方的数据需要说明一下。首先是调用FindWindow函数获取窗口句柄时,最后一个参数是窗口的名称。当然,我们启动游戏后,是可以在游戏窗口的左上角看到游戏窗口名称叫做“shooting”的。但实际上,为了严谨起见,我们一般是使用一款名为Spy++的工具来获取窗口名称的。该工具已经被集成到了我们的VS开发环境里面,只要选择菜单栏中的TOOLS,就可以找到Spy++了:我们选择菜单栏中的“Search”,再点击“Find Window”,就会弹出名为“Window Search”的对话框。我们将对话框中的Finder Tool后面的准心拖到游戏窗口里面,这样该工具就能够帮我们识别到窗口的真实标题内容了,即Caption的值:我们把这个值当作FindWindow函数的最后一个参数即可。 第二个需要说明的地方是程序开始位置的Address值,这个值在程序中是0x0019FD48,也就是我们之前使用CE所发现的用于保存分数的地址。不同版本的游戏的这个值往往是不一样的,因此为了严谨起见,就如同我们刚才获取窗口名称一样,每次都应该使用CE确认一下这个地址到底是多少。 编译通过后运行游戏,再运行本程序,就可以发现分数被修改为2000了。 其它有意思的修改 当我们把分数修改为2000以后,可以发现游戏难度呈几何级提升,一方面是对手的子弹有了跟踪的效果,我们很难躲避;另一方面是对手本体在躲避我方的子弹方面更加地智能,我们很难击中对手。针对于这两种情况,我们可以使用一些简单的小技巧来攻克。 首先是对手子弹的跟踪效果。我们这里不妨把自己设置成“无敌”的模式。当然,“无敌”可以考虑通过修改源代码实现,但是换一个角度来看这个问题,对手之所以会击中我们,就是由于绿色子弹的存在,而这个绿色子弹其实就是一个图片文件,位于shooting文件夹,文件名是EShot.png,那么我们不妨将这个文件删掉。再次回到游戏后就能发现,对手只会左右移动,不会发射子弹了,于是“无敌”功能实现。 可现在尽管无敌了,但我们还是很难击中对手,因此不妨使用同样的思维,在我们的黄色子弹上面做文章。使用画图软件对Shot.png进行编辑,比如将其拉伸变宽,然后保存,再进入游戏就能发现我们的子弹宽了很多:如此一来,即便对方身手再好,也不可能躲避掉我们的子弹了。
适用于移动应用程序的虚拟化加密软件 代码加密 概述: KiwiVM是用于移动应用程序的虚拟化加密软件。 它基于Clang编译器扩展,并且在编译项目时虚拟化指定的函数。借助自定义CPU指令的功能,一旦对代码进行加密并且从未解密,攻击者将无法恢复代码并分析核心业务逻辑。帮助公司为通信,支付,算法和核心技术定制加密,以避免由于安全问题而造成的财务损失。 特点与优势: 深度加密:自定义CPU指令,加密的代码不会被解密。 功能虚拟化:针对iOS项目的代码功能的虚拟化保护 完整架构:支持所有cpu架构,例如armv7,armv7s,arm64等。 兼容性:基于LLVM的IR代码的实现没有兼容性问题 文献资料: 您可以在几维安全上找到KiwiVM文档、ios代码混淆效果参考文档。 项目类型: 安卓NDK项目[SO动态库、静态库] 支持iOS项目[APP、动态库、静态库] 虚拟化效果分析: 1.代码虚拟化 在编译阶段以C、C++源文件的代码块为单位进行虚拟化,运行在受保护的虚机中,可防止IDA Pro等逆向工具的静态分析。 在IDA Pro中反编译未加密的函数在IDA Pro中反编译已加密的函数,原始代码逻辑已被加密隐藏2、字符串加密 对敏感字符串数据进行加密保护,避免攻击者通过关键词搜索来定位关键代码,增加逆向难度 在IDA Pro中查看未加密的字符串在IDA Pro中查看已加密的字符串说明: 目前提供的KiwiVM源码虚拟化产品支持iOS、Android、Linux等多个平台。 依靠团队的技术优势,KiwiSec专注于移动安全领域的下一代技术和产品开发。最初的KiwiVM虚拟机产品经过8年的编译器安全技术积累和3年的不断研究与开发,已完全解决了传统技术固有的保护弱,兼容性差等问题,例如强化和模糊化,为用户提供了便利,有效的安全解决方案。
一些Unity游戏开发插件介绍 本文介绍一下使用Unity开发游戏时,可以用到的一些插件,辅助游戏的开发效率! 1:Ultimate Mobile Pro 包含Unity与iOS和安卓原生api交互的插件,同时包含Admob插件,随意切换iOS和安卓部署,不需要更改任何代码。 下面三个插件为Ultimate Mobile Pro的子集。 A)iOS Native Pro (Ultimate Mobile Pro的子集,包含和iOS交互的插件 ) B)Android Native Pro (Ultimate Mobile Pro的子集,包含Unity和安卓交互的插件 ) C)Google Mobile Ads SDK (Ultimate Mobile Pro的子集,提供iOS和安卓使用Admob的插件 ) 2:Easy Mobile Pro 该插件提供和Ultimate Mobile Pro类似功能的功能。 3:Easy Save - The Complete Save & Load Asset 跟存储、加载、加密、序列化等类似的功能均可用此插件完成,完美支持iOS和安卓端。 4:Easy Touch 5 : Touchscreen & Virtual Controls Easy Touch 5是一款处理处理手势输入的插件,对手机游戏制作很有必要。 5:Fingers - Touch Gestures for Unity 也是一款制作触摸控制的插件。 6:Anti-Cheat Toolkit 保护游戏数据,免于被某些玩家作弊。制作单机手机游戏的开发者绝对不要错过这款插件。 7:Obfuscator Obfuscator可以保护游戏代码和游戏资源,防止被逆向工程破解。 8:Playmaker 对于没有深入学习过编程的小伙伴,这款可视化插件可以帮助你直观、简单的实现自己的想法。 9:Final IK FinalIK是一个反向动力控制插件。相比较unity的自带IK系统,FinalIK设置更加方便,用途更广。有很多情景化的应用,如针对和物体交互的动作系统。如果能使用好FinalIK插件,就可以使用少量的固定动画,在此基础上融合IK动作,做出千变万化的交互动作。 10:Behavior Designer - Behavior Trees for Everyone Behavior Designer可以帮助Unity开发者制作AI的行为树。 11:TopDown Engine TopDown Engine是制作2D/3D动作类手游的最佳引擎。 12:I2 Localization Unity功能最全的本地化插件,可以本地化图片、文字、声音、图集、预设等资源。支持Unity UI, Unity 2D, TextMesh Pro, NGUI, 2D ToolKit, SVG Importer等第三方插件。 13:Best HTTP Best HTTP是一款网络插件,它支持REST,WebSocket,Socket IO等,比Unity原生的WWW强大太多。 14:Photon PUN+ Classic PhotonPUN+是一款制作多人联机游戏的插件,同时支持PC端、手机端和网页端的Unity项。 15:Inventory Pro InventoryPro是制作背包仓储系统的最佳插件,可以用来存储物品、装备、武器和杂物等。是制作RPG游戏的必备插件。 16:Loxodon Framework Bundle LoxodonFrameworkBundle是一个非常好用的AssetBundle加载器,也是一个AssetBundle冗余分析工具。它能够自动管理AssetBundle之间复杂的依赖关系,它通过引用计数来维护AssetBundle之间的依赖。 17:Admob Unity Plugin Admob Unity插件提供了一种将admob广告集成到Unity3D Game和u3d应用中的方法。您可以将其用于具有相同c#或js代码的Unity iOS和Android App,此插件使js和c#开发人员更容易在Unity3d游戏中添加Google广告,支持admob插页式广告和横幅
浅谈安卓开发代码混淆技术 前言 随着移动互联网的快速发展,应用的安全问题不断涌现出来,于是越来越多的应用开发者将核心代码由java层转到native层,以对抗成熟的java逆向分析工具,然而如果native层的代码如果没有进行任何保护,还是比较容易被逆向分析工作者获取其运行逻辑,进而完成应用破解或者进行其他的操作。那么提高native代码的安全性有什么好办法吗?答案是肯定的,今天我们就来介绍一种有效对抗native层代码分析的方法——代码混淆技术。 那么,什么是代码混淆呢?代码混淆的学术定义如下:代码混淆(code obfuscation)是指将计算机程序的代码,转换成一种功能上等价,所谓功能上的等价是指其在变换前后功能相同或相近。其解释如下:程序P经过混淆变换为P‘,若P没有结束或错误结束,那么P’也不能结束或错误结束;而且P‘程序的结果应与程序P具有相同的输出。否则P’不是P的有效的混淆。 目前对于混淆的分类,普遍是以Collberg 的理论为基础,分为布局混淆(layout obfuscation)、数据混淆(data obfuscation)、控制混淆(control obfuscation)和预防混淆(preventive obfuscation)这四种类型。 布局混淆   布局混淆是指删除或者混淆软件源代码或者中间代码中与执行无关的辅助文本信息,增加攻击者阅读和理解代码的难度。软件源代码中的注释文本、调试信息可以直接删除,用不到的方法和类等代码或数据结构也可以删除,这样即可以使攻击者难以理解代码的语义,也可以减小软件体积,提高软件装载和执行的效率。软件代码中的常量名、变量名、类名和方法名等标识符的命名规则和字面意义有利于攻击者对代码的理解,布局混淆通过混淆这些标识符增加攻击者对软件代码理解的难度。标识符混淆的方法有多种,例如哈希函数命名、标识符交换和重载归纳等。哈希函数命名是简单地将原来标识符的字符串替换成该字符串的哈希值,这样标识符的字符串就与软件代码不相关了;标识符交换是指先收集软件代码中所有的标识符字符串,然后再随机地分配给不同的标识符,该方法不易被攻击者察觉;重载归纳是指利用高级编程语言命名规则中的一些特点,例如在不同的命名空间中变量名可以相同,使软件中不同的标识符尽量使用相同的字符串,增加攻击者对软件源代码的理解难度。布局混淆是最简单的混淆方法,它不改变软件的代码和执行过程。 数据混淆   数据混淆是修改程序中的数据域,而对代码段不作处理。常用的数据混淆方式有合并变量、分割变量、数组重组、字符串加密等。合并变量是将几个变量合并为一个数据,原来的每个变量占据其中一个区域,类似于一个大的数据结构。分割变量则是将一个变量分割为两个变量,对分割前后提供一种映射关系,将对一个变量的操作转化为对分割后两个变量的操作。   数组重组有数组的分割、合并、折叠和平滑等几种方式。分割是将一个数组分成2个或多个相同维度的数组;合并则相反;折叠是增加数组的维数;平滑则是相反。在ELF文件中,全局变量和常量字符串存放在数据段中,反汇编工具可以轻易查找到字符串与代码之间的引用关系。在软件破解中,通过一些字符串提示可以很方便的找到代码关键语句,从而破解软件。字符串加密则可以对这些明显的字符串进行加密存储,在需要时再进行解密。 控制混淆   控制混淆也称流程混淆,它是改变程序的执行流程,从而打断逆向分析人员的跟踪思路,达到保护软件的目的。一般采用的技术有插入指令、伪装条件语句、断点等。伪装条件语句是当程序顺序执行从A到B,混淆后在A和B之间加入条件判断,使A执行完后输出TRUE或FALSE,但不论怎么输出,B一定会执行。控制混淆采用比较多的还有模糊谓词、内嵌外联、打破顺序等方法。   模糊谓词是利用消息不对称的原理,在加入模糊谓词时其值对混淆者是已知的,而对反混淆者却很难推知。所以加入后将干扰反汇编者对值的分析。模糊谓词的使用一般是插入一些死的或不相关的代码(bogus code),或者是插入在循环或分支语句中,打断程序执行流程。   内嵌(in-line)是将一小段程序嵌入到被调用的每一个程序点,外联(out-line)是将没有任何逻辑联系的一段代码抽象成一段可被多次调用的程序。 打破顺序是指打破程序的局部相关性。由于程序员往往倾向于把相关代码放在一起,通过打破顺序改变程序空间结构,将加大破解者的思维跳跃。 预防混淆   预防混淆一般是针对专用的反编译器设计的,目的就是预防被这类反编译器反编译。他是利用特定的反编译器或反混淆器的弱点进行专门设计。预防混淆对于特定的反编译器非常有效,所以在使用时要综合利用各种反编译器的特点进行设计。 总结   腾讯御安全保护方案提供了以上所述四种混淆分类的多维度的保护;布局混淆方面:提供了针对native代码层中的函数名进行了混淆删除调试信息等功能;数据混淆方面:提供了针对常量字符串加密及全局变量的混淆的功能;控制混淆方面:针对代码流程上,提供了扁平化,插入bogus 分支以及代码等价变换等功能;预防混淆方面:在混淆过程中加入了针对主流反编译器的预防混淆的代码,能够有效地抵抗其分析。此外还对应用开发者提供不同等级的保护力度及多种混淆方式的功能的选择,用户可以根据自己的需求定制不同的混淆功能保护。安全保护方案除了提供代码混淆保护方面的技术,还提供代码虚拟化技术及反逆向、反调试等其他应用安全加固方案,综合使用多种代码保护方案可以有效地提高应用代码安全。
android逆向分析之从smali到java 本片通过ServerListActivity.smali(来源于上篇apktool反编译出来的)来讲述,首先打开此文件,片段如下: ## .class public Lcom/cpic/jst/ui/activity/ServerListActivity;. super Lcom/cpic/jst/ui/activity/BaseActivity;. source "ServerListActivity.java" ## .class <访问权限> [关键修饰字] <类名>; .super <父类名>; .source <源文件名> 相当于public class ServerListActivity extends BaseActivity。 .class指令表示当前的类名,类的访问权限是public,类名为Lcom/cpic/jst/ui/activity/ServerListActivity,类开头的L是遵循Dalvik字节码的规范,表示后面是一个类。 .super指定了当前类所继承的父类,后面指的就是这个父类的类名。 .source指定了当前类的源文件名。 ## interfaces. implements Landroid/text/TextWatcher; ## interfaces是注释,表示后面是一个interface。 .implements是接口关键字。 ## static fields.field public static final REQUEST_CODE_TO_SERVERLISTDETAILSACTIVITY:I = 0x7d8# instance fields. field public adapter:Lcom/cpic/jst/ui/adapter/HadVisitedListAdapter; .field private address:Ljava/lang/String; ## 经过上述叙述,应该明白此处# static fields为注释,一个静态字段,# instance fields注释为一个非静态字段,address为一个private的字段,现在拉取完整的smali和java代码作对比,如下: public class ServerListActivity extends BaseActivity implements TextWatcher{ public static final int REQUEST_CODE_TO_SERVERLISTDETAILSACTIVITY = 2008; public HadVisitedListAdapter adapter; private String address;} .class public Lcom/cpic/jst/ui/activity/ServerListActivity;.super Lcom/cpic/jst/ui/activity/BaseActivity;.source "ServerListActivity.java"# interfaces.implements Landroid/text/TextWatcher;# static fields.field public static final REQUEST_CODE_TO_SERVERLISTDETAILSACTIVITY:I = 0x7d8# instance fields.field public adapter:Lcom/cpic/jst/ui/adapter/HadVisitedListAdapter;.field private address:Ljava/lang/String; 1 smali2java 对于没有加壳加密处理过的apk,我们还可以直接通过smali2java来实现反编译,如果去深挖smali2java会发现,其实smali2java就是apktool的界面化操作,通过apktool反编译,再去把smali读取出来,smali2java不仅仅可以把整个apk读取出来,还能打开单独的smali文件然后转成java文件。 但是打开源码后,会有一些资源文件已经成了一些十六进制的代码呈现在我们眼前,这就**了,天知道这是啥呢? 少年别急,这些都是小问题。 通过刚刚的十六进制便可以在public中找到对应的名称。 对于单独一个smali文件转java文件,点击 文件–>处理单个smali文件,找到需要转换的smali文件后确定,弹窗显示smali反编译为java成功。
安卓逆向:重温Thumb汇编指令细节 主要内容 1.Thumb指令集详解 2.Thumb直接访问的寄存器 3.Thumb指令集组成部分详解 4.Thumb和arm状态却换 5.Thumb的常见应用场景 1.Thumb指令集详解 •ARM处理器支持两种指令集:ARM指令集和Thumb指令集。 •ARM指令集指令长度为32位,Thumb指令集指令长度为16位。在16位外部数据总线宽度下,ARM处理器上使用Thumb指令的性能要比使用ARM指令的性能更好。 •存在Thumb指令的意义:兼容数据总线宽度为16为的应用系统。 2.Thumb直接访问的寄存器3.Thumb指令集组成部分•3.1.Thumb数据处理指令•3.2.分支跳转指令•3.3.寄存器加载存储指令(单寄存器、多寄存器)•3.4.杂项指令 •SWI:软中断指令 指令格式如下: ## SWI immed_8 其中:immed_8 8 位立即数,值为0~255 之间的整数。 SWI 指令举例如下: SWI 1 ;软中断,中断立即数为0 SWI 0x55 ;软中断,中断立即数为0x55 使用SWI 指令时,通常使用以下两种方法进行传递参数,SWI 异常中断处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI 异常中断处理程序要通过读取引起软中断的SWI 指令。以取得8 位立即数。 (A)指令中8 位的立即数指定了用户请求的服务类型,参数通过用寄存器传递。 MOV R0,#34 ;设置子功能号为34 SWI 18 ;调用18 号软中断 (B)指令中的8 位立即数被忽略,用户请求的服务类型由寄存器R0 的值决定,参数通过其它的通用寄存器传递。 MOV R0,#18 ;调用18 号软中断 MOV R1,#34 ;设置子功能号为34 SWI 0 ## 4.Thumb和ARM状态却换 •ARM/Thumb之间的状态切换是通过一条专用的转移交换指令BX来实现的 汇编格式: BX{} Rm 功能: BX 指令跳转到指令中所指定的目标地址,并实现状态的切换。 Rm 是一个表达目标地址的寄存器。当Rm 中的最低位Rm[0] 为 1 时,强制程序从ARM 指令状态跳到Thumb 指令状态;当 Rm 中的最低位Rm[0]为0 时,强制程序从Thumb 指令状态跳到ARM 指令状态。 BX 指令示例 ## CODE32 ;ARM 程序段,32 位编码 arm1 ADR R0,thumb1+1 ;把thumb1 所在地址赋给R0 ,末位R0[0] 置1 ,要跳转THUMB 指令集 MOV LR,PC ;设置返回地址 BX R0 ;跳转 ADD R1,R2,#2 ;返回地址处,第4 条指令 CODE16 ;THUMB 程序段, 16 位编码 thumb1 ADD R1,R3,#1 ;THUMB 程序 BX LR ;跳转到返回地址处,执行第4 条指令 ## 以上示例分析:说明了带状态切换的子程序调用和返回结构,ARM 程序段执行MOV LR,PC 语句时将返回地址保存到了LR 寄存器中。 执行到BX 语句时 ,PC 指向下一个要执行的语句,此时PC(R15) 中的值为下一个语句ADD 指令所在的地址,并根据R0 中的bit[0] 实现了由ARM 状态切换到Thumb 状态。 从而调用Thumb 子程序,子程序调用完后使用BX LR 指令,从而实现了子程序调用的返回并切换到ARM 状态。 5.Thumb指令一些应用情况 •在ida中识别Thumb指令和ARM指令的方法 •CODE32表示的采用ARM汇编指令,CODE16表示采用的是THUMB汇编指令。•Thumb汇编的主要应用场景:逆向调试So文件的时候,编写ARM的shellcode代码的时候。 •以下是arm的shellcode的应用
HOOK框架之动态代理 动态代理的目的就是为了解决静态代理的缺点,通过使用动态代理,在运行时动态生成一个持有RealObject,并实现代理接口的Proxy,同时注入相同的扩展逻辑。即使你要代理的RealObject是不同的对象,代理不同的方法,都可以通过动态代理来扩展功能。 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中进行处理(InvocationHandlerinvoke)。在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一复用性更强。 一:动态代理的核心 动态代理的核心就是代理对象的生成,其核心代码需要三个参数。 ClassLoader:用于加载代理类的Loader类,通常这个Loader和被代理的类是同一个Loader类。 Interfaces:是要被代理的接口。 InvocationHandler:是用于执行除了被代理接口中方法之外用户自定义的操作,也是用户需要代理的最终目的。用户调用目标方法都被代理到 InvocationHandler类中定义的唯一方法invoke中。 二:如何实现动态代理 1.首先定义一个接口,如下图所示。2.实现这个接口类,如下图所示。3.代理需要实现类,代理实现的接口为InvocationHandler(为系统提供类),查看该系统提供的接口,发现只有一个方法,并且该方法有三个参数。实现Invoke方法中,传入相应的参数,定义要代理的对象并且初始化,设置完成后便调用,如下图所示。4.准备代理器,在MainActivity类里面实例化目标对象,然后调用方法用来设置代理的类。把代理传给系统的handler,生成新的代理对象,调用target得到类和接口、代理类、新的代理对象调用方法(指定接口),进而调用方法,如下图所示。5.运行查看效果。 小结 动态代理的概念以及如何使用动态代理,实战操作动态代理的使用以及代码的编写。 可以加群获取免费资料:876526335
HOOK框架之动态代理 动态代理的目的就是为了解决静态代理的缺点,通过使用动态代理,在运行时动态生成一个持有RealObject,并实现代理接口的Proxy,同时注入相同的扩展逻辑。即使你要代理的RealObject是不同的对象,代理不同的方法,都可以通过动态代理来扩展功能。 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中进行处理(InvocationHandlerinvoke)。在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一复用性更强。 一:动态代理的核心 动态代理的核心就是代理对象的生成,其核心代码需要三个参数。 ClassLoader:用于加载代理类的Loader类,通常这个Loader和被代理的类是同一个Loader类。 Interfaces:是要被代理的接口。 InvocationHandler:是用于执行除了被代理接口中方法之外用户自定义的操作,也是用户需要代理的最终目的。用户调用目标方法都被代理到 InvocationHandler类中定义的唯一方法invoke中。 二:如何实现动态代理 1.首先定义一个接口,如下图所示。2.实现这个接口类,如下图所示。3.代理需要实现类,代理实现的接口为InvocationHandler(为系统提供类),查看该系统提供的接口,发现只有一个方法,并且该方法有三个参数。实现Invoke方法中,传入相应的参数,定义要代理的对象并且初始化,设置完成后便调用,如下图所示。4.准备代理器,在MainActivity类里面实例化目标对象,然后调用方法用来设置代理的类。把代理传给系统的handler,生成新的代理对象,调用target得到类和接口、代理类、新的代理对象调用方法(指定接口),进而调用方法,如下图所示。5.运行查看效果。 小结 动态代理的概念以及如何使用动态代理,实战操作动态代理的使用以及代码的编写。 可以加群获取免费资料:876526335
HOOK框架之动态代理 动态代理的目的就是为了解决静态代理的缺点,通过使用动态代理,在运行时动态生成一个持有RealObject,并实现代理接口的Proxy,同时注入相同的扩展逻辑。即使你要代理的RealObject是不同的对象,代理不同的方法,都可以通过动态代理来扩展功能。 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中进行处理(InvocationHandlerinvoke)。在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使类职责更加单一复用性更强。 一:动态代理的核心 动态代理的核心就是代理对象的生成,其核心代码需要三个参数。 ClassLoader:用于加载代理类的Loader类,通常这个Loader和被代理的类是同一个Loader类。 Interfaces:是要被代理的接口。 InvocationHandler:是用于执行除了被代理接口中方法之外用户自定义的操作,也是用户需要代理的最终目的。用户调用目标方法都被代理到 InvocationHandler类中定义的唯一方法invoke中。 二:如何实现动态代理 1.首先定义一个接口,如下图所示。2.实现这个接口类,如下图所示。3.代理需要实现类,代理实现的接口为InvocationHandler(为系统提供类),查看该系统提供的接口,发现只有一个方法,并且该方法有三个参数。实现Invoke方法中,传入相应的参数,定义要代理的对象并且初始化,设置完成后便调用,如下图所示。4.准备代理器,在MainActivity类里面实例化目标对象,然后调用方法用来设置代理的类。把代理传给系统的handler,生成新的代理对象,调用target得到类和接口、代理类、新的代理对象调用方法(指定接口),进而调用方法,如下图所示。5.运行查看效果。 小结 动态代理的概念以及如何使用动态代理,实战操作动态代理的使用以及代码的编写。 可以加群获取免费资料:876526335
HTTP协议 一、HTTP协议的特点   1.HTTP协议是无状态的   就是说每次HTTP请求都是独立的,任何两个请求之间没有什么必然的联系。但是在实际应用当中并不是完全这样的,引入了Cookie和Session机制来关联请求。   2.多次HTTP请求   在客户端请求网页时多数情况下并不是一次请求就能成功的,服务端首先是响应HTML页面,然后浏览器收到响应之后发现HTML页面还引用了其他的资源,例如,CSS,JS文件,图片等等,还会自动发送HTTP请求这些需要的资源。现在的HTTP版本支持管道机制,可以同时请求和响应多个请求,大大提高了效率。   3.基于TCP协议   HTTP协议目的是规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节。底层是基于TCP实现的。现在使用的版本当中是默认持久连接的,也就是多次HTTP请求使用一个TCP连接。 二、HTTP报文   1.请求报文   简单来说请求报文就是由请求行、请求头、内容实体组成的,注意,每一行的末尾都有回车和换行,在内容实体和请求头之间另有一个空行。其中请求行指定的是请求方法、请求URL、协议版本;请求头是键值对的形式存在的,就是字段名:值;内容实体就是要传输的数据。稍后会对方法、请求头字段做详细的说明。 2.响应报文  简单来说响应报文由状态行、响应首部字段(响应头)、响应实体组成,其中第一行是状态行,依次包含HTTP版本,状态码和状态短语组成;在一个回车换行之后是响应头,也是键值对的形式,字段名:值;然后会有一个空行也包含回车换行,之后是响应实体,就是要传输的数据。在上面的例子当中就是一个非常简单的HTML页面。对于响应状态码,首部字段键值对稍后会有更加详细的说明。 三、HTTP请求方法   请求方法是客户端用来告知服务器其动作意图的方法。就像下达命令一样。在HTTP1.1版本中支持GET、POST等近10种方法。需要注意的是方法名区分大小写,需要用大写字母。下面详细说明。   1.GET:获取资源   GET方法用来请求访问已被URI识别的资源。也就是指定了服务器处理请求之后响应的内容。   2.POST:传输实体主体   POST方法用来传输实体主体。POST与GET的区别之一就是目的不同,二者之间的区别会在文章的最后详细说明。虽然GET方法也可以传输,但是一般不用,因为GET的目的是获取,POST的目的是传输。   3.PUT:传输文件   PUT方法用来传输文件。类似FTP协议,文件内容包含在请求报文的实体中,然后请求保存到URL指定的服务器位置。   4.HEAD:获得报文首部   HEAD方法类似GET方法,但是不同的是HEAD方法不要求返回数据。用于确认URI的有效性及资源更新时间等。   5.DELETE:删除文件   DELETE方法用来删除文件,是与PUT相反的方法。DELETE是要求返回URL指定的资源。   6.OPTIONS:询问支持的方法   因为并不是所有的服务器都支持规定的方法,为了安全有些服务器可能会禁止掉一些方法例如DELETE、PUT等。那么OPTIONS就是用来询问服务器支持的方法。   7.TRACE:追踪路径   TRACE方法是让Web服务器将之前的请求通信环回给客户端的方法。这个方法并不常用。 8.CONNECT:要求用隧道协议连接代理   CONNECT方法要求在与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信。主要使用SSL/TLS协议对通信内容加密后传输。   汇总:四、HTTP的响应状态码   状态码是用来告知客户端服务器端处理请求的结果。凭借状态码用户可以知道服务器是请求处理成功、失败或者是被转发;这样出现了错误也好定位。状态码是由3位数字加原因短语组成。3位数字中的第一位是用来指定状态的类别。共有5种。 HTTP状态码一共有60多种,但是不用全部都记住,因为大部分在工作当中是不经常使用的。经常使用的大概就是16种,下面来详细介绍。(其实最最常用的也就8种,下面有背景色的就是)   1. 200:OK   这个没有什么好说的,是代表请求被正常的处理成功了。   2. 204:No Content   请求处理成功,但是没有数据实体返回,也不允许有实体返回。比如说HEAD请求,可能就会返回204 No Content,因为HEAD就是只获取头信息。这里简单提一下205 Reset Content,和204 No Content的区别是不但没有数据实体返回,而且还需要重置表单,方便用户再次输入。   3. 206:Partial Content   这是客户端使用Content-Range指定了需要的实体数据的范围,然后服务端处理请求成功之后返回用户需要的这一部分数据而不是全部,执行的请求就是GET。返回码就是206:Partial Content。   4. 301 : Moved Permanently   代表永久性定向。该状态码表示请求的资源已经被分配了新的URL,以后应该使用资源现在指定的URL。也就是说如果已经把资源对应的URL保存为书签了,这是应该按照Location首部字段提示的URL重新保存。   5. 302:Found   代表临时重定向。该状态码表示请求的资源已经被分配了新的URL,但是和301的区别是302代表的不是永久性的移动,只是临时的。就是说这个URL还可能会发生改变。如果保存成书签了也不会更新。   6. 303:See Other   和302的区别是303明确规定客户端应当使用GET方法。   7. 304:Not Modified   该状态码表示客户端发送附带条件请求时,服务器端允许请求访问资源,但是没有满足条件。304状态码返回时不包含任何数据实体。304虽然被划分在3XX中但是和重定向没有关系。   8. 307:Temporary Redirect   临时重定向,与302 Found相同,但是302会把POST改成GET,而307就不会。   9. 400:Bad Request   400表示请求报文中存在语法错误。需要修改后再次发送。   10. 401:Unauthorized   该状态码表示发送的请求需要有通过HTTP认证的认证信息。   11. 403:Forbidden   表明请求访问的资源被拒绝了。没有获得服务器的访问权限,IP被禁止等。   12. 404:Not Found   表明请求的资源在服务器上找不到。当然也可以在服务器拒绝请求且不想说明理由时使用。   13. 408:Request Timeout   表示客户端请求超时,就是在客户端和服务器建立连接后服务器在一定时间内没有收到客户端的请求。   14. 500:Internal Server Error   表明服务器端在执行请求时发生了错误,很有可能是服务端程序的Bug或者临时故障。   15. 503:Service Unavailable   表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。如果事先得知解除以上状况需要的时间,最好写入Retry-After字段再返回给客户端。   16. 504:Getaway Timeout   网关超时,是代理服务器等待应用服务器响应时的超时,和408 Request Timeout的却别就是504是服务器的原因而不是客户端的原因 五、HTTP的首部字段   HTTP首部字段是构成HTTP报文最重要的元素之一。在客户端与服务端之前进行信息传递的时候请求和响应都会使用首部字段,会传递一些重要的元信息。首部字段是以键值对的形式存在的。包含报文的主体大小、语言、认证信息等。HTTP首部字段包含4种类型:   通用首部字段(General Header Fields)   代表请求报文和响应报文都会使用的字段   请求首部字段(Request Header Fields)   是客户端向服务端发送请求时使用的首部字段。包含请求的附加内容、客户端信息、响应内容相关优先级等信息。   响应首部字段(Response Header Fields)   是服务端向客户端返回响应时使用的首部字段,包含响应的附加内容,可能也会要求客户端附加额外的内容信息。   实体首部字段(Entity Header Fields)   是针对请求报文和响应报文的实体部分使用的首部。包含资源内容更新时间等和实体有关的信息。   在HTTP/1.1种规定了47种首部字段(图表参考《图解HTTP》,感谢作者。)   通用首部字段请求首部字段响应首部字段   实体首部字段六、关于HTTP的常见问题及解答   1.GET和POST的区别   A. 从字面意思和HTTP的规范来看,GET用于获取资源信息而POST是用来更新资源信息。   B. GET提交请求的数据实体会放在URL的后面,用?来分割,参数用&连接,举个栗子:/index.html?name=wang&login=1   C. GET提交的数据长度是有限制的,因为URL长度有限制,具体的长度限制视浏览器而定。而POST没有。   D. GET提交的数据不安全,因为参数都会暴露在URL上。   2.408 Request Timeout和504 Gateway Timeout的区别   408是说请求超时,就是建立连接之后再约定的时间内客户端没有发送请求到客户端到服务端。本质上原因在于客户端或者网络拥塞。504是网关超时,是说代理服务器把客户端请求转发到应用服务器后再约定的时间内没有收到应用服务器的响应。本质上原因在于服务端的响应过慢,也有可能是网络问题。   3.Cookie和Session的区别和联系   Cookie和Session都是为了保存客户端和服务端之间的交互状态,实现机制不同,各有优缺点。首先一个最大的区别就是Cookie是保存在客户端而Session就保存在服务端的。Cookie是客户端请求服务端时服务器会将一些信息以键值对的形式返回给客户端,保存在浏览器中,交互的时候可以加上这些Cookie值。用Cookie就可以方便的做一些缓存。Cookie的缺点是大小和数量都有限制;Cookie是存在客户端的可能被禁用、删除、篡改,是不安全的;Cookie如果很大,每次要请求都要带上,这样就影响了传输效率。Session是基于Cookie来实现的,不同的是Session本身存在于服务端,但是每次传输的时候不会传输数据,只是把代表一个客户端的唯一ID(通常是JSESSIONID)写在客户端的Cookie中,这样每次传输这个ID就可以了。Session的优势就是传输数据量小,比较安全。但是Session也有缺点,就是如果Session不做特殊的处理容易失效、过期、丢失或者Session过多导致服务器内存溢出,并且要实现一个稳定可用安全的分布式Session框架也是有一定复杂度的。在实际使用中就要结合Cookie和Session的优缺点针对不同的问题来设计解决方案。
Android中常见的HOOK框架有哪些? Android中常见的各类Java HOOK框架,先收集起来,因为好多的基础原理差不多,后面有时间会归类以及对于原理进行重点分析。 1.Xposed:Java层的HOOK框架,由于要修改Zgote进程,需要Root; 2.CydiaSubstrator:本地层的HOOK框架,本质上是一个inline Hook 3.dexposed框架:随着阿里的热修复的框架:原理上跟Xposed有很多雷同,dexposed框架(缺点:仅支持Dalvik不支持ART); 4.AndFix框架:找到方法在ART中的结构,然后将结构体的内容全部替换为Hook的内容;(缺点:原方法的信息全部被替换,所以无法再执行原方法 ); 5.Sophix 框架:后来阿里升级了一下,先对原来的方法作为备份,然后替换。(缺点:) 6.AndroidMethodHook框架:http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fgithub.com%2Fpanhongwei%2FAndroidMethodHook&urlrefer=e49df9a2b9b52fc219fb5d3a006119e2 7.Legend框架:在AndFix框架的基础上,在方法进行替换前进行了方法的备份; 8.YAHFA框架:http://tieba.baidu.com/mo/q/checkurl?url=http%3A%2F%2Frk700.github.io%2F2017%2F06%2F30%2Fhook-on-android-n%2F&urlrefer=f20b28b806530db301f87acbaac13795 9.EPIC框架:既然替换入口的方式无法达到Hook所有类型方法的目的,那么如果不替换入口,而是直接修改入口里面指向的代码呢?(这种方式有个高大上的学名:callee side dynamic rewriting) 这个框架是VirtualXposed和太极实现的一个基础。参考:http://tieba.baidu.com/mo/q/checkurl?url=http%3A%2F%2Fweishu.me%2F2017%2F11%2F23%2Fdexposed-on-art%2F&urlrefer=fab610acae6240dda48b7f4562731e53 随着后来VirtrualApp多开的出现,Java层的HOOK出现了VirtualXposed和太极: 均是免Root的。 10.VirtualXposed:Virtual APP与Xposed的一个结合。 11.太极:太极Xposed ☯️ 是一个无需Root、不用解锁Bootloader,也不需要刷机就能使用 Xposed 模块的一个APP。 epic:http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fgithub.com%2Ftiann%2Fepic&urlrefer=a3d143334ab62f6446eefdad3690ef37 太极阴:类似于VirtualXposed,会进行二次打包。 太极阳:跟magsik结合对系统应用进行HOOK,并且结合magsik更加的难以检测。 12.虚拟大师:一个运行在手机上的虚拟机,有点像windows下的VMware;缺点就是耗资源比较卡。 13.SandHook:坑大之作,http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fbbs.pediy.com%2Fthread-249163.htm&urlrefer=b5a58c12bf7108ae1419b7d9bd9e8bb4 14.riru:http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fgithub.com%2FRikkaApps%2FRiru&urlrefer=5b3e47eadd1271775687e813c33fc5d4替换一个会被 zygote 进程加载的共享库libmemtrack 15.EdXposed:使用的是riru+改进的yahfa 16.Magisk:另辟蹊径,通过挂载一个与系统文件相隔离的文件系统来加载自定义内容,为系统分区打开了一个通往平行世界的入口,所有改动在那个世界(Magisk 分区)里发生,在必要的时候却又可以被认为是(从系统分区的角度而言)没有发生过 隐藏Root的实现: http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fbbs.pediy.com%2Fthread-247408.htm&urlrefer=f6f057589fed176026f71aba770a21a4
有什么逆向程序流程的技巧? 1.善用堆栈 小到功能函数,大到“壳”,任何有意义的函数(不是运行完call就push个别的地址ret走的)都要维持堆栈。所以在分析函数、分析壳时,要多关注堆栈平衡 2.找到功能函数入口 除了直接用asm语言编出来的程序,其他各种语言都有一套固定的“底子”。多看文章、分析了解如果快速找到某按钮/过程的函数入口很重要。delphi有dede,其他的语言就靠经验了。(有点忘了,轻拍欢迎补充) 3.以函数为研究对象分析 除了一行一行分析最基本的函数外,最重要的目的是——这个函数(call)到底在实现什么功能?以堆栈输入的参数有哪些?寄存器的输出有哪些?输入用不用到寄存器?牵扯到哪些全局变量(直接读取写入在内存中而不走参数/寄存器) 4.静态和动态相结合 动态用OD,静态用IDA。现在IDA的X-RAY插件据说已经很完善了,不试试看?ida+od结合在ring3下就是神器…… 5.加强基础 SEH/TLS/多线程/基本反调试(虽然现在有插件了,但至少isdebuggerpresent这个api不能不知道吧)是基础内容……看懂了原理再实操比一上来就逆向要好得多……这一点推荐看雪的《加密与解密》以及看雪的精华集,几年前我还在玩逆向的时候,是每年出一版,每版收集当年论坛上的好文章,用处挺大的。平时没事可以玩玩crackme,试试做做注册机什么的。
数字加密算法 ## 一:数字签名简介 什么是数字签名?带有密钥(公钥,私钥)的消息摘要算法,用于验证数据完整性,认证数据来源,以及抗否认。通俗来讲就是证明某个消息或者文件是本人发出/认同的,这个的话用于的面就比较多了。比如电子合同,银行签约,电子授权等等。所以他的安全性是我们必须要考虑的。数字签名中常用的签名算法有RSA、DSA、ECDSA等。 ## 二:数字签名的基本过程 **基本过程如下:** (1)发送方生成非对称加密算法的公钥和私钥对,并公布其公钥和签名算法(例如sha256WithRSAEncryption); (2)发送方对发送的消息先计算其数字摘要,然后使用私钥对摘要进行加密,生成数字签名; (3)接收方在接收到声称来自XXX的消息时,先去查询XXX的公布的公钥和签名算法; (4)接收方使用公钥对数字签名解密并与计算出的数字摘要进行比对,如果比对一致,那么消息来自于XXX并且未被篡改。 **上述过程的安全前提基于以下两点:** ①发送方的签名算法无法被破解,且私钥未发生泄露 ②接收方查询的公钥以及签名算法属实 ## 三:在eclipse中分析DSA签名算法 **(1)在主类Mainactivity中分析** 分析这几个自定义方法: - getPublicKey(keyMap);//获取公钥 - getPrivateKey(keyMap);//获取私钥 - be.encode(publicKey));//公钥加密 - be.encode(privateKey));//私钥加密 - DSA.sign(data.getBytes(), privateKey);//讲私钥进行签名 - DSA.verify(data.getBytes(), publicKey, sign)+"");//进行验证**(2)在自定义类DSA中分析** 几个重要的方法 - generateKeyPair:生成密钥对 - getPublic获取公钥 - getPrivate:获取私钥 - X509EncodedKeySpec:根据给定的编码密钥创建一个新的X509EncodedKeySpec## 小结 1、介绍数字签名系列相关的知识以及实现原理。 2、在eclipse中分析了DSA签名算法的代码。 • **如果你也对安卓逆向感兴趣,请添加联系方式,微信搜索【宸道移动安全团队】,关注有惊喜哦!**
轮循检测技术 反调试的方法之一,就是保护程序的代码会一直监视进程的tracepid是否发生变化,不断轮循检查TracePid的值,假如为0的话,说明该进程没有被调试,假如不为0的话,就说明该进程正在被调试,这就是轮循检测。 ## 一:轮循检测代码 直接分析源码,如下图所示。1.查看反调试部分代码逻辑,如下图所示。2.获取当前进程的状态信息,如下图所示。3.从一系列检测状态信息中获取tracepid 字符串,如下图所示。4.如果tracepid的值一直为零,就直接循环打印,如下图所示。5.如果tracepid的值不为零,就返回false,如下图所示。## 二:演示程序运行效果 将poll_anti_debug和debugger两个文件push到手机目录下,如下图所示。 debugger文件:调试程序,选择程序的进程名称调试程序;poll_anti_debug:检测调试程序的存在。1.给poll_anti_debug权限,运行该程序,由于当前没有去调试它,所以tracepid的的值一直是零,如下图所示。2.新开一个cmd,使用调试器debugger去调试,发现字段值发生变化,如下图所示。小结 了解了轮循代码实现的原理,同时还演示了轮循检测的效果。 果你也对安卓逆向感兴趣。可以加入下方的群,大家一起讨论问题,或者扫描下方二维码关注公众号,关注回复 “安卓逆向” 获取免费教程 安卓逆向交流学习 Q群:876526335
1 下一页