文章章节
前言
本章为博主自己学习逆向的笔记总结,水平有限,可能总结有误,希望大家一笑了之
逆向为何要学汇编
首先逆向通常指自身把对方源代码(游戏或其他的exe软件)反编译成汇编代码,之后再根据汇编代码执行自己想要的操作,比如篡改汇编代码来达到自己目的效果,所以要想玩逆向,若连汇编代码都看不懂,你搁在哪改人家汇编代码都不知道那还玩啥逆向呢?
CPU如何工作的
首先CPU仅仅是一个负责运算的处理器,所以它要处理运算数据的话,就必须先拿到数据,因此毫无疑问数据当然是从内存条中取的啦,但CPU是怎么和内存条打交道的呢?这就要说下总线的作用,CPU是通过总线对内存进行读写指令的操作
- 地址总线 CPU通过这跟线发出要找的内存地址
- 数据总线 CPU通过这跟线将来自内存的数据保存到CPU中来
- 控制总线 CPU通过这跟线发出是读还是写的命令
- 地址总线也是有宽度的,比如某计算机地址总线宽度为32,就代表它有32根地址总线,总线有多宽就代表它能寻到到大的内存空间
那么问题来啦,如果8位的计算机要通过地址总线向CPU传输16位的数字,该怎么传呢?比如当前有一个89D8-hex的16进制数(单16进制最大到F,二进制四位就可以表示)
寄存器
寄存器是CPU存储处理数据的地方,8086CPU寄存器大小为16位,可以存储四个16进制数,比如FFFF(单16进制最大到F,二进制四位就可以表示)
- 通用寄存器 AX,BX,CX,DX
- 再细分可划分为AH(8位寄存器,只能存储8位的数字,当存储的数字大于8位时,所有大于8位的数将被舍弃掉比如D88hex,因为D大于8位,所以D将被舍弃,最终在寄存器中存储88hex-AH中的H代表高位),AL(L指low代表低位)
物理地址
内存按人类思维来讲其实是一格一格的,每一格都按顺序给排上编号的,这个编号就是物理地址
接下来抛出一个问题,当CPU为16位, 总线为20位,内存为32位时,CPU如何寻到20位的物理地址?
- 解决方案
CPU位数和总线并不总是一致的,当时是这样处理的,因为CPU是16位的,最多表示2^16-1,但是CPU可以通过传输两次,第一次传输16位的地址,第二次再传输16位的地址,这样在地址加法器里运算(第一次传输地址*16的基础上+第二次传输的地址)就可以得到20位的地址,之后再通过20位总线将地址传输过去 - 段地址(第一次传输的地址)*16+偏移地址(第二次传输的地址)=要寻址的物理地址
偏移地址范围可以从0~2^CPU位数-1之间
段寄存器
共四个 CS DS ES SS
作用: 存储段地址(CPU第一次传输的地址)
-
CS code segment 代码段寄存器,存储要执行的代码指令的段地址
抛出一个问题:CPU如何知道下一个要执行代码指令在内存中的物理地址呢?
- 答案: 首先CPU要拿到指令地址,就必须去CS和IP寄存器中去拿值作运算后才能拿到指令地址; CS寄存器中的值(存储代码指令的段地址)*16+IP寄存器中的值(存储偏移地址的)=要执行的下一条代码指令的物理地址,CPU在找到这条指令并执行完这条指令之后,IP寄存器再不断的累加它的值,那么CS+IP计算出的地址将不断的偏移,那么又会获取到新的指令地址
- 总结: CS+IP就指向代码指令的地址,我们可以通过改变CS/IP寄存器中的值来影响CPU执行代码指令的顺序
再抛出一个问题: 我们如何改变CS/IP的寄存器中的值?
- 答案: 汇编jmp命令 jmp 段地址:偏移地址
比如jmp 2AE3:3: 代表更改CS中的值为2AE3,更改IP中的值为3=>CPU执行代码位置跳到2AE3*16+3=2AE33处的地址(小技巧,16进制数左移一位代表乘16)
jmp a代表仅修改IP的值为a,比如jmp 21,代表修改IP的值为21
-
DS data segment 数据段寄存器
问题引申: CPU如何判断内存中的是数据还是代码段呢?
- 首先计算机只认0和1,也就是说不论这段内存你存的是什么(反正都是0和1).CPU只认寄存器,比如CS寄存器指向某块地址,那么CPU就会认为那片地址存放的是代码段;若是DS寄存器指向某块地址,那么CPU就会认为那片地址存放的是数据段;
-
ES extract segment 额外段寄存器
-
SS stack segment 栈段寄存器
内存
内存中的数据是如何存储的
举例: 存储十进制20000,在内存中如何存储的?
首先十进制2000转16进制是0x4E20(共16位,占两个字节),数据在内存中存储有这么一个特点,低地址存低位,高地址存高位,在这里,高位是4E,低位是20
指令
读取内存的值
如何读取某段内存的值?
待更
常用方法
根据窗口句柄及基地址获取某一地址的值
#include<stdio.h>
#include<Windows.h>
int main() {
// 根据窗口名获取进程句柄
HWND hWnd = FindWindowA(NULL, "Sekiro");
// Attack on Titan 2 / A.O.T. 2
//存放窗口进程id
DWORD dwPid = -1;
//存放读取的值
LPVOID tmp;
DWORD bytes;
//根据句柄获取进程id
GetWindowThreadProcessId(hWnd, &dwPid);
//根据进程id获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
BOOL a = true;
//进程句柄出错?
if (hProcess != NULL) {
printf("进程: %d\n", dwPid);
//进程句柄 基地址 实际读取数存放地址 读取的字节数 缓冲区地址
a = ReadProcessMemory(hProcess, (LPCVOID)0x00049010, &tmp, sizeof(DWORD), &bytes);
//扫雷01005194
//金句地址
if (a != NULL) printf("sucess\n");
else printf("faild\n");
printf("读取到的值: %d", tmp);
}
return 0;
}
若是存在1级或更高级偏移,如下图思路
先找到基址,再通过基址获取基址上的地址,这张图里是1级偏移,获取地址后再加上偏移量就得到真正数据的地址啦
根据窗口句柄及基地址更改某一地址的值
#include<stdio.h>
#include<iostream>
#include<Windows.h>
int main() {
// 根据窗口名获取进程句柄
HWND hWnd = FindWindowA(NULL, "扫雷");
DWORD dwPid = -1;
int writememory = 60;
DWORD dwNumberOfBytesRead;
GetWindowThreadProcessId(hWnd, &dwPid);
//进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//进程句柄 基地址 要写入数据的地址 读取的字节数 缓冲区地址
WriteProcessMemory(hProcess, (LPVOID)0x01005194, &writememory, sizeof(DWORD), NULL);
return 0;
}
通过call基址,用程序调用
#include<stdio.h>
#include<iostream>
#include<Windows.h>
void getcall();
int main() {
// 根据窗口名获取进程句柄
HWND hWnd = FindWindowA("MainWindow","植物大战僵尸中文版");
//存放窗口进程id
DWORD dwPid = -1;
//存放读取的值
int tmp = 0;
//根据句柄获取进程id
GetWindowThreadProcessId(hWnd, &dwPid);
//根据进程id获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
//在进程中分配内存,返回值为起始地址 进程句柄 开始分配的起始地址 分配内存大小 分配完立即执行策略 不晓得意义
PVOID thredfunadd=VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//向分配内存写入代码 进程句柄 分配内存的起始地址 功能函数 分配大小 不晓得意义
DWORD bywrite = 0;
BOOL sucesswrite=WriteProcessMemory(hProcess, thredfunadd, getcall, 4096, &bywrite);
//执行目标地址代码,其他置0就可以 强转
CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)thredfunadd, NULL, NULL, NULL);
return 0;
}
//告诉编译器代码为自己所写,不需要编译器
_declspec(naked) void getcall() {
__asm {
//pushad popad 堆栈平衡,代码安全考虑,加上就对啦,ret返回必须加
pushad
push - 1
push 5
mov eax, 4
push 4
//push时,数值必须加0x,表明是16进制
push 0x142C3600
mov edx, 0x0040D120
//程序里因为不能直接call+地址,所以必须间接通过寄存器的方式
call edx
popad
ret
}
}
注入DLL
代码摘自大佬,在此表示感谢添加链接描述
//注入代码主线程
#undef UNICODE
#include <windows.h>
#include <iostream>
using namespace std;
#define _T(queto) TEXT(queto)
void EnableProcessPrivilege(LPTSTR lpszPrivilege);
void InjectToProcess(DWORD dwProcessID, LPTSTR lpszDllName);
int main() {
DWORD dwProcessID;
cout << "Please input the id of process which you want to inject!" << endl;
cin >> dwProcessID;
//EnableProcessPrivilege(SE_DEBUG_NAME);
//这里填你要注入的dll;
char text[]= "D:\\Visual studio\\myproject\\project3\\DLL\\Debug\\DLL.dll";
InjectToProcess(dwProcessID, text);
}
void EnableProcessPrivilege(LPTSTR lpszPrivilege) {
HANDLE hToken;
TOKEN_PRIVILEGES priv = { 1,{0, 0, SE_PRIVILEGE_ENABLED} };
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
DWORD Size = lstrlen(lpszPrivilege);
LookupPrivilegeName(NULL, &(priv.Privileges[0].Luid), lpszPrivilege, &Size);
AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof(priv), NULL, NULL);
CloseHandle(hToken);
}
void InjectToProcess(DWORD dwProcessID, LPTSTR lpszDllName) {
HANDLE hProcToInject = INVALID_HANDLE_VALUE;
LPVOID szDllName = NULL;
HMODULE hKernel;
LPTHREAD_START_ROUTINE ThreadProc;
HANDLE hRemoteThread;
DWORD dwFileNameLength;
__try {
hProcToInject = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessID);
if (hProcToInject == NULL) {
cout << "OPen process failed!" << endl;
__leave;
}
dwFileNameLength = (lstrlen(lpszDllName) + 1) * sizeof(TCHAR);
szDllName = VirtualAllocEx(hProcToInject, 0, dwFileNameLength, MEM_COMMIT, PAGE_READWRITE);
if (szDllName == NULL) {
cout << "alloc memory failed!" << endl;
__leave;
}
if (!WriteProcessMemory(hProcToInject, szDllName, lpszDllName, dwFileNameLength, NULL)) {
cout << "wirite memory failed!" << endl;
__leave;
}
hKernel = GetModuleHandle(_T("kernel32.dll"));
if (hKernel == INVALID_HANDLE_VALUE) {
cout << "get module handle failed!" << endl;
__leave;
}
ThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernel, "LoadLibraryA");
if (ThreadProc == NULL) {
cout << "Get function failed!" << endl;
__leave;
}
hRemoteThread = CreateRemoteThread(hProcToInject, NULL, 0, ThreadProc, szDllName, 0, NULL);
if (hRemoteThread == NULL)
__leave;
//WaitForSingleObject(hRemoteThread, INFINITE);
}
__finally {
}
}
//注入的简单dll
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
{
switch ( fdwReason )
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA( NULL,"DLL已进入目标进程。", "信息", MB_ICONINFORMATION );
}
break;
case DLL_PROCESS_DETACH:
{
MessageBoxA( NULL, "DLL已从目标进程卸载。", "信息", MB_ICONINFORMATION );
}
break;
}
return TRUE;
}