Intel使用的是小端存储,也叫作逆序存储
RISC架构使用的是大端,如IBM的Power-PC
pediy的ASCII码是70h 65h 64h 69h 79h
Unicode的十六进制是0070h 0065h 。。。
Intel处理器中:7000 6500 。。。
百科上的句柄:句柄相当于应用程序每一个模块的指针。控件句柄是一个唯一的整数指向这个控件,windows会有一个句柄表,通过这个句柄表可以记录虚拟地址中控件的位置,也就是指针的作用,在Windows中大量使用,现在我们以使用指针为主。
应用程序只能通过API函数来处理不同的句柄,申请了地址后由于操作系统给应用程序分配的内存块都是可以被移动或被丢弃的,因此还需要锁定申请的内存块才可以进行使用。
1.memcpy((void*)dest,(const void*)src,(int)length);
2.
int GetWindowText(//作用是获取一个窗体的标题文字,或者一个文本控件的内容
HWND hwnd, //窗口或文本控件句柄
LPSTR lpString, //缓冲区地址
int nMaxCount //复制的最大字符数
);//如果成功就返回文本长度;失败则返回零值
3.HWND GetDlgItem(//作用是获取指定对话框的句柄
HWND hDlg, //对话框句柄
int nIDDlgItem //控件标识
);//如果成功返回对话框句柄,失败返回零值
4.UINT GetDlgItemText(//作用是获取对话框文本
HWND hDlg, //对话框句柄
int nIDDlgItem, //空间标识
LPTSTR lpString, //文本缓冲区指针
int nMaxCount //字符缓冲区的长度
);//成功返回文本长度,失败返回零值
5.UINT GetDlgItemInt(//获取对话框整数值
HWND hDlg, //对话框句柄
int nIDDlgItem, //控件标识
BOOL *lpTranslated, //接收成功/失败指示的指针
BOOL bSigned //制定是有符号数还是无符号数
);
6.int MessageBox(//创建和显示信息框
HWND hwnd, //父窗口句柄
LPCSTR lpText, //消息框文本地址
LPCSTR lpCaption,//消息框标题地址
UINT uType //消息框样式
);
unicode程序可以使用反汇编去查看内容。
GlobalAlloc()
GlobalFree() HeapFree()
unicode转为ANSI
windows是一个消息驱动式系统,系统有两种消息队列:系统消息队列和应用程序消息队列,计算机所有输入设备由windows监控。一个事件发生后windows先将消息放入系统消息队列中,再将输入的消息拷贝到相应的应用程序队列中,应用程序的消息循环从它的消息队列中检索每个消息并发送给相应的窗口函数中。
每个程序都有自己的4GB的寻址空间互不干扰。但系统dll无自己的虚拟内存空间,会被所有应用程序调用。
操作系统核心层在Ring0级别而Win32子系统运行在Ring3级别。
Ring3级别只能通过Win32 API或其他的环境子系统对他们进行访问。
PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,文件内容被分为不同的块(section),section中包含代码或数据,每个块都有自己的属性如是否可写。
PE相关名词:
.text(全是指令代码)、.rdata(只读数据)、.data(初始化数据块)、.idata(外来的dll函数及数据信息,即输入表)、.rsrc(全部资源)
入口点、文件偏移地址、虚拟地址、基地址(VC++建立的exe是00400000h,dll文件基地址是10000000h,不过可以改)
在磁盘和在内存中的结构是一致的,装载一个可执行文件到内存中,主要就是将一个PE文件的某一部分到映射到地址空间中。
OD的F7跟进call F8路过call F9到达ret
按F7可以进入系统dll的领空,alt+F9可以回到用户程序中
领空是指某一时刻CPU的CS:EIP所指向的某段代码的所有者。
F2设置INT3断点。INT3断点,指令码为mov eax,0CCCCCCCh。这个断点会导致一个异常,调试器捕捉到这个异常从而停在断点处,然后将断点处的指令恢复成原来的指令。
缺点是改变了原程序指令,易被检测到防下断。将断点下在函数的内部或末尾,就可以躲过检测。
调试时一般下断点在Entry point of main module或WinMain处即可。
在OD里按F9键将程序运行起来,按Ctrl+G键打开跟随表达式的窗口,输入GetDlgItemText/+A/+W或者是GetWindowText/+A/+W后单击ok就会到达USER32.DLL中的函数入口处。
API函数采用标准__stdcall调用约定。函数入口参数从右到左的顺序入栈,由调用者清理栈中的参数。
eax一般用来保存所有API函数的返回值,esp是栈顶寄存器,ebp是栈底寄存器。
eax是累加器(加法乘法指令),ebx是基址寄存器,ecx是计数器(是rep和loop的内定寄存器),edx用来存除法的余数。
eip用来存放下一个CPU指令存放的内存地址,当CPU执行完当前指令后,从eip中读取下一条然后执行。
esi和edi,低16位对应先前CPU中的si和di,低16位是对数据的读取,主要用于存放单元在段内的偏移量,用它们可以实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。也可以存储算数逻辑运算的操作数和运算结果。但是在串处理中一定是SI为原串地址,DI为目的串地址。
寄存器BP是和SP混合使用的。
标准代码段:
push ebp;保存当前ebp
mov ebp,esp;或者是mov [ebp-18],esp ebp设为当前堆栈指针
sub esp,xxx;预留xxx字节给函数的临时变量。
ja(jump if above)
jb(jump if below)
jnz(jump if not zero)
je(jump if equal)
序列号判断je short 0040122E,此处不跳转就可以注册成功,因此用nop(9090)替换这两行,就可以了。这种方式是补丁。
如果是逆向分析算法的话写出来的东西就是注册机。Call dword ptr [00404004]。因为一定会有lstrcmp函数来进行序列号对比,此时就会出来真正的序列号。
常见手段:
1.序列号保护机制
用以验证用户名和序列号之间的映射关系,映射关系越复杂注册码就越不容易被破解。
也可以不映射使用求逆的方式进行分析写出注册码算法。
如何攻击序列号保护:一种是跟踪输入码,将编辑框中的注册码字符串拷贝到自己的缓冲区中根据断点找到对应位置。
另一种是跟踪程序启动时对注册码的判断,因为程序每次启动时都需要将注册码读出来加以判断,从而决定是否以注册版的模式工作,根据序列号存放的位置不同可以使用不同的API断点。如果放在注册表中,可以用RegQueryValueExA(W);如果在ini中,用GetPrivateProfileStringA(W)等。如果在一般文件中,则CreateFileA(W)、_lopen()等函数。(惯例为存放用户输入序列号的内存地址+—90h字节的地方。)
软件作者一般都使用局部变量存放临时计算出来的注册码,使得他们可以在同一个Watch窗口中见到。
hmemcpy俗称万能断点。
按钮的消息断点:WM_LBUTTONDOWN和WM_LBUTTONUP。利用这个消息下断很容易找到按钮的事件代码。
利用提示消息搜索字符串。
xor eax,eax;对eax清0
寄存器比较:
mov eax [];
mov ebx [];
cmp eax,ebx;
jz(jnz)
串比较:比较字符串是否相等
lea edi [];
lea esi [];
repz cmpsd;
jz(jbz)
函数比较:
push xxx
push xxx
call xxxxxx
test eax eax
jz(jnz)
test属于逻辑运算指令,而cmp属于算术运算指令,记ZF位为1或0来运算JZ与JNZ。
可求逆的指令:xor/xor add/sub inc/dec rol/ror
KeyFile是一种利用文件来注册软件的保护方式
加密算法:
单向散列算法:哈希算法,是一种将任意长度的消息压缩到某一固定长度的函数,不可逆,可用于数字签名,消息完整性认证,消息起源认证检测等,常见的散列算法有MD5、SHA、HAVAL等。
MD5也称消息摘要算法,对输入的任意长度的消息进行运算,产生一位128位的信息摘要。
SHA是安全散列算法,常用SHA-256/384/512
对称加密算法的加密密钥和解密密钥是相同的DES、IDEA、AES、RC4
公开密钥加密算法RSA,既可以用作数据加密也能用于数字签名的算法。
数字签名分为签名过程和验证过程,签名是使用私钥,验证时使用公钥。
加密过程则是加密时使用公钥,解密时使用私钥。
还有CRC32算法、Base64编码等。
PE文件结构在磁盘上是以0x200为基址偏移的,在内存中是以0x1000为基址偏移。
默认情况下exe的内存中基址为0x00400000,dll的内存中的基址为0x10000000
RVA相对虚拟地址是针对虚拟地址中的基址而言的。
自定义数据存放区块:#pragma data_seg("MY_DATA")
相似的区块可以进行合并使用MERGE指令,可以节省内存磁盘空间。
区块的对齐值会产生区块间隙,用0来填充。PE中典型的对齐值是200h。一个页一般为4kb(1000h)来排列的或者是8kb(2000h)。每个区块的第一个字节对应于某个内存页。因此每个区块按1000h的倍数的内存偏移位置开始。
可执行文件使用来自于其他DLL的代码或数据时,称为输入,PE文件装入后windows加载器的工作之一就是定位所有被输入的函数和数据。输入表IT也称为导入表,输入表中保存的是函数名和其驻留的DLL名等动态链接所需的信息。外壳程序负责把用户原来的程序在内存中解开压缩并把控制权交还给解开后的真正的程序。一切工作在内存中运行用户根本不知道也不需要知道其中的运行过程。如果在外壳中加入对软件锁或钥匙盘的验证部分就是外壳保护了。有压缩壳和加密壳。外壳代码甚至可以检测系统的调试状态等,检测到非法活动后可以直接停止外壳解密或解压缩的过程。
输入函数就是被程序调用但是其执行代码又不在程序中的函数,这些函数的代码位于相关的DLL文件中,只有在加载入内存后才可以被调用。隐式链接到dll由Windows加载器完成,显式链接说明确定DLL已被加载,然后寻找API地址(通过LoadLibrary和GetProcAddress来完成)
如正常程序都需要链接KERNEL32.DLL,而它又从NTDLL.DLL输入函数。如果链接了GDI32.DLL又依赖于USER32、ADVAPI32、NTDLL和KERNEL32等DLL函数。
一旦模块被装入,IAT中包含所有调用输入函数的地址。(IAT中存储一组函数指针,PE中存在一组数据结构保存了DLL名字以及一组函数指针)。
CALL DWORD PTR [00402010](高效)
CALL 00401164(低效)
:00401164
Jmp dword ptr [00402010];//指向USER32.LoadIconA函数
由于编译器无法区别输入函数的调用和普通函数的调用。因此要告诉编译器__declapec(dllimport)就会让编译器产生高效的形式。给函数加上__imp_前缀送给链接器可以直接把参数送到IAT而不经过JMP stub。
如果被装载到虚拟内存的另一地址而不是默认地址则需要进行基址重定位,由.reloc块负责。
call会把指令地址压入堆栈而jmp只是单纯的跳转。call本质相当于push+jmp,ret本质相当于pop+jmp。
绑定输入,就要程序员实现能正确预测函数地址,不用每次都装入PE文件时都去修正IMAGE_THUNK_DATA的值了。
资源用类似磁盘目录结构的方式保存。
结构一共有三层:用于第一层时定义的是资源类型,第二层定义资源名称,第三层定义代码页编号。当最高位为0时字段作为ID使用(也就是IMAGE_RESOURCE_DIRECTORY_ENTRY),最高位为1时字段作为指针使用。经过三层IMAGE_RESOURCE_DIRECTORY_ENTRY后第三层的OffsetToData指向IMAGE_RESOURCE_DIRECTORY_ENTRY结构,该结构描述了资源数据的位置和大小,是RVA值。每一层使用偏移地址、Name/Id、OffsetToData来记录资源。
编译器可以直接修改PE中的文件值.rsrc,资源数据通常不能由程序源代码定义的变量直接访问,而是通过直接或间接地加载到内存中以备使用。资源通常包括菜单、对话框、串表等资源。
TLS(线程本地存储器)可以将数据与执行的特定线程连起来,当使用__declspec(thread)声明的TLS变量时,编译器将它们放入.tls区块中,当应用程序被加载到内存中时,系统要寻找可执行文件中的.tls区块并动态分配一个足够大的内存块,以便存放所有的TLS变量。TLS数组中也要存放一个指向已分配内存的指针。由FS:[2Ch]指向。TLS是程序最开始运行的地方,开始和结束时都会回调一次。
在一个进程中所有线程共享同一个地址空间,所以如果一个变量是全局或者是静态的,那么所有线程访问的是同一份,如果一个线程进行了修改会影响到其他线程。所以使用基于堆栈的自动变量或函数参数来访问数据,基于堆栈的变量总是和特定的线程相联系的。
TLS的作用就是能将数据和执行的特定线程联系起来。当我们依赖全局变量或静态变量,保证多线程程序能访问而不互相影响。
延迟装入一个DLL是一种混合方式,通过LoadLibrary和GetProcAddress获得延迟加载函数的地址,然后直接转向对延迟加载函数的调用。
IMAGE_DIRECTORY_ENTRY_TLS存储tls相关信息。
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT条目指向延迟装入的数据。
IAT也就是通过INT中的函数名或者hint号直接加载动态链接库,获取相应的API函数在虚拟内存的地址,然后保存在IAT中。
OriginalFirstThunk和FirstThunk是两个DWORD值,存储着两个RVA数值。其实就是两个指针。指向同一个数组前者叫做INT后者叫做IAT。
IAT是一个IMAGE_THUNK_DATA类型的数组,有多少个函数被导入就有多少个成员。
一个dll一个IAT表,IAT表们都是统一存储在一起的,每个IAT表以0结尾从而分离。
程序异常处理有基于框架的模式和基于表的模式。
.NET环境由公共语言运行环境CLR和.net框架的类库组成。CLR可以看做是一台虚拟机。.NET可执行文件是为了获得特定的装入内存的信息,有元数据和中间语言(IL)。
依靠MSCOREE.DLL作为程序的入口。
编写PE分析工具:
1.文件格式检查(判断是否为PE格式,判断文件开始的第一个字段是否为IMAGE_DOS_SIGNATURE,即5A4Dh。再通过e_lfanew找到IMAGE_NT_HEADERS,如果signature字段值为IMAGE_NT_SIGNATURE也就是00004550h。则说明是PE格式)
2.读取FileHeader和OptionalHeader内容(用wsprintf将欲显示的值进行格式化,再调用API函数的SetDlgItemText即可)
3.得到数据目录表信息(由一组数据显示,每组包含了执行文件的重要部分的起始RVA和长度)
4.得到区块表的信息(可以通过IMAGE_FIRST_SECTION宏来轻松得到头IMAGE_SECTION_HEADER的位置。)
5.得到输出表的信息(是一个表格内含函数名称,输出序数等。IMAGE_EXPORT_DIRECTORY)
6.得到输入表的信息(以IMAGE_IMPORT_DESCRIPTOR结构开始,可通过GetFirstImportDesc函数获得位置。循环终止条件是这个结构为空)
ImageHlp库中提供了大量的对PE image操作的API。
结构化异常处理
SEH分为两类:一类是监视某线程中某段代码是否发生异常(线程相关异常、PerThread类型异常),另一类是监视整个进程中所有线程(进程相关异常、Final型异常处理)。
TEB结构:线程环境块。windows在创建线程时都会为线程分配TEB,且都将FS段选择器指向当前线程的TEB数据,这就为程序提供了存取TEB数据的途径,由[FS:0]指向。
EXCEPTION_REGISTRATION结构:描述线程异常处理过程的地址。不知道怎么处理时先去查找异常处理链表。跟踪时需要提前设断。
EXCEPTION_POINTERS结构:包含EXCEPTION_RECORD和CONTEXT结构。
虚拟机保护软件
堡垒战术:MD5、RSA算法适合在软件注册算法中运用。
MD5由于不存在逆算法所以不直接用来对消息进行加密。也不适合拿来做注册机,由于MD5不存在逆函数,验证函数将不得不包含注册机。
RSA算法中存在若干“弱密钥”,在数论高手面前虚弱无力。
游击战术:将验证函数F分成多个不同的Fi,然后隐藏到程序当中去。也要放一些假的进去,还有战略转移,软件需要不停地将注册码搬家。
1.内存拷贝(会被用内存监视断点识破)
2.写入注册表或文件,然后在另一处代码中读入另一个内存地址(会被解密者的注册表、文件监视工具识破)
3.一次将注册码拷贝到多个地址,让解密者无法确定是哪一个
反静态分析技术上从扰乱汇编代码的可读性入手,如使用大量的花指令、将提示信息隐藏等。
数据与代码的区分问题。主要两类反汇编是线性扫描算法和递归行进算法
花指令(加入很多数据垃圾,干扰反汇编软件的判断)OD上有一个功能能根据收集的花指令特征码将垃圾数据替换为NOP指令。
SMC
SMC技术
远程连接使用的是mstsc
CRC可以实现磁盘文件校验的实现。在文件发布时用散列函数计算文件的散列值,并将此值放在某处,重新计算以判断文件是否被修改。
校验和,比较好的办法把校验和放在注册表中,重要的和系统的DLL及驱动文件中必须有一个校验和。
内存映像校验:数据区块的校验没有意义,代码区块在程序运行中是不会改变的,因此可以进行内存校验。CRC法会影响INT3断点,但是硬件断点可以用。
exe不存在重定位的问题,而dll还需要考虑重定位数据。
文件补丁:修改文件本身的数据。内存补丁:在内存中打补丁,也就是对正在运行的程序的数据进行修改。
如果待补丁文件被加壳,压缩或有完整性校验那么文件补丁就不能被使用,内存补丁的思想就是在某个时刻(解压、校验或某种情况发生以后),在目标程序的地址空间中修改数据,因此也被称为loader。
1.跨进程内存存取机制,一个进程与另一个进程之间应该是绝缘的,也就是说一个进程的地址、指令、内存使用等信息对于另一个进程应该是完全透明的。进程之间需要是封装好的也需要沟通。如friend、public、private、protected等存取层级概念。在windows中提供了两个用于进程互访内存的函数:ReadProcessMemory和WriteProcessMemory,只要进程的足够权限打开,就可读写进程的地址空间。可用VirtualProtect函数设置权限。
Loader可以用CreateProcess函数载入待补丁程序。资源释放、清理工作可以由ExitProcess函数完成。流程的核心在于流程中间的Loader对创建出来的待补丁进程的控制和修改。将要补丁的代码通过WriteProcessMemory函数写入目标进程的适当位置。
如果加了壳则需要先创建一个进程然后挂起读取目标代码是否到了补丁时机,否则继续让目标程序运行,运行后再挂起判断,直到目标进程进行解码。可用FindWindow来判断。用Read来对目标进程进行校验,Write再写入。由于无法精确定位一般是通过CreateProcess后马上Suspend目标进程,等补丁完成后再Resume。
理想的是拥有一个通知机制,当EIP=目标地址时发送一个消息(可以通过GetThreadContext获得EIP)。Debug API提供了相关机制。DEBUG API中有一个EXCEPTION_DEBUG_EVENT调试事件。
机制为:
1.进程A产生异常
2.操作系统检测到异常将异常包装到EXCEPTION_RECORD结构中。
3.查找正在对进程A进行调试的进程,将该结构通过EXCEPTION_DEBUG_EVENT消息发送给查找到的进程。
4.陷入循环的调试进程收到进程A的调试信息解析该信息并进行相关操作。
发送调试信息的方法:
单步程序控制,每执行一步都要和调试进程进行一次通信,效率低下。设置SF标志位为1。
INT3设断,在软件被外壳保护,CRC校验功能强大,多层SMC或代码动态生成机制时都不适用。
DLL劫持操作(适合加壳程序的补丁),windows加载器会将可执行模块映射到进程的地址空间中。DLL保存只有名字没有路径因此需要去磁盘查找。首先会从当前程序所在的目录上加载DLL,没有则去windows系统目录上查找,最后实在环境变量中列出的各个目录下查找。此时伪造一个dll放入当前目录下完成后再跳到同名dll函数中运行。除了对核心的dll无效以外,对网络应用程序ws2_32.dll、游戏程序中的d3d8.dll等大部分应用程序的lpk.dll都可被劫持。
补丁编程的实现:
00401496 EB 29 jmp short 004014C1
//将401496改为EB 29
unsigned char p401496[2]=(0xEB,0x29);
WriteProcessMemory(hProcess,(LPVOID)0x401496,p401496,2,NULL);
SMC能修改自身代码,可以对加壳程序直接进行补丁,相当于内存补丁。
入口点也被称作OEP
代码的二次开发:在没有源码和无接口的情况下扩充可执行文件的功能。
数据对齐是CPU结构的一部分,要与windows系统核心打交道一定要让其4字节对齐。映像页面相关的数据必须对齐排列,PE文件数据是按照这个要求对齐的。
增加区块需要:增加一个块头(IMAGE_SECTION_HEADERS STRUC),增加块头指向的数据段(用十六进制工具在文件尾部5000h处插入1000h大小的数据块),调整文件映像尺寸(修改SizeOfImage的值)。
一种是修改输入表结构,增加相应的API函数;另一种是显式链接调用DLL相关函数。
(LoadLibraryA、GetProcAddress)
WndProc是由一系列case语句组成的消息处理程序段。想增加功能就在WndProc里加入新的消息判断和事件代码。修改时不得破坏原有的消息循环和堆栈平衡。控件ID不能重复。
还可以增加接口,更好的办法是将消息循环延伸到dll里来处理,处理完再转到原WndProc中执行。
什么是SOAP:
WebService是一个平台独立的,低耦合的、自包含的、基于可编程的web的应用程序,可使用开放的XML标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。
SOAP是简单对象访问协议:用于交换XML编码信息的轻量级协议。主要有XML-envelop为描述信息内容和如何处理内容定义了框架,将程序对象编码成为XML对象的规则,执行远程过程调用RPC的约定。SOAP可以传输在任何其他的传输协议上。在传输层之间头是不同的,但XML有效负载保持相同。不同系统之间使用软件-软件对话。
WSDL是用机器能阅读的方式提供一个正式描述文档而基于XML的语言,用于描述WebService及其函数、参数和返回值。
UDDI基于Web的、分布式的、为WebService提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的WebServcie进行注册,以使别的企业能够发现的访问协议的实现标准(有点像ZooKeeper吗?)
RPC和消息传递。
基于类对象的传输协议。
SOAP封装,定义了一个框架,描述消息中的内容是什么,是谁发送的,谁应当接受并处理它以及如何处理它们。
SOAP编码规则,它定义了一种序列化机制,用于表示应用程序需要使用的数据类型的实例。
SOAP RPC表示定义了一个协议,用于表示远程过程调用和应答。
SOAP绑定,定义了SOAP使用哪种协议交换信息,HTTP/TCP/UDP都可以。