技术 逆向 64位逆向 ZEROKO14 2023-11-24 2023-11-24 64位游戏逆向经验
64位 64位逆向比32位还要简单一点
32位与64位的区别 内存大小的区别 32位:FFFFFFFF
64位:FFFFFFFFFFFFFFFF
寄存器的区别 32位:eax ebx ecx edx ebp esp esi edi
64位:rax rbx rcx rdx rbp rsp rsi rdi r8 r9 r10 r11 r12 r13 r14 r15
64的高8位很多是0
浮点寄存器的区别 32位:XMM0–XMM15 64位
64位:XMM0–XMM15 128位
64位添加了YMM0–YMM15 128位
寄存器的作用的区别 rax和eax依然作为返回值
32位的函数约定有很多种
64位的函数约定默认设置了fastcall,因此基本只用一种:fastcall 函数约定。函数传递方式几乎是固定的了
32位的fastcall: 优先ecx和edx来传递参数,再有参数的话用push
64位的fastcall: 优先rcx,rdx,r8,r9来传递参数,再有的话,就从堆栈传递(和32位有一点区别,开辟堆栈空间直接往里面赋值的,看不到push)
注意:再有参数从[esp+20]开始传递
因为编译器默认还会把那四个用寄存器传的值依然预留四个堆栈位置[rsp] [rsp+8] [rsp+10] [rsp+18],因为有时候依然会使用堆栈来访问传入参数。
rsp和esp一样依然是栈顶指针
rbp偶尔作为栈底指针,偶尔作为通用寄存器,偶尔作为其他指针。但ebp常作为栈底指针,而64位rbp更乱。
除了返回值:rax 参数:rcx rdx r8 r9 栈顶指针: rsp 这6个有特殊意义的寄存器,如果你想要在函数调用过程中使用其他所有寄存器,必须先push保存,然后retn前还原。
call的内部必须保存和还原寄存器了
64位的堆栈空间开辟都固定在头部了,局部变量和内部call的参数统一开辟,不能像32位先开辟空间,然后又push参数等方式继续开辟空间。然后在retn前统一还原堆栈
64位做了很多规范,使很多东西统一了,却反而更好逆向了。
64位堆栈与函数 小于4个参数的时候 (1)看rcx,edx,r8,r9看是否在call xxxxxx之前被赋值了,若被赋值了,很大概率就是参数。
(2)进入call内,若出现mov rcx,【123456】,说明rcx不是参数,无用。若出现mov rax,rdx说明rdx是参数有用。(但正常情况下,用rdx和后面的寄存器却不用rcx的情况特别少,但也有)
大于4个参数的时候 只要在call上面看到有mov [rsp+20],xxxxx就说明他是一个参数,另外rcx,rdx,r8,r9也是四个参数。
64位几乎不用考虑其他寄存器的值,就写完参数直接call就可以了,并且几乎不用考虑堆栈平衡。
因此函数里面不存在改变堆栈的操作了
栈地址,下断看看是返回到的上面还是返回到的下面,如果是参数就直接返回去追,如果是局部变量就直接往上追就可以了
rbp+-判断是局部变量还是参数就不行了,而是就正常的去往上追
x64写汇编 读写内存的时候涉及到64位的QWORD
另一个最重要的区别就是不能直接内联汇编了。
操作如下:
新建了x64项目后
右键项目选生成自定义,然后勾上masm,如下:
点确定。
新建后缀名为xxx.asm的文件
xxxx.asm后缀文件上右键属性
然后在xxx.asm中加上如下正确的代码:
1 2 3 4 5 .code //所有汇编代码写在.code和end之间 end
在汇编中写main函数示例 extern可以置于变量或者函数 前,以标示变量或者函数 的定义在别的文件中,提示编译器遇到此变量和函数 时在其他模块中寻找其定义。 另外,extern也可用来进行链接指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 extern getchar:far .code main proc//main函数开始 sub rsp,100h call getchar//调用getchar函数 add rsp,100h ret main endp//main函数结束 end
重点1:注意汇编中16进制必须后缀加h的写法
重点2:想使用函数必须在.code外面声明extern 函数名:far,然后才能到.code和end中call 函数名
重点3:内联汇编文件中的代码不能加注释(会报错),上面的注释只是写给自己看的。
cpp文件中的函数怎么由内敛汇编中调用 C++ 语言在编译的时候为了解决函数 的多态问题,会将函数 名和参数联合起来生成一个中间的函数 名称,而C语言 则不会,因此会造成链接时找不到对应函数 的情况,此时C函数 就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数 名。
比如说cpp中要声明如下:
1 extern "C" char * str1="hello world!" ;
然后汇编文件中也要进行相应的声明如下:
然后才可以在汇编中使用这个变量
注意:内联汇编中哪怕只有一点点错误也会编译不成功,所以最好加一条代码你就编译一下
添加库:
或者:
1 includelib legacy_stdio_definitions.lib
调用printf示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 includelib legacy_stdio_definitions.lib extern printf:far extern getchar:far extern str1:far .code main proc sub rsp,100h mov rcx,str1 call printf call getchar add rsp,100h ret main endp end
显示结果:
断点分析,发现莫名其妙的
出错分析结果:
rcx中存的是str1的地址!rcx里面存值指向的内容才是str1指针!
因此显示如上结果
因此改成以下代码可以正常运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 includelib legacy_stdio_definitions.lib extern printf:far extern getchar:far extern str1:far .code main proc sub rsp,100h mov rcx,str1 mov rcx,[rcx]//加了这一句 call printf call getchar add rsp,100h ret main endp end
改成如下代码代码后(把指针换成字符数组)也可以正确打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 includelib legacy_stdio_definitions.lib extern printf:far extern getchar:far extern str1:far .code main proc sub rsp,100h mov rcx,str1 call printf call getchar add rsp,100h ret main endp end
1 extern "C" char str1[13 ] = "hello world!" ;
效果:
事实上,从cpp传到xxxx.asm汇编文件中的都是传过来是指向所传内容的地址,因此真正取值的时候要再往里取值取一层。
如下案例:
1 2 extern "C" char *str1 = "hello world!%d" ;extern "C" int num = 10 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 includelib legacy_stdio_definitions.lib extern printf:far extern getchar:far extern str1:far extern num:far .code main proc sub rsp,100h mov rcx,str1 mov rcx,[rcx] mov rdx,num mov rdx,[rdx] call printf call getchar add rsp,100h ret main endp end
效果如下:
调call案例:
1 2 3 4 5 6 7 8 #include <stdio.h> extern "C" char *str1 = "hello world!%d" ;extern "C" int num = 10 ;extern "C" void printfcall () { printf ("\nhello world!\n" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 includelib legacy_stdio_definitions.lib extern printf:far extern getchar:far extern str1:far extern num:far extern printfcall:far .code main proc sub rsp,100h mov rcx,str1 mov rcx,[rcx] mov rdx,num mov rdx,[rdx] call printf call printfcall call getchar add rsp,100h ret main endp end
效果如下:
在代码中调用call的案例:
代码如下:
1 2 3 4 5 6 7 8 #include <stdio.h> extern "C" int add2 (int a, int b) ;void main () { printf ("%d" , add2(22 , 33 )); getchar(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 includelib legacy_stdio_definitions.lib .code add2 proc sub rsp,100h mov rax,rcx add rax,rdx add rsp,100h ret add2 endp end
效果如下:
x64位工具 CE,其实只是多了一个8字节搜索的选项
xdbg64,没有dd了,ctrl+g输入地址跳过去
注释位置:
符号按钮就是原od的模块
高亮操作=先点h,再点寄存器。
硬件断点也在断点菜单中
复制代码段的时候一定要把数据都撑开
ctrl+F9直到ret,还要按f7才能返回
x64多出来的某些汇编命令 movups xmmword ptr ss:[rsp+20],xmm0
xmmword代表128位,因此相当于xmm0赋值给rsp+20和rsp+28.
注意点
call中出来继续追[rcx],发现追到[rsp+20],由于rcx来源于一个堆栈地址rsp+20,因此[rcx]的值的来源可能是往后追而非往前追,因为可能是先开开辟地址,然后给地址所在位置赋值因此实际上【rcx】来源于xmm0,即选中那一句
WOW 为新手做的准备,血量特别容易崩
劫持注入 注入方式如下:
十几种游戏注入方法-做辅助必备
(INI)注入
360安全输入法注入
TP后 自动注入
劫持注入
控制台注入
命令注入
搜狗输入法注入
网络注入
线程注入
利用Window可以先加载当前目录下的dll特性,仿造系统的LPK.DLL,让应用程序先加载我们的伪LPK.DLL,然后在我们的dll中去调用原来系统的原函数.
操作步骤: 禁用优化,并且设置成64位
内联汇编要设置一下
添加新建项,64位的汇编文件
asm汇编文件代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 extern g_oldcalladdr:far .code GetFileVersionInfoA proc ;jmp 真实地址(;代表注释) jmp qword ptr [g_oldcalladdr+8*0] GetFileVersionInfoA endp GetFileVersionInfoByHandle proc ;jmp 真实地址(;代表注释) jmp qword ptr [g_oldcalladdr+8*1] GetFileVersionInfoByHandle endp ;等等等等,将要劫持的所有函数写到这里(按照顺序写到这里) end
劫持注入.c代码如下
1 2 3 4 5 6 7 8 9 10 11 extern "C" UINT_PTR g_oldcalladdr[20 ]={0 };void Call_获取原函数地址(){ HMODULE h模块句柄=LoadLibraryA ("oldVersion" ); for (int i=0 ;i<17 ;i++) { g_oldcallladdr[i]=(UINT_PTR)GetProcAddress (h模块句柄,(char *)i); } }
dll初始化位置DLLMAIN中代码如下:
1 2 3 4 5 case DLL_PROCESS_ATTACH:{ Call_获取原函数地址(); LoadLibraryA ("Mydll.dll" ); }
导出表.def要按照顺序写
清理掉自动生成的东西,不然会影响导出函数
p.s. UINT_PTR 相当于QWORD,无类型的64位
工具代替: 是的,纯手写伪造很麻烦,一会儿还得写转发代码~~~。 如果某DLL的导出函数比较多,那真是一个累人的活儿。
在这里我介绍一款比较好用的自动生成工具:
aheadlib
功能:根据输入DLL的导出表生成劫持源码。
把生成的源码载入vc编译器是可以正常编译的。
选项很简单,自己摸索,一般默认即可。
在生成的代码中,你能找到:
AheadLib_ + 原始DLL导出函数名 ()
这样的函数
●DLL劫持的实现● 这一步我们的工作就是通过编程来实现一个LPK.DLL文件,它与系统目录下的LPK.DLL导出表相同,并能加载系统目录下的LPK.DLL,并且能将导出表转发到真实的LPK.DLL。可以看出我们要实现的这个DLL需求如下: 1、构造一个与系统目录下LPK.DLL一样的导出表; 2、加载系统目录下的LPK.DLL; 3、将导出函数转发到系统目录下的LPK.DLL上;
4、在初始化函数中加入我们要执行的代码。
特定引擎 UE5 特定引擎特征
GNAME
CE搜ByteProperty,配合领近内存浏览,可以找到存放字符串的结构 None..ByteProperty.各种xxxProperty ,ByteProperty字符串的地址减掉0x8的地址用CE搜索可以找到一个基地址,该地址-0x10 = GNAME
逆向开发库 minhook库 X86/x64通用
代码语言为C/C++,采用的是MinHook库,作用是hook Windows API函数,拦截函数的执行,在这个过程中可以修改函数的参数,常用于封包拦截工具开发,封包过检测,当然也可以实现任意的关于拦截并修改API执行的功能
开源地址1 开源地址2
DLL劫持注入库 开源地址
X86/x64通用
逆向通用函数 特征码搜索相关 kmp[[算法]]实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 #include <Windows.h> #include <iostream> #include <vector> #include <time.h> #include <TlHelp32.h> using namespace std;#define BLOCKMAXSIZE 409600 BYTE* MemoryData; short Next[260 ];DWORD GetProcessId (char * szProcessName) { HANDLE hProcessSnapShot = NULL ; PROCESSENTRY32 pe32 = { 0 }; hProcessSnapShot = ::CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, NULL ); if (hProcessSnapShot == (HANDLE)-1 ) return NULL ; pe32. dwSize = sizeof (PROCESSENTRY32); if (Process32First (hProcessSnapShot, &pe32)) { do { if (!strcmp (szProcessName, pe32. szExeFile)) return pe32. th32ProcessID; } while (Process32Next (hProcessSnapShot, &pe32)); } else ::CloseHandle (hProcessSnapShot); return NULL ; } WORD GetTzmArray (char * Tzm, WORD* TzmArray) { int len = 0 ; WORD TzmLength = strlen (Tzm) / 3 + 1 ; for (int i = 0 ; i < strlen (Tzm); ) { char num[2 ]; num[0 ] = Tzm[i++]; num[1 ] = Tzm[i++]; i++; if (num[0 ] != '?' && num[1 ] != '?' ) { int sum = 0 ; WORD a[2 ]; for (int i = 0 ; i < 2 ; i++) { if (num[i] >= '0' && num[i] <= '9' ) { a[i] = num[i] - '0' ; } else if (num[i] >= 'a' && num[i] <= 'z' ) { a[i] = num[i] - 87 ; } else if (num[i] >= 'A' && num[i] <= 'Z' ) { a[i] = num[i] - 55 ; } } sum = a[0 ] * 16 + a[1 ]; TzmArray[len++] = sum; } else { TzmArray[len++] = 256 ; } } return TzmLength; } void GetNext (short * next, WORD* Tzm, WORD TzmLength) { for (int i = 0 ; i < 260 ; i++) next[i] = -1 ; for (int i = 0 ; i < TzmLength; i++) next[Tzm[i]] = i; } void SearchMemoryBlock (HANDLE hProcess, WORD* Tzm, WORD TzmLength, unsigned __int64 StartAddress, unsigned long size, vector<unsigned __int64>& ResultArray) { if (!ReadProcessMemory (hProcess, (LPCVOID)StartAddress, MemoryData, size, NULL )) { return ; } for (int i = 0 , j, k; i < size;) { j = i; k = 0 ; for (; k < TzmLength && j < size && (Tzm[k] == MemoryData[j] || Tzm[k] == 256 ); k++, j++); if (k == TzmLength) { ResultArray.push_back (StartAddress + i); } if ((i + TzmLength) >= size) { return ; } int num = Next[MemoryData[i + TzmLength]]; if (num == -1 ) i += (TzmLength - Next[256 ]); else i += (TzmLength - num); } } int SearchMemory (HANDLE hProcess, char * Tzm, unsigned __int64 StartAddress, unsigned __int64 EndAddress, int InitSize, vector<unsigned __int64>& ResultArray) { int i = 0 ; unsigned long BlockSize; MEMORY_BASIC_INFORMATION mbi; WORD TzmLength = strlen (Tzm) / 3 + 1 ; WORD* TzmArray = new WORD[TzmLength]; GetTzmArray (Tzm, TzmArray); GetNext (Next, TzmArray, TzmLength); ResultArray.clear (); ResultArray.reserve (InitSize); while (VirtualQueryEx (hProcess, (LPCVOID)StartAddress, &mbi, sizeof (mbi)) != 0 ) { if (mbi.Protect == PAGE_READWRITE || mbi.Protect == PAGE_EXECUTE_READWRITE) { i = 0 ; BlockSize = mbi.RegionSize; while (BlockSize >= BLOCKMAXSIZE) { SearchMemoryBlock (hProcess, TzmArray, TzmLength, StartAddress + (BLOCKMAXSIZE * i), BLOCKMAXSIZE, ResultArray); BlockSize -= BLOCKMAXSIZE; i++; } SearchMemoryBlock (hProcess, TzmArray, TzmLength, StartAddress + (BLOCKMAXSIZE * i), BlockSize, ResultArray); } StartAddress += mbi.RegionSize; if (EndAddress != 0 && StartAddress > EndAddress) { return ResultArray.size (); } } free (TzmArray); return ResultArray.size (); } int main () { MemoryData = new BYTE[BLOCKMAXSIZE]; DWORD dwProcId = GetProcessId ((char *)"web-client.exe" ); DWORD pid = 0 ; vector<unsigned __int64> ResultArray; if (dwProcId != 0 ) { cout << "进程ID:" << dwProcId << endl; } else { cout << "进程ID获取失败" << endl; } HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, false , pid); int start = clock (); SearchMemory (hProcess, (char *)"68 74 74 70 73 3A 2F 2F 73 70 61 63 65 2E 62 69 6C 69 62 69 6C 69 2E 63 6F 6D 2F 31 39 37 36 39 31 37 37 34 7D 22 00 00" , 0x0000000000000000 , 0x00007fffffffffff , 30 , ResultArray); int end = clock (); cout << "用时:" << end - start << "毫秒" << endl; cout << "搜索到" << ResultArray.size () << "个结果" << endl; long long addr = NULL ; for (vector<unsigned __int64>::iterator it = ResultArray.begin (); it != ResultArray.end (); it++) { printf ("%llx\n" , *it); addr = *it; } printf ("%llx\n" , addr); BYTE Buffer[] = { 0x68 ,0x74 ,0x74 ,0x70 ,0x3A ,0x2F ,0x2F ,0x73 ,0x70 ,0x61 ,0x63 ,0x65 ,0x2E ,0x62 ,0x69 ,0x6C ,0x69 ,0x62 ,0x69 ,0x6C ,0x69 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2F ,0x31 ,0x37 ,0x39 ,0x37 ,0x39 ,0x36 ,0x30 ,0x37 ,0x35 ,0x32 ,0x7D ,0x22 ,0x00 ,0x00 }; WriteProcessMemory (hProcess, (LPVOID)addr, Buffer, sizeof (Buffer), NULL ); getchar (); getchar (); getchar (); getchar (); system ("pause" ); return 0 ; }
浮点类型与二进制位的转换 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <stdio.h> #include <stdint.h> #include <limits.h> void show_ieee754 (float f) { union { float f; uint32_t u; } fu = {.f = f}; int i = sizeof f * CHAR_BIT; printf (" " ); while (i--) printf ("%d " , (fu.u >> i) & 0x1 ); putchar ('\n' ); printf (" |- - - - - - - - - - - - - - - - - - - - - - " "- - - - - - - - - -|\n" ); printf (" |s| exp | mantissa" " |\n\n" ); } int main (void ) { float f = 3.14159f ; printf ("\nIEEE-754 Single-Precision representation of: %f\n\n" , f); show_ieee754 (f); return 0 ; }