逆向笔记
逆向的开始!从这开始记录我在做逆向题目时遇到的问题以及总结一些逆向的知识点
BFS脚本:
1 |
|
Frida环境搭建与测试:
进入开发者模式,与手机进行连接adb connect 127.0.0.1:62001
(PC端命令行)
查看是否连接成功 adb devices
frida-server处理器类型与Android处理器对齐
查询虚拟机处理器架构: adb shell getprop ro.product.cpu.abi
进入frida移动端目录 adb shell
cd /data/local/tmp
运行 ./frida-server
执行 frida -U -f MyApplication
进行连接\
显示找不到MyApplication,查询app名称的id frida-ps -U -a
结果:
再次执行 frida -U -f com.example.myapplication
连接成功,脚本测试
1 | import frida |
RC4解密脚本:
1 | import base64 |
&0xff在python中可以表示取无符号整数,0xff表示八个比特1,也就是数byte数进行无符号运算,&0x7f表示取有符号字节类型
RC4的c语言加密脚本:
1 |
|
AES加解密C脚本
1 |
|
.rodata段是用来存放只读数据的一块内存区域,例如常量数据。ro代表read only,即只读的意思。.rodata段也叫常量区,它属于静态内存分配。使用const修饰符,例如
const int a = 10;
,这样的变量会被放在.rodata段中
Ida Python脚本:
1 | start = 0x00401500 |
attribute实现main函数之前执行相应函数:
1 |
|
函数调用默认传参的几种方式
$$
cdecl:这是C语言默认的调用约定,它要求参数从右向左依次压入栈中,由调用者负责清理栈空间。这种方式适用于可变数量的参数,如printf函数。在Windows和Linux系统中,cdecl通常用于32位x86架构的编译器。
$$
$$
stdcall:这是Windows API默认的调用约定,它要求参数从右向左依次压入栈中,由被调用者负责清理栈空间。这种方式适用于固定数量的参数,如MessageBox函数。在Windows系统中,stdcall通常用于32位x86架构的编译器。
$$
$$
fastcall:这是一种优化的调用约定,它要求将部分参数通过寄存器传递,而不是全部压入栈中,以提高函数调用的效率。不同的编译器可能会有不同的fastcall实现,例如Microsoft Visual C++和Borland C++ Builder分别使用ECX和EDX两个寄存器传递前两个参数,而GCC则使用EAX、EDX和ECX三个寄存器传递前三个参数。在Windows和Linux系统中,fastcall通常用于32位x86架构的编译器。
$$
$$
thiscall:这是C++类成员函数默认的调用约定,它要求将对象指针(this指针)通过ECX寄存器传递给函数,而其他参数则从右向左依次压入栈中。由于thiscall只适用于类成员函数,因此它通常不需要显式指定。在Windows和Linux系统中,thiscall通常用于32位x86架构的编译器。
$$
$$
syscall:这是Linux内核默认的调用约定,它要求将系统调用号通过EAX寄存器传递给内核,而其他参数则通过EBX、ECX、EDX、ESI、EDI和EBP六个寄存器按顺序传递给内核。如果参数超过六个,则需要通过栈传递。在Linux系统中,syscall通常用于32位x86架构的编译器
$$
$$
在64位x86架构(x86_64或AMD64)的编译器中,通常会有更多的寄存器可供使用,因此参数传递方式也会有所不同。例如,在Windows系统中,x64调用约定要求将前四个整数或指针类型的参数通过RCX、RDX、R8和R9四个寄存器传递,而前四个浮点类型的参数通过XMM0、XMM1、XMM2和XMM3四个寄存器传递;在Linux系统中,System V AMD64 ABI要求将前六个整数或指针类型的参数通过RDI、RSI、RDX、RCX、R8和R9六个寄存器传递,而前八个浮点类型的参数通过XMM0到XMM7八个寄存器传递。
$$
gbk解码转中文,先ida64 convert to string,再用b修饰,解码即可
1 | str = b"\xD7\xA2\xB2\xE1\xB3\xC9\xB9\xA6\xA3\xA1" |
异常处理和反调试
反调试的几种方式记录
1 |
|
dll的编译和使用
先编译dll文件,新建生成dll项目,创建一个新的Mydll.cpp和Mydll.h
Mydll.cpp
1 |
|
导出的两种方式的其中第一种方式,头文件导出
1 |
|
导出的第二种方式,源文件新建一个export.def文件
1 | LIBRARY "Mydll" |
新建一个项目导入生成的dll文件
导入的第一种方式,隐式链接的方式调用dll导出寒素
#pragma comment(lib,"Mydll.lib")//lib地址
头文件main.h
1 |
|
源文件main.cpp
1 |
|
第二种显示链接方式调用导出函数
不需要头文件和lib,源文件main.cpp如下
1 |
|
c语言RC4,base64加解密代码
1 | void init(unsigned char* S, const unsigned char* key, int keylen) { |
lea指令使用,防止遗忘
lea,官方解释Load Effective Address,即装入有效地址的意思,它的操作数就是地址;
常见的几种用法:
1、lea eax,[addr]
就是将表达式addr的值放入eax寄存器,示例如下:
lea eax,[401000h]; 将值401000h写入eax寄存器中
lea指令右边的操作数表示一个精指针,上述指令和mov eax,401000h是等价的
2、lea eax,dword ptr [ebx]
;将ebx的值赋值给eax
3、lea eax,c
;其中c为一个int型的变量,该条语句的意思是把c的地址赋值给eax;
内联汇编实现部分常用函数功能练习
在全局内定义字符串后的print hello world,因为编译器会编译的时候会自动寻址,最简单的一种方法
1 |
|
利用局部变量字符串,将字符串push进栈中,通过栈寻址,push传参来实现功能
1 |
|
利用kernel32.dll或者kernelbase.dll模块寻找loadlibrary和getprocaddr函数地址,以此加载想要的模块和获得想要的函数地址,从而调用想要的函数
1 |
|
准备了一个表格存储栈信息,ebp2对应的是print函数的栈底,pushadd到ebp2是Myshell函数形成的栈帧
msvc | |
---|---|
printf | |
28 | 6e |
printf 24 | 66 |
20 | msvc |
1c msvc | 63 |
18 | 64 |
14 | 6c |
esp 0x10 | |
0xc | get_adr |
0x8 | load_adr |
0x4 | kernel |
ebp2 | ebp1 |
4 | eip |
8 | ecx=esp1 |
load c | 64 |
10 | 72 |
14 | 41 |
18 | 0 |
get1c | 50 |
20 | 41 |
24 | 65 |
28 | 73 |
hello 2c | 6c |
30 | 6f |
34 | 21 |
38 | 0 |
pushadd | ad |
重温栈结构
今天在准备写一个壳的时候需要手写汇编,把汇编十六进制插入PE文件中,一时对栈结构有点遗忘,在xdbg中调了会温故了一下,在这里记一下,防止下一次遗忘
比方说我们有一个main函数时,在函数开始时经常有如下汇编代码
1 | push ebp |
为什么会出现以上代码?在主函数调用的子函数中能不能不出现这些代码?
push ebp 做的是一个保存ebp的操作,mov esp,ebp为下一个栈帧形成打下基础,在新栈帧中利用栈来保存数据,并且不会破坏原来栈帧中的数据
sub esp,0x20则是开辟了一个新的栈空间,这段空间可被函数直接利用,比如说做一些局部变量的初始化操作,也可以省略这部操作,直接通过push提高栈顶(地址下降)然后再通过mov在栈中储存数据(初始化局部变量)
主函数下的子函数结构和main函数类似,其实也可以说main函数时其他函数的子函数,没有什么较大的区别
在call的时候其实把下一条要执行汇编代码地址push进了栈中,然后在函数内部在push ebp这些操作,使得每次函数调用结束时,会出现以下代码
1 | pop ebp |
ret返回的正是下一条汇编代码执行处
在函数调用时,有时通过push来传递参数,这个时候栈顶提高,但是参数传递之后对主函数没有任何作用,所以函数调用结束时主函数有时候会通过add esp来调整栈空间。
push指令在x86架构下是推入四个字节数据或地址,推入常量高位用0填充,可以通过mov指令实现对栈中数据进行字节位的调整
ret 和 retn的区别
- ret和retn都是返回指令,用于从子程序中返回到调用处,同时从栈中弹出返回地址
- ret和retn的区别在于是否有操作数。ret没有操作数,只是从栈中弹出返回地址;retn有一个操作数,表示要从栈中弹出的字节数
- 例如add esp,0x4; ret;,等价于retn 0x4
repe cmpsb字符串比较汇编指令
是一个汇编语言指令,它的意思是重复比较两个字符串的字节,直到不相等或者计数器为零为止。它涉及到三个寄存器:ECX,EDI和ESI。ECX是计数器,表示要比较的字节数;EDI和ESI是两个字符串的起始地址。每次比较后,EDI和ESI会根据方向标志DF的值自动增加或减少。如果DF为0,表示正向比较,EDI和ESI都加一;如果DF为1,表示反向比较,EDI和ESI都减一。比较的结果会影响标志寄存器中的零标志ZF。如果ZF为1,表示两个字节相等;如果ZF为0,表示两个字节不等。repe cmpsb通常用来检测两个字符串是否完全相同
jmp指令偏移地址计算
在使用jmp汇编指令时,关于算偏移地址的时候机器码十六进制的计算
往下运行时,0x2 = 0x44A04D-0x44A049-0x2,也就是jmp到的地址-原来jmp前的地址-jmp这条指令的字节数,本质就是算中间有多少字节
往上运行时,0xFFF870F7 = 0x401140 - 0x44A044 - 5 最后一个五是jmp指令的字节数,0xFFF870F7转化成机器码就是F770FBFF
IDAPatch保存后动调
Keypatcher进行patch,ctrl+w保存patch结果,但是动调有影响
Apply patches to后动调即可 ,在edit-patch program里
Ollvm控制流平坦化技术
ghidra自动化去混淆工具: https://github.com/PAGalaxyLab/ghidra_scripts
IDA自动化去混淆工具d810: https://github.com/joydo/d810
Hook
Hook的多种方式
HOOK和Callback的区别
钩子技术(Hook)和回调函数(Callback)在编程中都是用于实现程序的扩展和灵活性,但它们有着不同的作用和实现方式。
相同点
- 事件响应: 钩子技术和回调函数都用于实现对特定事件的响应和处理。
- 灵活性: 两者都可以增强程序的灵活性和扩展性,使得程序能够更好地处理不同的情况和需求。
差异性
- 作用:
- 钩子技术主要用于拦截、监视和修改系统级别或应用程序级别的事件,比如键盘输入、鼠标操作、窗口消息等。
- 回调函数主要用于在特定事件发生时被调用,通常用于异步编程或事件驱动编程中,比如网络请求完成后的回调函数、定时器触发后的回调函数等。
- 实现方式:
- 钩子技术通常是通过操作系统提供的接口或库函数来实现的,可以在系统级别或应用程序级别进行钩子的安装和管理。
- 回调函数是一种编程模式,通过将函数指针或函数引用传递给其他函数,以便在特定事件发生时被调用。
- 应用场景:
- 钩子技术适用于需要拦截、监视和修改事件流的情况,比如实现键盘记录器、窗口管理工具等。
- 回调函数适用于需要异步处理或事件驱动的情况,比如处理异步任务完成后的结果、处理定时器事件等。
综上所述,钩子技术和回调函数虽然都与事件响应相关,但它们的作用、实现方式和应用场景有着明显的区别。钩子技术更专注于事件的拦截和修改,而回调函数更专注于事件的响应和异步处理。
从代码实现角度来看,Hook主要分成两种,Inline Hook 和 非 Inline Hook
Inline Hook指的是:
在call的调用的函数内里添加 jmp 想执行代码地址
非Inline Hook指的是:
事实上就是改变了Call操作的目标地址,实际上就是IAT Hook
C++虚表的Hook
一个类的内存布局:
通过对象的地址来获得虚表的首地址,从而获得所有虚函数的地址
C++调用fish类的虚函数代码:
1 | fish Myfish; |
汇编调用fish类的虚函数代码:
1 | mov eax,ecx //eca是MyFish对象的地址 |
Detours Hook
框架学习
1 | //1.保存detour的事务 |
Hook MessageBox函数使得弹窗显示永远是 “你已经被Hook了!”
mfc程序
mfc.cpp
1 |
|
dll程序
solve.cpp
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
Dll1main.cpp
1 |
|
Windows的S.E.H
S.E.H全称是Struct Exception Handler即结构化异常处理机制
S.E.H是操作系统提供给线程来感知和处理异常的一种回调机制,S.E.H在线程栈上以单链表的形式存在
在win32中由于FS寄存器总是指向当前的TIB(线程信息块),因此在FS:[0]处能找到最近的一个EXCEPTION_REGISTERATION结构
当我们通过try/catch或__try/except等操作来注册S.E.H的时候,FS:[0]会指向新的S.E.H,且新的S.E.H的Prev字段会指向之前FS:[0]指向的S.E.H,整个操作类似于单链表的表头插入操作
Windows的V.E.H
S.E.H是基于线程的异常处理,V.E.H是基于进程的异常处理
V.E.H处理异常的优先级低于调试器,高于S.E.H,即KiUserExceptionDispatcher()函数首先检查进程是否处于被调试状态,然后检查V.E.H链表,最后才是检查S.E.H链表
Windows提供了注册V.E.H的回调函数的API
1 | PVOID AddVectoredExceptionHandler//注册回调函数 |
可以通过该函数进行虚假注册,通过函数返回值后去链表的头指针或者尾指针,从而遍历整个V.E.H链表
硬件断点
调试器寄存器
IA-32处理器定义了8个调试寄存器,DR0-DR7其中DR0-DR3用来指定断点的内存地址或IO地址;DR4和DR5是保留的,DR6的作用是当调试事件发生时向调试器报告事件的详细信息,以供调试器啊判断发生的是何种事件;DR7用来进一步定义中断条件
硬件断点的优势
硬件断点HOOK是结合DR0-DR3调试寄存器和Windows S.E.H 或V.E.H机制所引入的一种HOOK机制.如果HOOK采用的方式是修改代码,那么很容易被检测到,然而硬件断点HOOK并不涉及修改代码,所以他的优点主要体现在隐蔽性上
线程上下文环境以及Windows下线程与CPU之间的关系
线程执行环境CONTEXT的结构体:
1 | typedef struct_CONTEXT |
Windows是基于线程调用的多任务抢占式操作系统,如果CPU目前运行的线程1,这时优先级更高的线程2将抢占执行,那么操作系统会把线程1的执行环境取出放到线程1的CONTEXT结构中,接着把线程2的CONTEXT信息放入CPU中,继续运行线程2
S.E.H Hook
未成功代码,显示有没经处理的异常
应该是MyExceptionFilter函数没起作用
dll中
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
V.E.H Hook
试验成功
1 | //dllmain.cpp |