推荐肉丝r0ysue课程(包含安卓逆向与js逆向):
做了笔记从来不看系列……丢云端清本地了
逻辑运算
cpu中如何计算2+3
X:0010
Y:0011
xor:0001
保存至R:0001
and:0010
左移:<<1 -> S:0100
判断是否等于0
若等于0 ,则结果为R
若不等于0
X = R = 0001
Y = S = 0100
xor:0101
保存至R:0101
and -> R:0
<<1 -> R:0
判断是否等于0
等于0,则结果为R
最终结果 R:0101
32位通用寄存器 | 主要用途 |
---|---|
EAX | 累加器 |
ECX | 计数 |
EDX | I/O指针 |
EBX | DS段的数据指针 |
ESP | 堆栈指针 |
EBP | SS段的数据指针 |
ESI | 字符串操作的源指针 SS段的数据指针 |
EDI | 字符串操作的目的指针 ES段的数据指针 |
PUSH指令:
PUSH r32
PUSH r16
PUSH m16
PUSH m32
PUSH imn8/ imm16/ imm32
POP指令
进位标志CF (Carry Flag) :如果运算结果的最高位(如8位的第九位)产生了一个进位或借位,那么,其值为1,否则其值为0。(无符号)
奇偶标志PF (Parity F1ag):奇偶标志PF用于反映运算结果二进制中“1”的个数的奇偶性。如果“1”的个数为偶数,则PP的值为1,否则其值为0。
只看最后一个字节
辅助进位标志AF (Auxiliary Carry Flag): .
在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0;
(1)、在字操作时,发生低字节向高字节进位或借位时;
(2)、在字节操作时,发生低4位向高4位进位或借位时。
零标志ZF (Zero Flag): 零标志ZF用来反映运算结果是否为0。
如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。
符号标志SF (Sign F1ag): 符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。
溢出标志0F (Overflow Flag):溢出标志0F用于反映有符号数加减运算所得结果是否溢出。
如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1, 否则,OF的值被清为0。
OF = 符号位进位 xor 最高有效数值位向符号位产生的进位
溢出主要是给有符号运算使用的,在有符号的运算中,有如下的规律:
正+正=正如果结果是负数,则说明有溢出
负+负=负如果结果是正数,则说明有溢出
正+负永远都不会有溢出.
ADC指令:带进位加法
格式: ADC R/M, R/M/ IMM
操作对象1=操作对象1 + 操作对象2 + CF
两边不能同时为内存,宽度要一样
SBB指令:带借位减法
格式: SBB R/M, R/M/IMM
操作对象1=操作对象1 - 操作对象2 - CF
两边不能同时为内存,宽度要一样
XCHG指令:交换数据
格式: XCHG R/M, R/M/IMM
两边不能同时为内存宽度要一样
movsx 带符号位拓展,高位拓展为符号位
movzx 不带符号位拓展,高位拓展为0
MOVS指令:移动数据 内存—内存
BYTE/WORD/ DWORD
MOVS BYTE PTR ES:[EDI],BYTE PTR DS: [ESI] 简写为: MOVSB
MOVS WORD PTR ES:[EDI],BYTE PTR DS: [ESI]
简写为: MOVSW
OVS DWORD PTR ES:[EDI], BYTE PTR DS:[ESI]
简写为: MOVSD
DF标志位看方向,=1 减;等于0,加
STOS指令:将A1/AX/EAX的值存储到[EDI]指定的内存单元
REP指令:按计数寄存器
(ECX) 中指定的次数重复执行字符串指令
指令 | 含义 | 标志位 |
---|---|---|
JE, JZ | 结果为零则跳转(相等时跳转) | ZF=1 |
JNE,JNZ | 结果不为零则跳转(不相等时跳转) | ZF=0 |
JS | 结果为负则跳转 | SF=1 |
JNS | 结果为非负则跳转 | SF=0 |
JP,JPE | 结果中1的个数为偶数则跳转 | PF=1 |
JNP,JPO | 结果中1的个数为偶数则跳转 | PF=0 |
JO | 结果溢出了则跳转 | OF=1 |
JNO | 结果没有溢出则跳转 | 0F=0 |
JB,JNAE | 小于则跳转(无符号数) | CF=1 |
JNB,JAE | 大于等于则跳转(无符号数) | CF=0 |
JBE,JNA | 小于等于则跳转(无符号数) | CF=1 or ZF=1 |
JNBE,JA | 大于则跳转(无符号数) | CF=0 and ZF=0 |
JL,JNGE | 小于则跳转(有符号数) | SF≠OF |
JNL,JGE | 大于等于则跳转(有符号数) | SF=OF |
JLE,JNG | 小于等于则跳转(有符号数) | ZF=1 or SF≠OF |
JNLE,JG | 大于则跳转(有符号数) | ZF=0 and SF=OF |
JAE---->jump when above or equal
JNB---->jump when not below
JB---->jump when below
JNAE---->jump when not above or equal
JBE---->jump when below or equal
JNA---->jump when not above
JG---->jump when greater
JNLE---->jump when not less or equal
有符号拓展时用符号拓展,无符号数拓展时用0拓展
MOVSX 先符号拓展,再传送(赋值)(即正数0拓展,负数1拓展)
MOVZX 先0拓展,再传送
空函数汇编代码
push ebp # 压入ebp,即旧的栈底指针
mov ebp,esp # 开始一个新的栈帧
sub esp,40h # 为当前函数申请一段栈空间
push ebx
push esi
push edi # 保存旧的寄存器值
lea edi,[ebp-40h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi] # 将申请的栈空间用CC填充(即int 3)
##############
##函数核心功能##
##############
pop edi
pop esi
pop ebx # 恢复寄存器值
mov esp,ebp # 取消新的栈帧
pop ebp # 弹出旧栈帧的栈底指针
ret # 返回
裸函数
void __declspec(naked) Plus() //中间 决定是否为裸函数
c语言中写汇编语言
__asm
{
//汇编指令
}
main 或WinMain 是“语法规定的用户入口”,而不是“应用程序入口”。应用程序入口通常是启动函数。
main 函数被调用前要先调用的函数如下:
GetVersion()
:获取操作系统版本_heap_init()
GetCommandLineA()
_crtGetEnvironmentStringsA()
_setargv()
_setenvp()
_cinit()
这些函数调用结束后就会调用main函数,根据main 函数调用的特征,将3 个参数压入栈内作为函数的参数。main()函数有3个参数
float,double遵循IEEE
float存储方式
31 | 30 | 22 0 |
---|---|---|
1位 | 8位 | 23位 |
符号位 | 指数部分|阶码 | 尾数 |
将float转换位内存存储格式
先转换为二进制形式
将小数点左移或右移n位(科学计数法)左移为+,右移为-,得到阶码真实值
实际阶码存储时候,会将阶码真实值+127,进行存储
存储阶码 = 真实阶码 + 127
符号位 为小数的符号位,小数为正则为0,小数为负则为1
尾数 从 小数点 后取23位存入尾数,右端用0补足
例如:8.25
整数部分:1000 ↑
小数部分:01 ↓
1000.01
小数点左移3位,变为1.00001则真实阶码为+3,存储阶码为+3+127,转换为二进制:1000 0010
符号位,整数,符号位为0
尾数01,右端用0补足,则为00001000000000000000000
最终为0100 0001 0000 0100 0000 0000 0000 0000
转换为16进制为41040000
double同float 64位
63 | 62 | 51 0 |
---|---|---|
1位 | 11位 | 52位 |
符号位 | 指数部分|阶码 | 尾数 |
#pragma pack的基本用法为:
#pragma pack( n ) //结构体按n为字节对齐
**自身对齐值:**数据类型本身的对齐值,例如char类型的自身对齐值是1,short类型是2;
**指定对齐值:**编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
**有效对齐值:**自身对齐值和指定对齐值中较小的那个对齐参数: n为字节对齐数,其取值为1、2、4、8,默认是8。
指针的指针:二级指针、三级指针、n级指针
指针数组
int * arr[10]; //一个长度为10的数组,其中每一个元素都为一个int*类型的指针
数组指针
int (* arrPtr)[10] = NULL; // 一个指针,它指向一个有10个int元素的数组
//若有
int arr[]={...};
*arrPtr = arr;
//有以下式子成立
arrPtr = arr;
*arrPtr = arr;
*(arrPtr+0) = arr;
arrPtr[0] = arr;
结构体指针:指向结构体的指针
struct stu{
int id;
int level;
}player;
stu * Ptr;
Ptr->id = 0;
Ptr->level = 20;
指针函数:返回值为指针的函数
int arr[] = {...};
int * func()
{
int * p = arr;
return p;
}
函数指针:
double (*funcPtr)(double, double);//定义了一个指向函数的指针,该函数具有两个 double 类型的参数和 double 类型的返回值
double result;
funcPtr = pow; // 使得funcPtr指向函数pow()
// 因此,表达式*funcPtr获得函数pow()
result = (*funcPtr)( 1.5, 2.0 ); // 通过funcPtr调用函数
result = funcPtr( 1.5, 2.0 ); // 与上等效的函数调用
double Add( double x, double y ) { return x + y; }
double Sub( double x, double y ) { return x - y; }
double Mul( double x, double y ) { return x * y; }
double Div( double x, double y ) { return x / y; }
// 具有5个函数指针的数组,这些函数需要两个double类型参数,返回值为double类型
double (*funcTable[5])(double, double)
= { Add, Sub, Mul, Div, pow }; // 初始化器列表
//自己的解释:funcTable是一个长度为5指针数组,其中数组内的每一个元素都是一个函数指针
SAL:算术左移
低位补零,高位存进CF标志位
例:CF=0 1000 0001 -> 0000 0010 CF = 1
SAR:算数右移
低位存进CF标志位,高位不变并且右移
例:CF=0 1000 0001 -> 1100 0000 CF = 1
SHL:逻辑左移,同算术左移
SHR:逻辑右移
高位补零,低位存进CF标志位
例:CF=0 1000 0001 -> 0100 0000 CF = 1
ROL:循环左移,循环移动,每次移出的位赋值给CF并补到另一边
例:CF=0 1000 0000 -> 0000 0001 CF = 1
ROR:循环右移,循环移动,每次移出的位赋值给CF并补到另一边
例:CF=0 0000 0001 -> 1000 0000 CF = 1
RCL:带进位循环左移,将CF标志位也放在循环里一起移动,多出来的移到CF,少的用CF位补
例:CF=1 0000 1000 -> 0001 0001 CF = 0
RCR:带仅为循环右移,将CF标志位也放在循环里一起移动
例:CF=0 0001 0001 -> 0000 1000 CF = 1
C语言中,左移<< 为逻辑左移
右移>> 有符号:SAR 无符号:SHR
用Debug的R命令查看、改变CPU寄存器的内容;
用Debug的D命令查看内存中的内容;
用Debug的E命令改写内存中的内容;
用Debug的U命令将内存中的机器指令翻译成汇编指令;
用Debug的T命令执行一条机器指令;
用Debug的A命令以汇编指令的格式在内存中写入一条机器指令。
cs 指令的段寄存器 IP:偏移
ds 数据的段寄存器 [xxx]:偏移
ss 栈的段寄存器 sp:偏移 ss:sp指向栈顶
bx 存放的数据作为偏移,段地址在ds中
inc bx bx = bx + 1
dec bx bx = bx - 1
cx 存放循环次数
loop 标号
(cx) = (cx) - 1 判断cx的值 //(cx)表示cx里面的值
先讲(cx)-1 若(cx)不为0,则转向s
mov cx,11
s: add ax,ax
loop s
mov cx,循环次数
s:
循环执行的程序段
loop s
用循环累加来实现乘法,使用dx寄存器进行累加
mov ax,2000h
mov dx,ax
mov al,ds:[0] //把2000:0地址里的值给al
相当于进行下面几步操作
((es)*16+(di)) = ((ds)*16+(si))
//mov es:[di],byte ptr ds:[si]
若df = 0 则:
(si)++
(di)++
若df = 1 则:
(si)--
(di)--
movsb传送一个字节
也可传送一个字,使用movsw
常与rep配合使用
rep movsb
用汇编语言来描述
s:movsb
loop s
可实现(cx)个字符的传送
对df进行设置
cld指令:将df位置0
std指令:将df位置1
例:用串传送指令,将data段中的第一个字符串复制到它后面的空间中
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向data:0
mov es,ax
mov di,16 ;es:di指向data:0010
mov cx,16 ;rep循环16次
cld ;设置df=0,正向传送
rep movsb
code ends
汇编指令 | 功能 |
---|---|
jmp 段地址:偏移地址 | 用指令给出的段地址修改CS,偏移地址修改IP |
jmp 某一合法寄存器 | 用寄存器中的值修改IP |
Loop 标号 | cx–;判断cx的值,若不为0则跳转到标号处执行程序,若为0则向下执行 |
and | 逻辑与指令,按位进行与运算 |
or | 逻辑或指令,按位进行或运算 |
div | 除法指令 |
adc 对象1,对象2 | 功能:对象1 = 对象1 + 对象2 + ZF |
sbb 对象1,对象2 | 功能:对象1 - 对象2 - CF |
cmp 对象1,对象2 | 计算 对象1-对象2 但是不保存结果,仅影响标志位 |
shl指令 | 逻辑左移指令 |
shr指令 | 逻辑右移指令 |
div指令:
mul指令
两个相乘的数:要么都是8位,要么都是16位
shl逻辑左移指令
shr逻辑右移指令
转移指令 | 条件及含义 |
---|---|
jcxz指令 | 如果(cx)==0,跳转 |
loop指令 | cx=cx-1;如果cx不等于0,跳转 |
ret指令 | 相当于pop ip,更改ip的内容 |
retf指令 | 相当于pop ip,pop cs;更改ip和cs的内容 |
call 标号 | 相当于push ip;jmp near ptr 标号 |
call far ptr 标号 | 相当于push cs,push ip;jmp far ptr 标号 |
条件跳转指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则跳转 | zf==1 |
jne | 不等于则跳转 | zf==0 |
jb | 低于则跳转 | cf==1 |
jnb | 不低于则跳转 | cf==0 |
ja | 高于则跳转 | cf==0且zf==0 |
jna | 不高于则跳转 | cf==1或zf==1 |
j:jump
e:equal相等
b:below低
a:above高
标志寄存器 | 含义 |
---|---|
ZF标志位 | 判断指令执行结果是否为0 若结果为0则ZF=1;结果不为0则ZF=0 |
PF标志位 | 判断指令执行结果 的二进制位 的1的个数 若1的个数为偶数,则PF=1 |
SF标志位 | 判断指令执行结果 是否为负 如果为负,则SF=1;若为非负,则SF=0 |
CF标志位 | 进行无符号数运算时,结果 如果进位、或者借位,则CF=1 |
OF标志位 | 进行有符号数运算时,结果 若有溢出,则OF=1;若无溢出,则OF=0 |
DF标志位 | DF执行串处理指令时,控制每次操作后si,di的增减;df0 每次操作后si,di递增;df1 每次操作后si,di递减 |
指令 | 快捷键 | 含义 |
---|---|---|
Restart | Ctrl+F2 | 重新开始调试(终止正在调试的进程) |
Step Into | F7 | 执行一句OP code(操作码),若遇到调用命令(CALL),将进入函数代码内部 |
Step Over | F8 | 执行一句OP code(操作码),若遇到调用命令(CALL),仅执行函数本身,不跟随进入 |
Execute till Return | Ctrl+F9 | 一直在函数代码内部运行,直到遇到RETN命令,跳出函数 |
Go To | Ctrl+G | 移动到指定地址,用来查看代码或内存,运行时不可用 |
Execute till Cursor | F4 | 执行到光标位置,即直接转到要调试的地址 |
Comment | ; | 添加注释 |
User-defined comment | 鼠标右键菜单 → Search for → User-defined comment | |
Label | : | 添加标签 |
User-defined label | 鼠标右键菜单 → Search for → User-defined label | |
Set/Reset BreakPoint | F2 | 设置或取消断点(BP) |
Run | F9 | 运行(若设置了断点,则执行至断点处) |
Show the current EIP | * | 显示当前EIP (命令指针)位置 |
Show the previous Cursor | - | 显示上一个光标的位置 |
Preview CALL/JMP address | Enter | 若光标处有CALL/JMP等指令,则跟踪并显示相关地址(运行时不可用,简单查看函数内容时非常有用) |
All referenced text strings | 鼠标右键菜单Search for → All referenced text strings | 查看代码中引用的字符串 |
All intermodular calls | 鼠标右键 → Search for(查找) → All intermodular calls(所有模块间的调用) | 查看代码中调用的所有API函数 |
Name in all modules | 鼠标右键 → Search for(查找) → Name in all modules(所有模块中的名称) | 查看所有API函数 |
Edit data | Ctrl+E | 编辑数据 |
Assemble | Space | 编写汇编代码 |
Copy to executable file | 鼠标右键菜单Copy to executable file | 创建文本副本(修改的项目被保留) |
执行Goto(Ctrl+G)命令,输入[地址],点击【OK】,光标自动定位到[地址]处,F4执行到光标位置
F2在当前光标位置下断点,Alt+B打开断点窗口
摁“;”在指定地址添加注释,并可以通过查找命令找到它。
鼠标右键 → 查找 → 用户自定义注释
红字显示部分即是光标所处位置。注释位置和光标位置重合时,将仅以红字方式显示。
双击相应注释,光标将自动定位到相应位置。
摁“:”在指定地址添加注释,并可以通过查找命令找到它。
鼠标右键 → 查找 → 用户自定义标签
红字显示部分即是光标所处位置。注释位置和光标位置重合时,将仅以红字方式显示。
双击相应注释,光标将自动定位到相应位置。
当程序功能非常明确时,逐条执行指令来查找需要查找的位置。
鼠标右键 → Search for(查找) → All intermodular calls(所有模块间的调用)
调出窗口 Alt+R
鼠标右键 → Search for(查找) → Name in all modules(所有模块中的名称)
调用窗口 Alt+N
在数据窗口摁Ctrl+G快捷键执行Goto命令,输入[字符串的地址]进入字符串缓冲区
或者选中字符串地址调用命令,右键→数据窗口中跟随→立即数
用鼠标选中字符串,Ctrl+E快捷键打开编辑窗口,进行修改.(字符串结尾要以NULL结束,需要在HEX项目中添加“00 00”)
选中修改的地方,右键→保存到可执行文件,在弹出的Hex窗口中单击右键→保存文件
但无法保存
OllyDbg常用命令
参见2.3.1调试器指令
Assembly(汇编语言)基础指令
参见汇编指令
术语
术语 | 说明 |
---|---|
VA (Virtual Address) | 进程的虚拟地址 |
OP code (OPeration code) | CPU指令 (字节码byte code) |
PE (Portable Executable) | Windows可执行文件 (EXE、DLL、SYS等) |
字节序是多字节数据在计算机内存中存放的字节顺序。字节序主要分为小端序和大端序两大类。
BYTE b = 0x12;
WORD w = 0x1234;
DWORD dw = 0x12345678;
char str[] = "abcde";
大端序与小端序的不同
TYPE | Name | SIZE | 大端序类型 | 小端序类型 |
---|---|---|---|---|
BYTE | b | 1 | [12] | [12] |
WORD | w | 2 | [12] [34] | [34] [12] |
DWORD | dw | 4 | [12] [34] [56] [78] | [78] [56] [34] [12] |
char[] | str | 6 | [61] [62] [63] [64] [65] [00] | [61] [62] [63] [64] [65] [00] |
字符串str存储时用ASCII码表示,字符串最后是以NULL结尾的。
通用寄存器 | 寄存器名称 | 额外描述 |
---|---|---|
EAX | (针对操作数和结果数据的)累加器 | 一般用于函数返回值,所有Win32 API函数都会把返回值保存到EAX内返回 |
EBX | (DS段中的数据指针)基址寄存器 | |
ECX | (字符串和循环操作的)计数器 | |
EDX | (I/O指针)数据寄存器 | |
EBP | (SS段中栈内数据指针)拓展基址指针寄存器 | EBP表示栈区域的基地址 |
ESI | (字符串操作源指针)源变址寄存器 | |
EDI | (字符串操作目标指针)目的变址寄存器 | |
ESP | (SS段中栈指针)栈指针寄存器 | ESP表示栈区域的栈顶地址 |
段寄存器 | 寄存器名称 | 额外描述 |
---|---|---|
CS | Code Segment,代码段寄存器 | CS寄存器用于存放应用程序代码所在段的段基址 |
SS | Stack Segment,栈段寄存器 | SS寄存器用于存放栈段的段基址 |
DS | Data Segment,数据段寄存器 | DS寄存器用于存放数据段的段基址 |
ES | Extra(Data) Segment,附加(数据)段寄存器 | |
FS | Data Segment,数据段寄存器 | |
GS | Data Segment,数据段寄存器 | ES、FS、GS寄存器用来存放程序使用的附加数据段的段基址 |
标志寄存器 | 寄存器名称 | 额外描述 |
---|---|---|
ZF | Zero Flag,零标志位 | 运算结果为0,则其值为1(Ture),否则其值为0(False) |
OF | Overflow Flag,溢出标志位 | 有符号整数溢出时,OF值被置为1,此外,MSB(最高有效位)改变时,其值也被设为1 |
CF | Carry Flag,进位标志位 | 无符号整数溢出时,其值被设为1 |
指令指针寄存器 | 寄存器名称 | 额外描述 |
---|---|---|
EIP | Instruction Pointer,指令指针寄存器 | 指令指针寄存器保存着CPU要执行的指令地址 |
栈内存在进程中的作用:
暂时保存函数内的局部变量
调用函数时传递参数
保存函数返回后的地址
栈是一种由高地址向低地址拓展的数据结构,按照FILO(First In Last Out,先进后出)的原则存储数据。
向栈压入数据时,栈顶指针减小,向低地址移动;从栈中弹出数据时,栈顶指针增加,向高地址移动。
栈顶指针在初始状态下指向栈底。
参见汇编指令
动调一遍
VB文件特征:
术语说明 | |
---|---|
调用者 | 调用函数的一方 |
被调用者 | 被调用的函数 |
例:在main()函数中调用printf()函数,调用者为main(),被调用者为printf() |
retn 8
这样的代码来清理栈。含义:retn
和pop 8
fastcall方式与stdcall方式基本类似
参数入栈顺序:第一个和第二个参数通过ecx和edx传递,多余的参数从右到左入栈
函数名:前加下划线 后面紧跟@符号,其后跟着参数的尺寸
该方式通常会使用寄存器(而非栈内存)去传递那些需要传递给函数的部分参数(前两个),前两个参数分别使用ECX与EDX寄存器来传递。
优势:可实现对函数的快速调用。
但有时需要额外的系统开销来管理ECX、EDX寄存器。若ECX和EDX中存有重要数据,需先备份。
gcc x64调用参数顺序为edi,esi,edx,ecx,r8d,r9d;
edi为第一个参数,esi为第二个参数,以此类推,再多余的参数再从右向左使用栈来传递
字段名 | 描述 | 字段值 | 偏移 |
---|---|---|---|
WORD e_magic | magic word | 5A4D(MZ)【表明是PE文件】 | 0 |
LONG e_lfanew | 保存着指向NT头的偏移 | (E8)字段值为NT头偏移 | 3C |
DWORD Signature | pe file signature | 4550【表明是PE文件】 | E8(e_lfanew) |
WORD Machine | 可执行文件的目标CPU | 0x014c【Intel 386】 0x0200【Intel 64】 |
) |
WORD Characteristics | PE文件类型 | 0x0002(可执行文件) 0x2000(dll文件) |
|
AddressOfEntryPoint(OEP) | 程序准备执行指令的第一个RVA | ||
ImageBase | PE文件的优先装载地址 | ||
SectionAlignment | 内存中节对齐的粒度 | ||
FileAlignment | 文件中节对齐的粒度 | ||
汇编指令 | 含义 |
---|---|
第2章 | |
CALL xxxx | 调用xxxx地址处的函数 |
JMP xxxx | 跳转到xxxx地址处 |
PUSH xxxx | 将xxxx压入栈 |
RETN | 返回,跳转到栈顶的地址 |
第6章 | |
push | 入栈指令 |
call | 调用指定位置的函数 |
int | 值加1 |
dec | 值减一 |
jmp | 跳转到指定地址 |
cmp | 比较给定的两个操作数(类似于sub命令),但是计算结果不会保留,仅改变标志位寄存器(若2个操作数的值一致,sub结果为0,ZF被置为1) |
je/jz | 条件跳转指令,若ZF为1,则跳转(也就是结果为0) |
第8章 | |
test | 逻辑比较,按位与两个操作数(类似于and命令),但计算结果不会保留,仅改变标志位寄存器(2个操作数只要有1个为0,则and的运算结果为0,ZF被置为1) |
题目:【安恒杯2019】crackme
SEH: 结构化异常处理
VEH: 向量化异常处理
TopLevelEH:顶层异常处理
EXCEPTION_EXECUTE_HANDLER :该异常被处理。从异常处下一条指令继续执行
EXCEPTION_CONTINUE_SEARCH:不能处理该异常,让别人处理它吧
EXCEPTION_CONTINUE_EXECUTION:该异常被忽略。从异常处处继续执行
//调试器返回值:
DBG_CONTINUE : 等同于EXCEPTION_CONTINUE_EXECUTION
DBG_EXCEPTION_NOT_HANDLED :等同于EXCEPTION_CONTINUE_SEARCH
如果该出现异常的程序正在被调试,则该异常首先交给调试器处理(通过DebugPort)。
调试器拿到这个异常后,需要判断是否要处理该异常,如果处理该异常返回DBG_CONTINUE,否则返回DBG_EXCEPTION_NOT_HANDLED
如果没有被调试,或者调试器返回DBG_EXCEPTION_NOT_HANDLED,则就会检查是否存在VEH。如果存在VEH,则把异常交给他们处理。
VEH是个链表,可以存在多个Veh。每个VEH按顺序被调用。
一个VEH可以返回连个值:EXCEPTION_CONTINUE_SEARCH、EXCEPTION_CONTINUE_EXECUTION。返回EXCEPTION_EXECUTE_HANDLER是无效的,等同于EXCEPTION_CONTINUE_SEARCH。
当一个Veh返回EXCEPTION_CONTINUE_SEARCH,则把异常交给下一个VEH处理。
如果返回EXCEPTION_CONTINUE_EXECUTION,认为已经被处理,退出异常处理器在异常指令处继续执行。
从执行顺序来看,VEH是在SEH之前执行的,并且不依赖某一线程,本进程中任何线程出现异常都可以被VEH处理,所以在有些时候是很有用处的。
当所有的VEH都不处理该异常,该异常就会让SEH处理。
SEH是基于线程栈的异常处理机制,所以它只能处理自己线程的异常。
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter (
_In_LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
设置异常捕获函数。
参数:lpTopLevelExceptionFilter ,函数指针。当异常发生时,且程序不处于调试模式(在vs或者别的调试器里运行)则首先调用该函数。
返回值:返回以前设置的回调函数。
- EXCEPTION_EXECUTE_HANDLER equ 1 表示我已经处理了异常,可以优雅地结束了
- EXCEPTION_CONTINUE_SEARCH equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
- EXCEPTION_CONTINUE_EXECUTION equ -1 表示错误已经被修复,请从异常发生处继续执行
注意:在调用者进程中的所有线程已经将来创建的线程的异常处理链顶函数都会被修改为当前设定的回调函数。
VEH通过使用 Win32 API 函数 AddVectoredExceptionHandler可注册新的异常处理函数,函数的参数就是指向 EXCEPTION_POINTERS 结构的指针。
同时,增加了函数地址的注册处理程序链表。由于系统中使用一个链表来存储矢量异常处理程序,程序可以安装尽可能多的向量处理器,只要有必要。
在用户模式下发生异常时,异常处理分发函数在内部会先调用遍历 VEH 记录链表的函数, 如果没有找到可以处理异常的注册函数,再开始遍历 SEH 注册链表。
二者之间具体联系:VEH优先权高于SHE,只有所有VEH全不处理某个异常的时候,异常处理权才会到达SHE。只要目标程序中没有利用VEH,那么,你所设计的VEH将是第一个得到控制者。现在采用SEH作为异常处理的普通C/C++程序对你将不会再有干扰,可以通过使用VEH来进行hook处理了。
如果存在调试器,那么控制权转向将会发生新的变化。当异常发生后,首先通知的将会是调试器,调试器不处理才会再返回控制权给VEH;如果VEH不处理,再返回给SHE;若SEH不处理,再给调试器一个机会,如果还不处理,则交由系统处理。
PVOID WINAPI AddVectoredExceptionHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
功能:用来向VEH链注册一个异常处理函数,
参数FirstHandler : 是否插入到VEH链首部
- 如果FirstHandler为1非0,则此注册的函数会在出现异常之后优先被触发(仅仅是优先,是否First还要看是否有其他人也注册了函数
- 如果FirstHandler为0,则此注册函数会在出现异常之后滞后被触发(理由同上)
参数VectoredHandler : 是一个要注册的异常处理回调函数
- 也就是需要执行的自定义函数
ULONG WINAPI RemoveVectoredExceptionHandler(
_In_ PVOID Handler
);
功能:从VEH链移除一个异常处理函数
参数Handler : 用前一个函数注册之后返回的指针
PVOID WINAPI AddVectoredContinueHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
//与第一个函数类似,但有区别
ULONG WINAPI RemoveVectoredContinueHandler(
_In_ PVOID Handler
);
//与第二个函数类似,但有区别
区别:
- 第一组函数注册、移除的VEH异常处理回调函数,会在SEH链里面的异常处理函数执行之前执行
- 第二组函数注册、移除的VEH异常处理回调函数,会在SEH链里面的异常处理函数执行之后执行
常用第一组
VEH由AddVectoredExceptionHandler添加处理函数
处理函数有一个参数参数类型为PEXCEPTION_POINTERS结构体
结构PEXCEPTION_POINTERS中保存着当前异常的各个寄存器,堆栈,地址,等多种信息 以下是PEXCEPTION_POINTERS的展开图
1.异常处理结构中 VEH是唯一一个可以接受到所有异常信息的处理 换句话说 所有的异常信息 都会经过VEH
2.异常信息 通常是由[数组越界,内存访问出错,无效参数,int 3]等 造成的
3.一旦发生异常 操作系统会立即便利VEH 如果有处理函数 中断线程 并由处理函数处理。
根据以上信息 可以确定一个思路 比如我们hook 消息框API 给API的首地址写入int 3 断点 当执行时会产生异常 线程暂停 转交给异常处理函数处理 此时则可以在处理函数中做手脚 比如修改堆栈参数等。
相应操作完成后 将int3(0xcc)修改回源代码 修改EIP=addr(异常地址)让此处的代码重新执行一次正确的代码,将Eflags寄存器修改为单步调试模式(Eflags |= 0x100) 返回EXCEPTION_CONTINUE_EXECUTION(-1)让程序继续运行 然后又会产生一次断点(单步异常),此时我们将已经执行过的代码重新修改为int3(0xcc) 返回-1让程序继续运行。
Enter 跟进函数实现,查看标号对应的地址
Esc 返回跟进处
A 解释光标处的地址为一个字符串的首地址
B 十六进制数与二进制数转换
C 解释光标处的地址为一条指令
D 解释光标处的地址为数据
G 快速查找到对应地址
H 十六进制数与十进制数转换
k 将数据解释为栈变量
; 添加注释
M 解释为枚举成员
N 重新命名
O 解释地址为效据段偏移量,用于字符串标号
T 解释数据位一个结构体成员
X 转换视图到交叉参考模式
Shift+F9 添加结构体
Shit+f12 查找字符串
shift+e 提取数据
寄存器传参和栈传参
Template的语法类似于C结构体,可以自己直接编写并加载,且支持基本的C语言语法。
ida的快捷键 定义数据的操作就是d 按u 是undefined成未定义的方式 按c 变成代码 按p 手动变成函数 按shift+F12,可以看到所有的字符串 按y统一编辑
ida下有一个dbgsrv中有一堆远程调试的东西
机器先root 安装Xposed框架 安装XInstaller,在其他设置中勾选调试应用
拿到flag:破除保护外壳、理解程序过程、找到验证函数、逆推得到flag 1. 正面长驱直入(文件比较小) - 从程序的入口点开始,逐步分析 - 层层深入最后抵达验证函数 - 静态分析 2. 从信息输入/输出处寻找(文件比较大)
1.没算法; - 直接输出flag 2.常见的算法; - 简单异或 - 带雪崩效应的异或(雪崩效应:前面值的结果会导致后面值结果的改变) - 加密算法(RSA、AES) - 散列算法(MD5、SHA1) - 解方程 3.有趣的算法; - 走迷宫 - ……(各种脑洞)
1.边信道攻击 - CTF中可以使用pintools - 原理:监测程序执行指令数 - 应用: - 逐字节验证的题目 2.Google大法好 - 加_双引号_搜索S-Box等常量快速确定算法 3.逆向小trick - 快速找main位置 - 寻找一个大跳转 - 快速定位关键位置 - 从Function List靠前的位置开始乱翻 - 从main函数旁边翻 - 编译时不同的原文件会被分别编译为.o再由编译器合并 - 应对MFC程序 - 使用_xspy_工具查看消息处理函数 - 拖上去看感兴趣的函数就可以,比如OnClick OnCommand - 手动加载Signature - 碰到无法自动识别库函数时 - (Shift - F5) View -> Open Subviews -> Signature - (Shift - F11) View -> Open Subviews -> Type Libraries 4.调试小技巧 - 如何得知MessageBox后在程序在哪里继续运行 - 在OD或x64dbg中找到内存布局列表(OD:Alt-M,或查看 -> 内存) - 找到自己程序的代码段(通常是.text,按F2)(下区段断点) - 返回程序点击即可
目前的CTF: - 代码量小 - 结构简单 单文件 - 编码单一 - 现代语言特性少 面向过程 - 加密壳/优化少 - 语言常见 C C++ ASM 现实: - 代码量巨大 - 结构复杂 大量静态库 动态库 - 各种乱码 - 大量线代语言特性 OO、Template - 优化和加密壳十分常见 - 语言可能十分神奇 Go 、VB 、Delphi
1.设置字符串编码&格式 - 快捷键:Alt-A - 可以设置字符串类型 - 可以设置字符串编码 2.导入/导出数据 - 快捷键:Shift-E - Edit -> Import/Export Data - 操作:选定后按快捷键 - 可以方便地提取数据或修改idb数据库 3.选定大段数据 - 快捷键:Alt-L - Edit -> Begin Selection 4.批量应用类型 - 组合技能 - 操作: - 设置好第一个类型 - 利用刚才的技巧选定数据 - 按d弹出建立数据对话框 - 不勾选Create as array 5.设置间接跳转地址 - 快捷键:Alt-F11 - Edit -> Plugins -> Change the callee address 6.修复跳转表 7.IDAPython - IDA自带支持脚本 - 可以使用几乎所有IDA提供的API - 可以快速完成大量重复性操作 - 可以方便的了解IDA内部的数据和结构
32位和64位混合时,手动加载(Munual Load)
导入头文件 如果能拿到程序的一部分头文件,比如程序公开的API接口,那么可以通过头文件让IDA了解这个结构并辅助分析 改可变参数,…三个点
一个功能会有多个切入位置
例如想让Win10 14393允许关闭UAC时运行 Windows Store App就有如下切入点:
(反向)注册表中EnableLUA键值控制UAC开闭
(反向)“无法使用内置管理员账户”提示
(
正向
)从Store App启动流程入手
《加密与解密》 《IDA Pro权威指南》 《软件调试》
run //运行 缩写r
disas 函数名 //反编译函数
break *地址(函数名) //设置断点 缩写 b
info breakpoint //查看断点 缩写 i b
info register //查看寄存器 缩写 i r
ni //单步执行
si //单步步入
backtrace //显示上层所有stack frame 信息
continue //继续运行 缩写 c
x/wx address //查看address中的内容
w可以换成b/h/g分别对应1/2/8 byte
/后可以跟数字,表示一次列出几个
第二个x可以换成u/d/s/i以不同方式表示地址内的内容
list //列出源代码
print 变量名 //打印变量值
info local //显示局部变量
set *address = value
将address中的值设成value,一次设4byte
可以将*换成{char/short/long}分别表示1/2/8byte
1-9 分别使用 1-9 个脉冲,0 则表示使用 10 个脉冲。
点(·):1
划(-):111
字符内部的停顿(在点和划之间):0
字符之间的停顿:000
单词之间的停顿:0000000
字符间可用 空格 或 单斜杠/ 来分割,单词之间采用....
分割
摩斯电码解码:
敲击码(Tap code)是一种以非常简单的方式对文本信息进行编码的方法。因该编码对信息通过使用一系列的点击声音来编码而命名,敲击码是基于 5 ×5 方格波利比奥斯方阵来实现的,不同点是是用 K 字母被整合到 C 中。
Tap Code | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | A | B | C/K | D | E |
2 | F | G | H | I | J |
3 | L | M | N | O | P |
4 | Q | R | S | T | U |
5 | V | W | X | Y | Z |
源文本 | B | N |
---|---|---|
位置 | 1,2 | 3,3 |
敲击码 | … . | … … |
A-Z/a-z 对应 1-26 或者 0-25
特点:大量的百分号
源文本: The
[Hex]: The
[Decimal]: The
\U [Hex]: \U0054\U0068\U0065
\U+ [Hex]: \U+0054\U+0068\U+0065
文件后缀名 | 文件头(Hex) | Offset | 文件尾(Hex) |
---|---|---|---|
JPEG(jpg) | FF D8 FF | 0 | FF D9 |
PNG(png) | 89 50 4E 47 0D 0A 1A 0A | 0 | AE 42 60 82 |
GIF(gif) | 47 49 46 38 | 0 | 00 3B |
zip / apk / jar | 50 4B 03 04 | 0 | 50 4B |
RAR(rar) | 52 61 72 21 1A 07 | 0 |
更多请参考List of file signatures
file
命令根据文件头(魔法字节)去识别一个文件的文件类型。
root in ~/Desktop/tmp λ file flag
flag: PNG image data, 450 x 450, 8-bit grayscale, non-interlaced
strings
命令打印文件中可打印的字符,经常用来发现文件中的一些提示信息或是一些特殊的编码信息,常常用来发现题目的突破口。
可以配合 grep
命令探测指定信息
strings test|grep -i XXCTF
也可以配合 -o
参数获取所有 ASCII 字符偏移
root in ~/Desktop/tmp λ strings -o flag|head
14 IHDR
45 gAMA
64 cHRM
141 bKGD
157 tIME
202 IDATx
223 NFdVK3
361 |;*-
410 Ge%
binwalk
命令binwalk 本是一个固件的分析工具,比赛中常用来发现多个文件粘合再在一起的情况。根据文件头去识别一个文件中夹杂的其他文件,有时也会存在误报率(尤其是对Pcap流量包等文件时)。
root in ~/Desktop/tmp λ binwalk flag
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 PNG image, 450 x 450, 8-bit grayscale, non-interlaced
134 0x86 Zlib compressed data, best compression
25683 0x6453 Zip archive data, at least v2.0 to extract, compressed size: 675, uncompressed size: 1159, name: readme.txt
26398 0x671E Zip archive data, at least v2.0 to extract, compressed size: 430849, uncompressed size: 1027984, name: trid
457387 0x6FAAB End of Zip archive
配合 -e
参数可以进行自动化提取。
也可以结合 dd
命令进行手动切割。
root in ~/Desktop/tmp λ dd if=flag of=1.zip bs=1 skip=25683
431726+0 records in
431726+0 records out
431726 bytes (432 kB, 422 KiB) copied, 0.900973 s, 479 kB/s
\
:强制换行uname
命令 查看系统相关信息
-a:显示主机名、内核版本、硬件平台等详细信息
-r:显示内核版本
hostname
查看主机名
ifconfig
查看ip地址
cat proc/cpiomfo
查看CPU信息
cat /proc/meminfo
查看内存信息
shutdown -h now
立刻关机
halt
立刻关机
shutdown -r now
立刻重启
reboot
立刻重启
cp filename
复制文件或者目录
-r 递归复制整个
-z
调用gzip程序进行解压或者压缩-j
调用bzip2程序进行解压或者压缩-c
创建归档文件, 拓展名为.tar-v
输出详细信息-f
使用归档文件-x
解开归档文件-t
列表查看包内文件tar -cvf 终.tar file1 file2 file3 ...
将文件123打包成终.tartar -xvf 终.tar
解包终文件tar -xvf file.tar -C dir
解包tar文件释放到dir目录重启网络服务service network restart
ls
列出目录下的文件ls -l
列出当前目录下文件的详细信息ls - alh
列出当前目录下文件的详细信息(包括隐藏文件cd
更换工作目录pwd
显示当前所在目录cd ..
返回上级目录cd -
返回上次操作目录cd ~
返回用户目录mkdir
创建空白目录rmdir
删除空目录rm
删除目录或文件rm -rf *
删除全部mv dir_name destination
移动dir_name到destination目录mv dir/file newname
重命名查看
cat file
查看文件head file
显示文件前几行tail file
显示文件后几行tac file
从最后一行开始显示直到第一行nl file
带行号查看文件more file
一页一页显示文件du -h file
查看文件大小df -h file
查看文件占用磁盘大小进程
ps -aux | grep root
检索root用户下进程kill -9 PID
结束进程vi编辑器
touch file
新建一个文件vi file
编辑某个文件(不存在就创建
-r
用于恢复系统突然崩溃时正在编辑的文件-R
以只读方式打开文件+n
进入vi后直接位于文件的第n行,不指定n位于最后一行用户信息
cat /etc/passwd
查看用户信息useradd username
添加用户userdel username
删除用户groupadd groupname
添加用户组groupdel groupname
删除用户组cat /etc/group
查看组cat /etc/shadow
查看用户密码cat /etc/chadow |grep username
查看指定用户密码passwd username
修改用户密码文件权限
4 可读 2 可写 1 可执行
chmod 777 file
配置权限可读可写可执行chmod -R 777 file
递归更改权限chmod +/-x file
添加或减少权限find查找文件
find -name filename
查找filename这个文件设置计划任务
crontab -l
查看计划任务
crontab -e
进入编辑模式设定计划任务
格式:分 时 日 月 周 命令
ctrl + x 退出
yum命令
yum -y install 软件名
下载安装yum -y localinstal 软件包名
安装软件yum -y localinstall *.rpm
本地批量安装yumupdate
全部更新yum update 包名
更新某个包yum check-update
检查可更新的包yum grouplist
列举系统中以组安装的包yum remove 软件包名
卸载软件yum groupremove 组名
删除程序组yum deplist 包名
查看依赖关系yum clean all
清除全部缓存yum ma kecache
更新源yum list |grep 包名
查看有没有对应的包apt-get
apt-get update
更新源apt-get upgrade
更新系统apt-get install 包名
安装软件apt-get remove 包名
删除软件apt-cache search 某个关键字
搜索需要的软件包含在哪个包里apt-get clean
清空缓存包pip命令
pip --version
查看pip版本和路径pip --help
查看帮助pip install -U pip
升级pippip install
安装包(最新版本) 指定版本号可用==,>=,<=,>,<pip install --update
升级指定的包pip uninstall
卸载指定的包pip search
搜索包pip show
显示安装包信息pip show -f
查看指定包的详细信息pip list
列出已安装的包pip list -o
查看可升级的包python2 -m pip install XXX
python3 -m pip install XXX
文本命令
cat file |grep 要搜索的关键字
搜索包含关键字的行并显示cat file |grep -v 要去除的关键字
显示去除后的文本cat file |sort
文本排序后显示cat file |uniq
文本去掉重复的再显示cat file wc -l
计算行数file>>file
文本重定向diff file1 file2
比较两个文本的差异split -l 数量n file
将文本文件分割成数量n个小文件