# IDA 的使用

根据程序的位数选择对应位数的 IDA 打开程序。(附件是 64 位的
一般没有特殊需求,我们直接默认点击 OK 就好了

image-20240929110409063

在 functions windows 里面找到 main

然后在右边的窗口里按 F5 就可以跳出反汇编窗口

buuctf-easyre

image-20240929110715802

https://blog.csdn.net/weixin_45723661/category_11348511.html

# IDA 功能界面介绍

image-20240929112232598

- 蓝色 : .text section
- 深蓝:用户写的函数编译后的代码区
- 浅蓝:编译器自己添加的函数,像启动函数,异常函数等等 ..
- 粉红色 : .idata section
有关输入表的一些数据信息
- 军绿色: .rdata section
纯数据,只读
- 灰色:为了段对齐而留下的空隙
- 黑色:禁区

# 汇编代码界面注释

shift + ;

伪代码注释

/

  • G 跳转
  • N 改名

# imports

程序从动态链接库中导入的要使用的函数

# exports

程序导出给其它程序使用的函数

# 字符串窗口

shift + F12

双击字符串找到位置

在 DATA XREF 里面可以找到谁访问了这个地址

buuctf-reverse_1

image-20240929113449816

在数字上按 R 可以转换成字符

# 这个是带换行的

image-20240930190521829

  • 按 A 转换成字符串
  • D 转换成数据
  • C 转换成汇编代码
  • U 转换成原始字节

# export data

shift + E 可以导出

image-20240930191508047

TODO:

总结 SimpleRev

# 调整基地址

Edit 里面 Segment 里面 Rebase Program, 然后输入基地址就可以和 x64dbg 地址对齐

# 跳转地址

按 G 跳转

# Linux 调试

使用 windows 下面的 ida pro 调试 Linux 下面的程序需要使用 ida 的远程调试功能。
首先将 ida 文件夹中的 dbgsrv/linux_serverx64 文件放入需要调试程序的同级文件夹中。再使用 chmod a+x linux_serverx64 改变该文件的权限。

把要调试的文件也要 + x 运行权限

然后连接

再 application 和 inputfile 里填入要调试的绝对路径 ,host 里面填 ip 地址,密码不用填,然后就可以了

buuctf-level3

下断点进调试看 base64 数组被改变的值

# RSA

buuctf - signin

p
__gmpz_init_set_str(v7, "ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35", 16LL);
  __gmpz_init_set_str(v6, v9, 16LL);
  __gmpz_init_set_str(v4, "103461035900816914121390101299049044413950405173712170434161686539878160984549", 10LL);
  __gmpz_init_set_str(v5, "65537", 10LL);

看到这些整数可能是 RSA E=65537, N = 1034....

__gmpz_init_set_str 这是一个 GNU 高精度算法库

这三个参数分别是多精度整数变量,字符串,进制

__gmpz_powm(v6, v6, v5, v4) 这个的意思其实就是 计算 v6 的 v5 次方并对 v4 取模,结果写入 v6。

10346103590081691412139010129904904441395040517371217043416168653

http://www.factordb.com/index.php 这个可以查质数分解

整数到 bytes

n
print(unhexlify(hex(M)[2:]))
# si = int.from_bytes(s) # byte to int
# rsa.transform.int2bytes(c) # alternative

题解

n
import math
from Crypto.Util.number import inverse
from binascii import unhexlify
e = 65537
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
d = inverse(e, math.lcm(p-1,q-1))
M = pow(c, d, n)
print(rsa.transform.int2bytes(M))	
# 或者
decrypted = rsa.PrivateKey(n,e,d,p,q).blinded_decrypt(c)
print(rsa.transform.int2bytes(decrypted))

从 key 文件里面读取

n
from Crypto.PublicKey import RSA
f = open("pub.key", "rb")
pkdata = f.read()
pk = RSA.import_key(pkdata)
n = pk.n
e = pk.e
print(n)
print(e)

#

用 exeinfope 可以看有没有壳, 或者用 file 指令

# UPX

buuctf - 新年快乐

可以看出来是 UPX 的壳

image-20240929125830292

file 指令会有

image-20240929130135608

解壳

l
upx -d xx.exe

# 安卓 / JAVA

jdk 安装 https://www.oracle.com/java/technologies/downloads/#jdk22-windows

# jadx

安装 https://github.com/skylot/jadx/releases/tag/v1.5.0

可以打开文件,把 APK 传进去,在左边可以看到源代码

打开 windows gui 的那个

# IOS 逆向

https://www.i4.cn/

装爱思助手

越狱,要制作启动盘

https://www.bilibili.com/video/BV1or421G7vo/?spm_id_from=333.788.videopod.sections&vd_source=2c1227d0a7d29725db75d3fc52e6089b

# Frida

l
pip install frida-tools

# Ghigra

# x64dbg

gitbub 上有

# Unity

BUUCTF [BJDCTF2020]BJD hamburger competition

第一次接触 Unity 的逆向题
这种关键代码一般都在 assembly-csharp.dll 里面,加载到 dnspy 里面,左边点开可以看到源代码

# Python

l
pip install uncompyle6

用这个命令

用 online 的比较好 https://www.lddgo.net/en/string/pyc-compile-decompile

# Python ByteCode

https://www.codeconvert.ai/assembly-to-python-converter

这个网站可以转换 ByteCode

# GO

https://github.com/sibears/IDAGolangHelper

可以装这个插件

# 代码分析

buuctf lucky guy

p
((((unsigned int)((unsigned __int64)j >> 32) >> 31) + (_BYTE)j) & 1
  - ((unsigned int)((unsigned __int64)j >> 32) >> 31) == 1

等价于

j % 2 == 1

# 复用代码解密

buuctf 刮开有奖

这里面的函数 IDA 会判断错误,需要改正,类型改了代码也要记得改

# 答案在其他函数里

buuctf [2019 红帽杯] easyRE

往 main 的下面找找

# 花指令

有时候 IDA 反编译不了,这个时候要改指令,通常是 jnz 指令

buuctf Maze

img

如何 nop:

  1. Edit/Patch program/Change byte 将要修改的 byte 改成 90,修改完后 Edit/Patch program/Apply patches to input file 保存修改后的文件,
  2. 或者 Edit/Patch program/Assemble 直接输入 nop ,然后同样要保存

在这里插入图片描述

# 把报错的函数编译一下

[MRCTF2020]Xor

把报错的函数点进去反编译一下,然后再回来编译就可以了

# 数据类型判断

p
__int64 v6[6];
for ( i = 0; i <= 5; ++i )
  {
    printf("%s", "input: ");
    a2 = (char **)((char *)v6 + 4 * i);
    __isoc99_scanf("%d", a2);
  }

这个 v6 应该是 4byte, 因为 (char *)v6 + 4 * i) 是 4 字节

# 加密算法

# TEA 加密

p
v3 = *a1;
v4 = a1[1];
v5 = 0;
for ( i = 0; i <= 0x3F; ++i )
{
    v5 += 1166789954;
    v3 += (v4 + v5 + 11) ^ ((v4 << 6) + *a2) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
    v4 += (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
}
*a1 = v3;
result = v4;
a1[1] = v4;
return result;

解密

p
unsigned int a[6] = {0xDF48EF7E,550153460, 3774025685, 1548802262, 2652626477,0x84F30420};
unsigned int table[4] = {2,2,3,4};
unsigned int decoded[6];
for (int j = 0;j < 6; j += 2) {
    int v5 = 0x458BCD42 * 64;
    unsigned int v3 = a[j], v4 = a[j+1];
    for (int i = 0;i <= 63; ++i) {
        v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + table[2]) ^ ((v3 >> 9) + table[3]) ^ 0x10;
        v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + table[0]) ^ ((v4 >> 9) + table[1]) ^ 0x20;
        v5 -= 0x458BCD42;
    }
    decoded[j] = v3;
    decoded[j+1] = v4;
}

要把 v3,v4 的类型写对,否则计算结果不对

# 解方程 z3

l
pip install z3-solver

buuctf [ACTF 新生赛 2020] Universe_final_answer

# MD5

n
from Crypto.Hash.MD5 import MD5Hash
from binascii import hexlify
token = "1001"
print(hexlify(MD5Hash(token.encode()).digest())[:20].upper())

# init 函数修改

https://www.cnblogs.com/NozoMizo/articles/17737745.html

# Windows API

# FindResourceA

buuctf CrackRTF

c
hResInfo = FindResourceA(0, (LPCSTR)0x65, "AAA");
if (!hResInfo)
    return 0;

FindResourceA 函数用于查找一个指定的资源。

  • 第一个参数是模块句柄,这里为 0 ,表示当前模块。
  • 第二个参数 (LPCSTR)0x65 是资源的标识符(ID),这里使用的是资源 ID 0x65
  • 第三个参数 "AAA" 是资源的类型名称。

如果 FindResourceA 返回 NULL ,则表示资源没有找到,函数立即返回 0

用 Resource Hacker 可以提取 exe 的资源

SizeofResource 获取指定资源的大小,结果赋值给 nNumberOfBytesToWrite ,表示将要写入的字节数

# LoadResource

LoadResource 函数将资源加载到内存中,返回资源的句柄 hResData

如果加载失败,返回 NULL ,函数返回 0

# LockResource

c
lpBuffer = LockResource(hResData);

LockResource 锁定资源并返回一个指向资源数据的指针 lpBuffer 。通过这个指针可以访问资源内容

# CreateFile

c
hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0);
if (hFile == (HANDLE)-1)
    return 0;

CreateFileA 用于创建或打开文件,这里试图创建一个名为 "dbapp.rtf" 的文件。

  • 0x10000000uGENERIC_WRITE ,表示文件以写入模式打开。
  • 00 :表示不使用文件共享和默认安全属性。
  • 2uCREATE_ALWAYS ,表示总是创建文件,即使文件已经存在也会覆盖。
  • 0x80uFILE_ATTRIBUTE_NORMAL ,表示文件的普通属性。

如果文件创建失败,返回 (HANDLE)-1 ,函数返回 0

# WriteFile

p
if (!WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0))
    return 0;

WriteFilelpBuffer 中的数据写入到文件 hFile ,写入的字节数为 nNumberOfBytesToWrite

&NumberOfBytesWritten 用于接收实际写入的字节数。

如果 WriteFile 失败,函数返回 0

# 多线程

buuctf Youngter-drive

counter = 29

最终加密 TOiZiZtOrYaToUwPnToBsOaOapsyS

线程 1:若 counter>-1 每次调用对 counter 位置加密,并且每次 - 1

线程 2:若 counter>-1 每次 -1

所以加密基数或者偶数部分

CreateMutexW: 作用是找出当前系统是否已经存在指定进程的实例.

CreateThread:CreateThread 是一种微软在 Windows API 中提供了建立新的线程的函数,该函数主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过 CloseHandle 函数来关闭该线程对象。

CloseHandle: 关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等。在 CreateThread 成功之后会返回一个 hThread 的 handle,且内核对象的计数加 1,CloseHandle 之后,引用计数减 1,当变为 0 时,系统删除内核对象。

WaitForSingleObject:WaitForSingleObject 函数用来检测 hHandle 事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的 dwMilliseconds 毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果时间已经到达 dwMilliseconds 毫秒,但 hHandle 所指向的对象还没有变成有信号状态,函数照样返回。

在这里就相当于,第一个线程调用该函数,然后这里的 WaitForSingleObject 函数用来检测之前线程的信号状态,暂时挂起

多线程例子:

p
#include <iostream>
#include <windows.h>
HANDLE hMutex;
int arr[100];
int last = 0;
DWORD WINAPI thread01(LPVOID lvParameter) {
	for (int i = 0; i < 100; ++i) {
		WaitForSingleObject(hMutex, INFINITE);
		if (last == 100) break;
		arr[last] = 1;
		last++;
		std::cout << "thread 1 work on " << last - 1 << std::endl;
		ReleaseMutex(hMutex);
	}
	return 0;
}
DWORD WINAPI thread02(LPVOID lvParameter) {
	for (int i = 0; i < 100; ++i) {
		WaitForSingleObject(hMutex, INFINITE);
		if (last == 100) break;
		arr[last] = 1;
		last++;
		std::cout << "thread 2 work on " << last - 1 << std::endl;
		ReleaseMutex(hMutex);
	}
	return 0;
}
int main()
{
	hMutex = CreateMutexA(NULL, FALSE, (LPCSTR)"Test");
	if (hMutex == NULL) {
		std::cout << "ERROR" << std::endl;
		return 1;
	}
	HANDLE hTread1 = CreateThread(NULL, 0, thread01, NULL, 0, NULL);
	if (hTread1 == NULL) {
		std::cout << "ERROR" << std::endl;
		return 1;
	}
	
	HANDLE hTread2 = CreateThread(NULL, 0, thread02, NULL, 0, NULL);
	if (hTread2 == NULL) {
		std::cout << "ERROR" << std::endl;
		return 1;
	}
	WaitForSingleObject(hTread1, INFINITE);
	WaitForSingleObject(hTread2, INFINITE);
	
	CloseHandle(hTread1);
	CloseHandle(hTread2);
	
	CloseHandle(hMutex);
	return 0;
}

这题里面

# 游戏逆向

# 任务管理器找进程

打开任务管理器

<img src="逆向工程 /image-20240811072446821.png" alt="image-20240811072446821" style="zoom:50%;" />

然后右键,转到详细信息就有了

# CE

资源网址:https://wpfx.org/

[CE](D:\Software\Cheat_Engine_7.5\Cheat Engine 7.5)

ollydbg

搜字符串的时候把代码页勾上,可以搜出更多

# 找血量

可以换装备,被怪打

# 搜 byte

可以勾选快速扫描,4 字节对齐,这样可以加快速度

# 天龙八部

TODO: p37

记得启动器里面选 32 位

axery123@changyou.com

123axery

岳朋 男 211481198401154411

# 人物状态

rcx + 234

1A34D9946D0 + 234

rcx = rax+80

rax=1A30B1C01D0

Game.exe+593935

# 汇编指令

# lea

lea eax, dword ptr [ebp-8]

把右边的地址复制给左边

# pushad, popad

把 8 个寄存器全部 push 到栈上面

# rep movs

循环 ecx 次,每次把 esi 地址的内容复制给 edi 地址的内容,然后 esi,edi 增加 4

#

# ebp

栈顶

  • ebpebp 是保持上次的 ebp 值 push ebp,mov ebp,esppush \ ebp, mov \ ebp, esp

  • [ebp+4][ebp+4] 是回去的地址

  • [ebp+8][ebp+8] 是传入的第一个参数

# OllyDBG

ollydbg

汉化包

https://github.com/lennylxx/ollydbg-zh/releases

复制到根目录之后,可以在 file 里面选择 gui language 为中文

# 快捷键

  • 回车:进入 call
  • F9: 运行
  • F8: 步过 把 call 运行完
  • F7: 步入: 运行到 call 的开头
  • - 后退
  • + 前进
  • F2:断点
  • F4:运行到当前指向的命令
  • Ctrl+F9: 执行到返回
  • Ctrl+G: 查看地址,APT 代码

# x64dbg

  • 再选项里吗可以设置关闭断点

  • 带参数调试 : 文件,改变命令行

  • 高亮寄存器:快捷键 H,再选项中打开

# 命令

  • API 断点: bp MessageBoxA
  • dd 输出地址的值 dd eax

# 查找字符串

image-20240929115846957

# 附加进程

文件,附加 j 进程,选择想要的进程

点击三角形让程序运行,

如果卡住了可以按 TT , 然后右键 Resume all threads

# OD 界面

EE 是模块,选择游戏模块

CC 是初始界面

查找注释:

先在 E 窗口找到主程序,然后右键查找用户定义的注释

# 跳转

Ctrl+G 打开调整,输入内存地址或者 ws2_32.send

x64dbg 是 game + 2ECE21 可以加入基地址

可以跳到这个模块的 send 函数

# OD 断点

右键可以下断点

# 内存断点

内存访问的时候断

# 硬件断点

只能 4 个

可以在内存地址右键,硬件断点,硬件写入,Byte

来查找谁写入了这个位置

# F2 断点

可以下任意多个

# api 断点

bp ws2_32.send

# 条件断点

shift + F2

[76ad5000]==06aa72a0

# 发包

# 发包函数

  • send
  • sendto
  • WSASend
  • WSPSend: 在 ws2_32.WEP 下面有个 call esi 这个是

一般是 ws2_32.send 在这个模块里,如果不是的话,需要指定模块名字

# 易语言

https://www.dywt.com.cn/ 产品下载 5.93

下载易语言安装补丁 ( CTF/逆向 )

e.exe 替换过去,linker 里面的内容也拖过来替换

然后打开替换来的 link.e 用易语言打开,然后运行一下就好了

# 使用

  • ctrl + L: 添加变量

# 编译

编译,静态编译,

静态的会打包所有需要的 dll

# 常用设置

程序,程序配置对话框:可以配置图标,描述

工具

  • 支持库配置里全勾上

  • 系统设置,

    • 通常:可以取消编译时检查死循环
    • 存根,可以设置请求管理员权限

# 自定义数据类型

就是 c 的 struct

# 基本函数

到文本:转换成字符串

# if

如果 (时间 % 2)

没有 else 的

如果真 ()

判断 是 switch

# for

j 从 1 到 100

计次循环首(100, j)
s = s+j
计次循环尾()

# while


a = 1
判断循环首 (a < 10)
    a = a + 1
判断循环尾 ()
标签1.标题 = 到文本 (a)

# Dll 命令

比如 Messagebox

# 组件

# 时钟

时钟周期

# Visual Studio

https://visualstudio.microsoft.com/de/ 下载就行了

然后只勾选 c 和 windows, 扩展开发,在 C 桌面开发里面额外勾选 MFC

文件,账户选项,搜索字体,可以调整字体大小

# 反汇编

打个断点运行后,右键选择 转到反汇编

右键可以选择是否显示变量名

# 内存

在调试,窗口,内存里面找

内存里面可以 &a 来找到变量 aa 的地址内容

# MFC DLL

选择静态链接

image-20240811135822371

视图,其他窗口,资源视图,把他选出来

然后右键添加资源,选择 Dialog

image-20240811140259142

确定和取消按钮可以自己删掉

# 32 位程序

可以选择在 debug 旁边 选择 x86 来写 win32 程序

# 项目,TL1 属性

可以选择 windows sdk 版本

使用的字符集

# 工具箱

视图,工具箱,里面有组件

视图,属性窗口也要选

# 创建类

右键 dialog 的空白处,添加类作为创建的这个 dialog 的类名,可以取名 XXDialog

在 DLL 的主 cpp 文件中

p
TLDialog* dialog;
DWORD WINAPI ShowDialog(LPARAM lpData) {
	dialog = new TLDialog; // 分配空间
	dialog->DoModal(); // 阻塞的方式 模态窗口
	delete dialog; // 释放空间
	FreeLibraryAndExitThread(theApp.m_hInstance, 1); // 释放 DLL 退出线程
	return TRUE;
}
// CMFCLibrary1App 初始化
BOOL CMFCLibrary1App::InitInstance()
{
	CWinApp::InitInstance();
	::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ShowDialog, NULL, NULL, NULL); // 创建线程
	return TRUE;
}

image-20240811144327614

右键重新生成来编译

在 CE 中查找好基地址和便宜

练习:查找新天龙八部怀旧的人物状态

p
try
{
    DWORD base_addr = (DWORD)GetModuleHandleA("Game.exe") + 0x5946DC;
    DWORD offset1 = *(DWORD*)base_addr + 0x0c;
    DWORD offset2 = *(DWORD*)offset1 + 0x14c;
    DWORD addr = *(DWORD*)offset2;
    outputdebug("人物信息:人物状态: %d", addr);
}
catch (const std::exception&)
{
    outputdebug("Error!");
    return;
}

然后是输出在 debug 信息的代码

p
void outputdebug(char* pszFormat, ...) {
#ifdef DEBUG
	char szbufFormat[0x1000];
	char szbufFormatGame[0x1100] = "";
	va_list argList;
	va_start(argList, pszFormat);
	vsprintf_s(szbufFormat, pszFormat, argList);
	strcat_s(szbufFormatGame, szbufFormat);
	OutputDebugStringA(szbufFormatGame);
	va_end(argList);
#endif
}

这个 dubug 信息可以在 DebugView 来看

用管理员权限打开 DebugView, 然后打开监视核心,可以选择过滤字符串。

# 汇编代码注入

call 函数的时候要记得先放在寄存器里

Detach 之后再注入

pushad
mov ecx, 0x3F080A28
mov eax, 0x6a5e60
call eax
popad

ecx:

[[[Game.exe+5946FC]+ 60]+ 154]

# 天龙八部怀旧

# 背包数组

cnt:

[[i*4+[[Game.exe+594724]+181c4]]+0x14]+0x58

base

[[Game.exe+594724]+181c4]

名字

[[base+i*4]+2c]+18]

  • +2c,+1c 说明
  • +2c,+20 使用等级
  • +2c,+40 耐久

# NPC 遍历

c45b801d423975f7a189ebf3b9e8db1

这是一个链表

p
esi = [[[edi+1c]+4]+4];
while (esi != [edi+1c]) {
    call([[[esi+8]]+14])
    esi = [esi+4];
}

# 血量遍历

先用 CE 搜索浮点数找到血量百分比

[[[[[[[[[0x117CCB8]+0x54]+0x4]]]+0x14]+0x148]+0x10]+0x8]=target