逆向学习速览-汇编基础篇

前言

本章为博主自己学习逆向的笔记总结,水平有限,可能总结有误,希望大家一笑了之

逆向为何要学汇编

首先逆向通常指自身把对方源代码(游戏或其他的exe软件)反编译成汇编代码,之后再根据汇编代码执行自己想要的操作,比如篡改汇编代码来达到自己目的效果,所以要想玩逆向,若连汇编代码都看不懂,你搁在哪改人家汇编代码都不知道那还玩啥逆向呢?

CPU如何工作的

首先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;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值