Notes-Decompile

DOS模拟器

DOSbox

挂载主机文件夹

开机自动配置如下:

1
2
3
4
mount c C:\Users\admin\Desktop\tool\debug
mount d C:\Users\admin\Desktop\binary\16asm
set path=c:
d:

msdosplayer

VS插件-MASM/TASM

16ASM汇编

8086cpu组织结构——寄存器(cpu的“局部变量”)

•EU部件:执行部件(excution unit)、译码、执行指令

•BIU部件:总线接口部件(bus interface unit)、取指令、读取数据、写入数据

常见反汇编指令

查看地址汇编-u

1
2
3
-u[range]
-u 11c 120 #[range] = [startaddr][endaddr]
-u 11c l 2 #从11c地址开始,(list)打印两个字节的存储地址 [startaddr l num]

执行汇编指令-a

1
2
3
4
5
6
-a[address] 
-a 110
mov ax, ax
mov dx, dx
mov ax, dx
mov byte ptr [110],al

寄存器操作-r

1
2
3
4
-r
-r eax
:ff00
-r

内存查看-d

1
2
3
-d[range]
-d 120 130
-d 120 l 10

修改内存-e

1
2
3
-e 110
-e 120 11,12,13,14,15,16 17 18 19 20
-e 120 "Hello world"

调试命令(g,t,p)

1
2
3
g - F5
t - F11
p - F10

写入文件(n,cx,w)

1
e addr "字符串"

标志寄存器

  1. 条件标志位
    SF ZF OF CF AF PF
    CPU执行完一条指令后自动设置。
    反映算术、逻辑运算等指令执行完毕后,运算结果的特征。
  2. 控制标志位
    DF IF TF
    控制CPU的运行方式,工作状态。
1
OF DF IF SF ZF AF PF CF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
溢出OV(overflow,OF=1)
无溢出NV(no overflow,OF=0)

减量DN(direction down,DF=1)
增量UP(direction up,DF=0)

允许中断EI(enable interrupt,IF=1)
进制中断DI(disable interrupt,IF=0)

负NG(negative,SF=1)
正PL(plus,SF=0)

零ZR(zero,ZF=1)
非零NZ(no zero,ZF=0)

辅助进位AC(auxiliary carry,AF=1)
无辅助进位NA(no auxiliary carry,AF=0)

偶校验PE(even parity,PF=1)
奇校验PO(odd parity,PF=0)

进位CY(carry,CF=1)
无进位NC(no carry,CF=0)

CF进位标志

当运算结果的最高有效位有进位(加法)或借位(减法)时设置。

进位标志置1,即CF = 1;否则CF = 0

用途:用于表示两个无符号数高低。

举例:

3AH + 7CH=B6H,没有进位:CF = 0

AAH + 7CH=(1)26H,有进位:CF = 1

1
2
3
4
5
-a #编写汇编指令
mov al, 3a
add al, 7c
-u #查看当前地址
-t #执行,关注IP

ZF零标志

运算结果为0则ZF=1,否则ZF=0。表示两个无符号数高低。

用途:用于表示两个无符号数高低。

1
2
3AH + 7CH=B6H,结果不是零:ZF = 0
84H + 7CH=(1)00H,结果是零:ZF = 1

OF溢出标志

•使用该标志位判断运算结果是否溢出。(当将操作数作为有符号数时)

–加法:若同符号数相加,结果的符号与之相反则OF=1,否则OF置0。

–减法:被减数与减数异号,而结果的符号与减数相同则OF=1,否则置0。

•发生了溢出,说明了运算结果不可信。

1
2
3AH + 7CH=B6H,产生溢出:OF = 1
AAH + 7CH=(1)26H,没有溢出:OF = 0

•进位针对的是无符号数运算,溢出针对的是有符号数运算。

•当看成无符号数,则关注CF标志,看成有符号数,则关注OF标志。

SF符号标志

•运算结果最高位为1,SF为1,否则为0。

•有符号数据用最高有效位表示数据的符号,最高有效位是符号标志的状态。

1
2
3AH + 7CH=B6H,最高位D7=1:SF = 1
84H + 7CH=(1)00H,最高位D7=0:SF = 0

PF奇偶标志位

•当运算结果(指低8位)中1的个数为偶数时,PF置1,否则置0。

•作用:该标志位主要用于检测数据在传输过程中的错误。

1
2
3AH + 7CH=B6H=10110110B
结果中有5个1,是奇数:PF = 0

AF辅助进位标志位

•表示一个字节的低4位是否有进位和借位。运算时D3位(低半字节)有进位或借位时,AF = 1;否则AF = 0。

•处理器内部使用,用于十进制算术运算调整指令中,用户一般不必关心

1
3AH + 7CH=B6H,D3有进位:AF = 1

分段和指令

8086是16位cpu,最多可访问(寻址)多大内存? 2^16 = 65536 = 2^6 * 2^10 = 64k

8086允许最大内存1M,1M = 10K = 2 ^ 20 内存[00000]-[FFFFF]

段基址(CS) + 段偏移 。的方式一般写作 段地址:段偏移,称作逻辑地址

偏移地址称作EA

通过逻辑地址计算出来的内存地址称作物理地址

逻辑地址 -> 地址加法器 -> 物理地址

内存地址 = 段基址 *** 10h +** 段偏移

073F:0100 = 073 * 10 + 100

8086中,段基址都是存储在段寄存器中,段偏移可以用立即数或者通用寄存器指明。

立即寻址

操作数的值存储在指令中的方式称作立即寻址。

汇编中整数常量称作立即数。

立即数可以是8位数,也可以是16位数。

1
2
MOV AL, 80H    ;将8位立即数80H送入AL寄存器
MOV AX, 1234H ;将16位立即数1234H送入AX寄存器

寄存器寻址

操作数的值存储在寄存器的寻址方式称作寄存器寻址。

寄存器包括通用寄存器,段寄存器。

1
2
MOV CL, DL    
MOV AX, BX

PS:

段寄存器之间不能赋值。

指令指针寄存器不能用作寻址。

直接寻址

操作数值在内存中,机器码中存储16位段内偏移的寻址方式称作直接寻址。

1
MOV AL, [1064H]  #基地址(ds)为2000
1
2
3
4
5
6
7
8
9
-r
-r ds
:2000
-r
-e 1064 45 78
-d 1064 l 20
-r
-a xxx
mov ax, [1064]

PS:

-r 查看当前寄存器状态,能查看到各种寄存器的值,CS用于存储基地址IP用于指向偏移地址,最下面的指令是后续将要执行的指令地址和内容,常用于直接-a编辑。

间接寻址

a.操作数值在内存中,段内偏移存储在寄存器中的寻址方式称作寄存器间接寻址。

b.间接寻址的寄存器有BX, BP, SI, DI。

1
2
MOV AX, [SI]  ;将SI中的值作为段内偏移,从内存中取出数据赋值AX
MOV [BX], AL ;将BX中的值作为段内偏移,把AL中的值赋值给对应内存
相对寻址

a.操作数值在内存中,段内偏移存储由[寄存器+立即数]计算得来的的寻址方式称作寄存器相对寻址。

b.寄存器相对寻址的寄存器有BX, BP, SI, DI。

c.寄存器相对寻址的立即数可以是8位,可以是16位的。

1
2
MOV  [SI+10H], AX  
MOV CX,[BX+COUNT]
基址变址寻址

a. 操作数值在内存中,段内偏移由[寄存器+寄存器]计算得来的寻址方式称作基址变址寻址。

b. 可用做基址的寄存器有BX, BP。(只能基址+变址/变址+基址)

c. BX默认DS段,BP默认SS段。

d.可用作变址的寄存器有SI, DI。

1
2
MOV  [BX+DI], AX
MOV CX, [BP+SI]
基址变址相对寻址

a. 操作数值在内存中,段内偏移由[基址寄存器+变址寄存器+偏移常量]计算得来的寻址方式称作基址变址寻址。

b. 可用做基址的寄存器有BX, BP。

c. BX默认DS段,BP默认SS段。

d.可用作变址的寄存器有SI, DI。

e.可用作常量的数值可以是8位,可以是16位。

可用于模拟数据运算

PS:

不可内存到内存,mov [bx],[ax]

关于机器码

手动分析:

1
2
3
4
5
6
7
8
mov ax,bx
mov ax,cx
mov ax,dx
mov ax,ax
mov ax,bp
mov ax,si
mov ax,di
mov ax,sp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
89D8  MOV AX,BX  11 011 000  
89C8 MOV AX,CX 11 001 000
89D0 MOV AX,DX 11 010 000
89C0 MOV AX,AX 11 000 000
89E8 MOV AX,BP 11 101 000
89F0 MOV AX,SI 11 110 000
89F8 MOV AX,DI 11 111 000
89E0 MOV AX,SP 11 100 000

AX 000
CX 001
DX 010
BX 011
SP 100
BP 101
SI 110
DI 111

MOV DX,BP
89 11 101 010
=>89 1110 1010
=>89EA

内存里运行的是机器码

数据传送和算术指令

机器码、内存、寄存器、立即数(8086、286、386、486)

MOV传送指令

把一个字节或字的操作数从源地址传送至目的地址。注意:不存在存储器向存储器的传送指令。

1
2
3
4
mov [bx],ax
mov ax,[bx]
mov byte ptr[bx], 12 ;要指定字节数,mov双方寄存器长度要一致
mov byte word[bx], 12 ;两个字节,可能存在溢出覆盖
1
2
mov byte ptr[bx],12
mov word ptr[bx],12

XCHG交换指令

1.寄存器与寄存器之间对换数据

2.寄存器与存储器之间对换数据

3.不能在存储器与存储器之间对换数据

1
XCHG AX,BX

XLAT换码指令

将BX(数组首地址)指定的缓冲区中、AL(数组下标索引)指定的位移处的一个字节取出赋给AL

al <– ds:[ bx + al ]

1
2
3
mov bx,0
mov al,e
XLAT

数组首地址是0,索引是e,取值赋给al

push、pop堆栈操作指令

1
2
3
4
5
6
7
8
9
1. 进栈(push reg)
sub sp , 2
mov [sp] , reg
2.出栈(pop reg)
mov reg, [sp]
add sp , 2
3.保存所有寄存器环境
16位:pusha / popa
32位:pushad / popad

PUSHF、POPF标志进出栈指令

•PUSHF指令将标志寄存器的内容压入堆栈,同时栈顶指针SP减2

•POPF指令将栈顶字单元内容送标志寄存器,同时栈顶指针SP加2

LEA地址传送指令

•地址传送指令将存储器单元的逻辑地址送至指定的寄存器

–有效地址传送指令 LEA

–指针传送指令 LDS和LES

•注意不是获取存储器单元的内容

1
2
3
4
5
6
7
LEA和MOV的区别(bx+si = ea)
LEA bx,[bx+si] // bx = ea 不取地址内容
mov bx,[bx+si] // bx = [ea] 取地址内容

add bx,si
mov ax,bx
=> lea ax,[bx,si]

算数运算类指令

加法(ADD|ADC|INC)

ADD:加法,影响标识位

1
2
3
4
ADD reg,imm/reg/mem
;reg←reg+imm/reg/mem
ADD mem,imm/reg
;mem←mem+imm/reg

ADC:带进位加法,带标识位

1
2
3
4
ADC reg,imm/reg/mem
;reg←reg+imm/reg/mem+CF
ADC mem,imm/reg
;mem←mem+imm/reg+CF

INC:加一,不影响CF标志位

减法(SUB|SBB|DEC)

SUB:减法,影响标识位

1
2
3
4
SUB reg,imm/reg/mem
;reg←reg-imm/reg/mem
SUB mem,imm/reg
;mem←mem-imm/reg

SBB:带借位的减法

1
2
3
4
SBB reg,imm/reg/mem
;reg←(reg-(imm/reg/mem)-CF)
SBB mem,imm/reg
;mem←mem-imm/reg-CF

DEC: -1,不影响CF位

求补(NEG)

•NEG指令对操作数执行求补运算:用零减去操作数,然后结果返回操作数

•求补运算也可以表达成:将操作数按位取反后加1

•neg ax ;如果ax = 0,则CF标志位 = 0;若ax != 0, 则CF = 1

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
问题:
Reg == 0 ? 0 : -1
Reg == 1 ? 1 : 0
Reg == 8 ? 9 : 8
Reg == 6 ? 8 : 9

mov ax, X
sub ax, 0
neg ax
sbb ax, ax
sbb ax, ax

1.X = 8(1000)
mov ax, X ; ax = 8
sub ax, 0 ; ax = 8
neg ax ; ax = -8, CF = 1
sbb ax, ax; ax = -1, CF = 1
sbb ax, ax; ax = -1

2.X = 0
mov ax, X ; ax = 0
sub ax, 0 ; ax = 0
neg ax ; ax = 0, CF = 0
sbb ax, ax; ax = 0, CF = 0
sbb ax, ax; ax = 0

比较(CMP)

•格式:: CMP OPD,OPS

•功能:(OPD)-(OPS)

•说明:目的操作数减去源操作数,然后根据结果设置标志位,但该结果并不存入目的地址(sub存入)

•影响标志位:AF、CF、OF、PF、SF、ZF

•作用:一般的后面跟一条条件转移指令,根据比较结果转向不同的程序分支,用于处理OPD和OPS大小比较的不同情况。

乘法(MUL/IMUL)

无符号乘法

格式:MUL Reg/Mem

功能:显式操作数*隐含操作数(看成无符号数)。

影响标志位:CF和OF。

位数 隐含的被乘数 乘积的存放位置 举例
8位 AL AX MUL BL
16位 AX DX&AX MUL BX
32位 EAX EDX&EAX MUL ECX
1
2
3
4
5
6
7
8
9
10
11
两个byte相乘要用word放(FF*FF=FE01)
两个word相乘要用dword放(FFFF*FFFF=FFFE0001,没dword就两个寄存器)

mov bl, 88
mov al, 99
mul bl //存到AX

mov word ptr[0], 1234
mov bx, 4567
mov ax, 4567
mul word ptr[0], 4567 //存到DX&AX

如果乘积的高一半位(AH/DX/EDX)包含有乘积的有效位,则CF=1、OF=1; 否则,CF=0,OF=0。

OF=CF=1则说明:
字节乘字节结果超过了8位
字乘字结果超过了16位
双字乘双字结果超过了32位

1
2
3
4
mov bl, 15
mov al, 4
mul bl

有符号乘法

IMUL Reg/Mem

IMUL Reg, Imm ;80286+

IMUL Reg,Reg,Imm;80286+

IMULReg,Reg/Mem;80386+

功能:有符号数相乘

影响标志位:CF和OF。会扩展符号

1
2
3
4
5
6
7
8
9
如果乘积的高一半位(AH,DX,EDX)不是低位的符号扩展,则CF=1、OF=1;否则,CF=0,OF=0。
-1 * 1 = -1
FF * 1 => AX => 00FF 会被当做正数,则补全符号位 => FFFF


-举例:
mov bl, -2
mov al, 1
imul bl
1
2
3
mov bl, -15
mov al, 15
imul bl //不完全是符号位扩展,有数据位

EB * 15 = 1347

除法(DIV/IDIV)

无符号除法

格式:DIV Reg/Mem 被除数/除数 = 商 .. 余数

位数 被除数 除数 余数
8位 AX 8位ops AL AH
16位 DX,AX 16位ops AX DX
32位 EDX,EAX 32位ops EAX EDX

影响标志位:未定义

未定义:指令执行后这些标志是任意的,不可预测的。没有影响:指令执行后不改变标志状态

1
2
3
mov ax, 9
mov bl, 4
div bl
1
2
3
4
mov dx, 1234   // dx作用暂存
mov ax, 5678
mov bx, 4569
div bx // 12345678 / 4569 = 4324 . 18B4
有符号数除法

格式:IDIV Reg/Mem

位数 被除数 除数 余数
8位 AX 8位ops AL AH
16位 DX,AX 16位ops AX DX
32位 EDX,EAX 32位ops EAX EDX

影响标志位:AF、CF、OF、PF、SF和ZF

1
2
3
mov ax, -19 // 十六进制的-19  0001 1001 => 1110 0111 => E 7 
mov bl, 2 // 02
idiv bl // E7/2=F4

被除数远大于除数时,所得的商就有可能超出它所能表达的范围。

idiv除法溢出:-字节除时商不在-128~127范围内,或者在字除时商不在-32768~32767范围内

div除法溢出: 8位除法运算结果大于8位,16位除法运算结果大于16位。

举例: ax= FFFF, bl = FF, div bl

结果:相当于FFFF/FF=101,此时AH显然放不所以商溢出

符号扩展指令(CBW/CWD)

CBW(将字节转换成字指令):

-语句格式:CBW(convert byte to word)

-功能:将AL中的符号扩展至AH中,操作数是隐含且固定的。把数值最高位扩展

1
2
3
4
MOV AL, 04
CBW
MOV AL, FE
CBW

CWD(将字转换成双字指令):

-语句格式:CWD

-功能:将AX中的符号扩展至DX中,操作数是隐含且固定的

1
2
3
4
MOV AX, 1234
CWD
MOV AX, FFF7
CWD

逻辑运算

逻辑与:AND

-指令的格式:AND Reg/Mem, Reg/Mem/lmm

-受影响的标志位:CF(O)、OF(O)、PF、SF和ZF(AF无定义)

1
2
3
4
5
6
mov ax, 4569  ; 0100 0101 0110 1001
and ax, 1234 ; 0001 0010 0011 0100
mov bx, 4541
and ax, bx
mov word ptr [0], 1111
and ax, [0]
逻辑或:OR

-指令的格式:OR Reg/Mem, Reg/Mem/Imm

-受影响的标志位:CF(O)、OF(O)、PF、SF和ZF(AF无定义)

1
2
3
mov ax, 4569  ; 0100 0101 0110 1001
or ax, 1234 ; 0001 0010 0011 0100
; 0101 0111 0111 1101
逻辑非:NOT

-指令的格式:NOT Reg/Mem

-受影响的标志位:无

1
2
mov ax, 4569 ; 0100 0101 0110 1001
not ax
异或:XOR

-指令的格式:XOR Reg/Mem,Reg/Mem/lmm

-受影响的标志位:CF(O)、OF(O)、PF、SF和ZF(AF无定义)

(相同为0不同为1,用于求两个数是否不同)

1
2
3
mov ax, 4569 ; 0100 0101 0110 1001
mov bx, 1234 ; 0001 0010 0011 0100
xor ax,bx ; 0101 0111 0101 1101
TEST 指令

格式:TEST Reg/Mem,Reg/Mem/lmm

作用:执行AND,但是不影响目标操作数

受影响的标志位:CF(O)、OF(O)、PF、SF和ZF(AF无定义)。

1
2
3
Eg: test ax, ax
// ax为0,则ZF=0;
// ax不为0,则zF = 1

总结

1)如果要将目的操作数中某些位清O,用AND,称之为屏蔽

2)如果要将目的操作数中某些位置1,用OR

3)用来测试目的操作数中某一位或某几位是否为0或1,而目的操作数不变,TEST

4)TEST与CMP的区别,前者是测试一位或几位,后者测试整个字节/字/双字是否相等

5)操作数自身相或、相与结果不变6)xOR AX,Ax 将Ax置0,比mov ax,0 更高效。

1
2
3
4
5
6
7
8
mov ax, 4
=>
mox dx, cx
not cx
and dx, cx
mov bx, 4
xor bx, dx
mov ax, bx

逻辑恒等式

1
2
3
4
5
6
a and 1 = a
a and 0 = 0
a xor 1 = not a
a xor 0 = a
a xor a = 0
a and not a = 0

无分支求绝对值

1
2
3
4
mov ax, X
cwd
xor ax, dx
sub ax, dx

无分支求三目运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1、reg == 8 ? 13:7
mov ax, X
sub ax, 8
neg ax
sbb ax, ax ;if X=8, ax=0
;if X!=8, ax=-1
mov dx. -6
and dx, ax
add dx, 13

2、reg == 4 ? 6:11
mov ax, X
sub ax, 4
neg ax
sbb ax, ax ;if X=4, ax=0
;if X!=4, ax=-1
mov dx. 5
and dx, ax
add dx, 6

反汇编

反编译

汇编不会优化、高级语言会优化

反汇编引擎的基本框架

默认都是ds

bp -> ss

ip -> cs

用winhex从内存寻找到dbox对应的值

DAA/DAS、 AAA/ AAS/AAM/AAD

特权指令

指令手册,不同系统

808x、286、386、486

移位指令

逻辑移位&算数移位
指令 名称 格式 意义 影响标志
SAL(shift arithmetic left) 算数左移 op r/m, 1/cl 1、左移CNT位
2、高位进CF
3、低位补0
OF,ZF,SF,PF,CF
SHL(shift logical left) 逻辑左移 op r/m, 1/cl 1、左移CNT位
2、高位进CF
3、低位补0
OF,ZF,SF,PF,CF
SAR(shift arithmeticright) 算数右移 op r/m, 1/cl 1、右移CNT位
2、高位保持不变(补自己)
3、低位进CF
OF,ZF,SF,PF,CF
SHR(shift logical right) 逻辑右移 op r/m, 1/cl 1、左移CNT位
2、高位补0
3、低位进CF
OF,ZF,SF,PF,CF
1
2
3
4
5
6
7
8
MOV AX, FEFE
SHL AX, 1
MOV CL, 2
SHL AX, CL
MOV WORD PTR [0], 5566
SHL WORD PTR [0], 1
SHL WORD PTR [0], CL
AX, CL
循环移位
指令 名称 格式 意义 影响标志
ROL(rotate) 循环左移 op r/m, 1/cl 1、左移
2、高位进低位
3、高位进CF
OF、CF
其他标志无定义
ROR(rotate) 循环右移 op r/m, 1/cl 1、右移
2、低位进高位
3、低位进CF
OF、CF
其他标志无定义
RCL(carry) 带进位循环左移 op r/m, 1/cl 1、左移
2、高位进CF
3、CF进低位
OF、CF
其他标志无定义
RCR(carry) 带进位循环右移 op r/m, 1/cl 1、右移
2、高位进CF
3、CF进高位
OF、CF
其他标志无定义
1
2
3
4
5
mov ax, fefe
rol ax, 1
mov ax, fefe
rol ax, 2
rol ax, cl

ASM基础语法

配置Masm615

VSCode安装MASM/TASM插件

编译命令

1
2
ml /c xx.asm
link xx.obj

编译+调试 脚本

1
2
3
ml /c %1.asm
link %1.obj
debug %1.exe

直接用插件,插件的环境是临时的

1
2
3
4
5
6
7
8
9
10
11
12
ml /c test.asm    ;编译生成obj文件
link test.obj ;链接生成exe文件
debug test.exe

data_seg segment
mov ax, ax
mov ax, ax
ENRTY:
mov ax, ax
mov ax, ax
data_seg ends
end ENRTY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data segment ;数据段
string db 'zhuge shabi$'
data ends
code segment ;代码段
assume cs:code,ds:data
start:
mov ax,data ;获取段基址
mov ds,ax ;将段基址送入寄存器
mov dx,offset string
mov ah,9
int 21h
mov ah,4ch
int 21h
code ends
end start

debug exe,用前边的-u,-a调试

入口

  • 一个程序必须至少有一个段
  • 一个程序中可以定义多个段
  • 段不能嵌套
  • 段可以重名,重名的段会被编译到同一块内存中
1
2
3
4
5
段名 segment 
start:
xxx
段名 ends
end start

数值

整数

  • 整数可以支持多个进制
  • 数值必须以数字开头,如果非数字前面必须加0
  • 负数前面可以加减号(-)
1
2
3
4
十进制 mov ax, 1234 / mov ax, 1234d
二进制 mov ax, 1011b
八进制 mov ax, 76o
十六进制 mov ax, 76h / mov ax, 0abh
1
2
3
4
5
6
7
8
9
10
code segment 
start:
mov ax, 256
mov ax, 256d
mov ax, 10000000b
mov cx, 100
mov dx, 1234h
mov dx, -1h
code ends
end start

字符

1
2
3
4
mov al, 'a'
mov bl, 'b'
mov cl, 'c'
mov dl, 'd'

变量

整数
  • 整数可以支持多个类型
  • 整数可以有多个初值,未初始化的值用问号(?)表示
  • 变量一般定义在一个单独的段中

格式:变量名 类型 初始值
val dd 5566h

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
;数据段和操作段分开
data_seg segment
g_btVal db 55h ; db 字节
g_wVal dw 5566h ; dw 字
g_dwVal dd 55667788h ; dd 双字
g_qVal dq 1122334455667788h ; dq 8字节
g_tVal dt 11223344556677889900h ; dt 10字节
data_seg ends

uinitdata_seg segment
g_btVal1 db ?
g_wVal1 dw ?
g_tVal dt ?
uinitdata_seg ends

code_seg segment
START:
assume ds:data_seg ;ds:ip,此处指定下面g_btVal变量去data_seg找
mov ax, data_seg ;还需要重新指定ds的值,否则基地址错误
mov ds, ax

mov al, g_btVal
mov ax, g_wVal
code_seg ends
end START

-d 查看内存往前偏移

076D->0769为什么是偏移两行

-u 看基址,-d指定基地址看数据,mov是取地址的值,-u看要mov的值的地址

字符串
  • 字符串都可以用单引号()或双引号(“”)
  • 字符串一般以美元符$结尾
1
2
3
4
9_sz db "hello world$" ;16位汇编中以美元符结尾
data_seg segment
g_szHello db "hello world$" ; db 字节
data_seg ends
数组
1
2
3
4
5
6
data_seg segment
g_ary db 10h, 11h, 'a', 'b', 'c', 'd'; db 字节
g_ary1 db 10 dup(0cch); 重复10个字节,每个字节填充cc
g_ary2 db 10 dup(?)
g_ary3 db 45h, 10 dup(46h), 47h, 20 dup(48h)
data_seg ends
属性

masm提供了很多伪指令,可以获取变量的大小和地址称之为变量的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
seg      取段基址
offset 取段偏移
type 取元素类型大小
length 取元素个数
size 取数据大小

data_seg segment
g_btVal db 55h ; db 字节
g_wVal dw 5566h ; dw 字
g_dwVal dd 55667788h ; dd 双字
g_qVal dq 1122334455667788h ; dq 8字节
g_tVal dt 11223344556677889900h ; dt 10字节
data_seg ends

code_seg segment
START:
assume ds:data_seg ;ds:ip,此处指定下面g_btVal变量去data_seg找
mov ax, data_seg ;还需要重新指定ds的值,否则基地址错误
mov ds, ax
mov ax, seg g_wVal
mov ax, offset g_dwVal
code_seg ends
end START
1
2
3
;获取g_ary3的大小
mov ax, offset g_ary4
sub ax, offset g_ary3
堆栈

SS:存放栈的段地址;
SP:堆栈寄存器SP(stack pointer)存放栈的偏移地址;
BP: 基数指针寄存器BP(base pointer)是一个寄存器;

1
2
3
4
5
6
7
8
9
10
11
12
stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

push ax
push bx
push cx
push dx
pop ax
pop bx
pop cx
pop dx
中断
  • dos系统提供的功能(API),通过21号中断来调用
  • 每个功能都有一个编号,通过AH指定功能号
  • 每个功能的参数查看手册

INT指令,本指令将产生一个软中断,把控制转向一个类型号为n的软中断,该中断处理程序入口地址在中断向量表的n*4地 —- 址处的二个存储器字(4个单元)中.

1
2
3
4
API功能默认调用00:00的函数
-a
; 0000:0000 60 10 00 F0 0B 00 70 00-08 00 70 00 08 00 70 00
INT 00 ;表示调用 F000:1060
1
2
3
4
5
6
7
code_seg segment
START:
mov ah, 4ch
mov al, 00h
int
code_seg ends
end START ; -g或是.exe直接退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data_seg segment
g_szHello db "hello world$" ; db 字节
data_seg ends

code_seg segment
START:
assume ds:data_seg, ss:stack_seg
mov ax, data_seg
mov ds, ax

mov ah, 09
mov dx, offset g_szHello
int 21h
code_seg ends
end START ;-p参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.model small

.data
strs DB 'hello world',13,10,'$'
.code
start:
mov ax,@data
mov ds,ax
mov dx,offset strs
mov ah,09h
int 21h
mov ah,4ch
int 21h
end start

https://www.cnblogs.com/dgwblog/p/11865850.html

串操作

  • 源操作数使用si,默认段为DS(以此为基址),可段超越
  • 目的操作数使用di,默认段为ES(以此为基址),不可段超越
指令 功能 DF = 0(UP) DF = 1(DN)
movsb
(串传送)
1、mov string
2、将si地址的内容拷贝到di地址
3、(di) <- (si)
si <- si + 1
di <- di + 1
si <- si - 1
di <- di - 1
movsw 同 movsb si <- si + 2
di <- di + 2
si <- si - 2
di <- di - 2
stosb
(串存储)
1、store string
2、将al或者ax内存存储到di地址
3、(di) <- al或(di) <- ax
di <- di + 1
di <- di - 1
stosw 同 stosb di <- di + 2 di <- di - 2
lodsb
(串读取)
1、load string
2、将si地址内容读入al或者ax
3、al <- (si) 或 ax <- (si)
si <- si + 1 si <- si -1
lodsw 同 lodsb si <- si + 2 si <- si - 2
cmpsb
(串比较)
1、cmp string
2、si地址内容减去di地址内容,不存储结果,影响标志位
3、(si) - (di)
si <- si + 1
di <- di + 1
si <- si - 1
di <- di - 1
cmpsw 同 cmpsb si <- si + 2
di <- di + 2
si <- si - 2
di <- di - 2
scasb
(串扫描)
1、scan string
2、al或者ax减去di地址内容,不存结果,影响标志位
3、al - (di)或ax - (di) es:di
di <- di + 1 di <- di - 1
scasw 同 scasb di <- di + 2 di <- di - 2

movsb & movsw

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
data_seg segment
g_szSrc db "hello world", '$'
g_szDst db 64 dup(0)
data_seg ends

code_seg segment
START:
assume ds:data_seg
mov ax, data_seg ; 这三句是固定的基本上,不然会找错值
mov ds, ax

mov ax, data_seg ; 这三句是固定的基本上,不然会找错值
mov es, ax ; 因为di用的是es

lea si, g_szSrc
mov di, offset g_szDst ; 相对起始位置
;std -g 11
movsb ;movsw
movsb
movsb
movsb
movsb
code_seg ends
end START ;-p参数

;-t
;-d 0 l 20
;-t
;-d es l 20 可以把ds和es分开段写
;movsb si bi默认是加一,根据DF标志位加一减一
;STD DF<-1 自动减
;CLD DF<-0 自动加

0E24:0010 = 0E25:0000??

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
data_seg segment
g_szSrc db "hello world", '$'
data_seg ends

data_seg0 segment
g_szDst0 db 64 dup(0)
data_seg0 ends

code_seg segment
START:
assume ds:data_seg, es:data_seg0
mov ax, data_seg ; 这三句是固定的基本上,不然会找错值
mov ds, ax

mov ax, data_seg0 ; 这三句是固定的基本上,不然会找错值
mov es, ax ; 因为di用的是es

lea si, g_szSrc
mov di, offset g_szDst0 ; 相对起始位置
;std -g 11
movsb ;movsw
movsb
movsb
movsb
movsb
code_seg ends
end START ;-p参数

stosb & stosw

1
2
3
4
5
6
7
8
mov di, offset g_szDst
cld
mov ax, 0cccch
stowd
stowd
stowd
stowd
stowd

其他自行看

代码虚拟化、软件模拟硬件

重复前缀

串操作指令一般都配合重复前缀使用,实现内存的批量操作,

前缀 串操作 重复条件
rep 1、movs
2、stos
3、loads
cx != 0
repz / repe 1、cmps
2、scas
cx != 0


zf = 1
repnz / repne 1、cmps
2、scas
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
data_seg segment
g_szSrc db "hello world", '$'
g_szSrcEnd db ?
data_seg ends

buf_seg segment
g_szDst db 16 dup(0)
g_szDstEnd db ?
buf_seg ends

code_seg segment
START:
assume ds:data_seg
mov ax, data_seg ; 这三句是固定的基本上,不然会找错值
mov ds, ax

mov ax, buf_seg ; 这三句是固定的基本上,不然会找错值
mov es, ax ; 因为di用的是es

lea si, g_szSrc
lea di, g_szDst
cld
mov cx, offset g_szSrcEnd
sub cx, offset g_szSrc
rep movsb ;cx每次减一
code_seg ends
end START ;-p参数

流程转移指令

无条件跳转

直接跳转
名称 修饰关键词(可选) 格式 功能 指令长度 示例
短跳(单字) short jmp short 标号 ip <- 标号偏移 2 0005:EB0B
jmp 0012
近跳(双字) near ptr jmp near 标号 ip <- 标号偏移 3 0007:E90a01
jmp 0114
远跳(四字) far ptr jmp far ptr 标号
jmp 段名:标号
ip <- 标号偏移
cs <- 段地址
5 0000:EA00007c07
jmp 077C:0000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
code_seg0 segment
LABEL2:
mov cx, cx
code_seg0 ends
code_seg segment
START:
jmp far ptr LABEL2 ; jmp code_seg0:LABEL2
jmp short LEABEL0
mov ax, ax
LEABEL0:
jmp near ptr LEABEL1
db 256 dup(0)
mov bx, bx
LEABEL1:
mov ax, 4c00h
int 21h
code_seg ends
end START

Jmp下条指令+偏移

1
2
3
4
5
6
7
;计算机器码
;短跳0xEB,近跳0XE9
0AE7:0102 jmp 0120 ; 短跳是一个字节,两个十六进制 120-104 = EB1C,主要是计算下条指令的偏移
0AE7:0102 jmp 0320 ; 近跳是两个字节,四个十六进制 320-105 = E91B02,因为三个位可以表示?
0AE7:0102 jmp 0020 ; E91BFF,补码E4用单字(-128-127)存不下,要用近跳
0AE7:0402 jmp 0100
0AE7:0122 jmp 0110
无指令跳转

使用寄存器间接转移

  • 格式 jmp reg
  • reg为通用寄存器
  • 功能 ip<-reg
  • 只能用于段内转移
1
2
mov ax, offset LEABEL1
jmp ax

条件跳转

依据标志位判断,条件成立则跳转,条件不成立则不跳

单条件跳转
指令 英文单词 标志 说明
JZ/JE zero,equal ZF=1 相等/等于零
JNZ/JNE not zero,not qual ZF=0 不相等,不等于零
JCXZ CX is zero CX=0 cx为0
JS sign SF = 1 结果为负
JNS not sign SF = 0 结果为正
JP/JPE parity, parity even PF = 1 1为偶数个
JNP/JPO not parity, parity odd PF = 0 1为奇数个
JO overflow OF = 1 溢出
JNO not overlow OF = 0 不溢出
JC carry CF = 1 进位,小于
JNC not carry CF = 0 不进位,大于等于
1
2
3
4
5
6
7
8
FIND:
cmp byte ptr [si], '$'
je ENDFIND

inc si
jmp FIND
ENDFIND:
sub si, 1
无符号数判断
指令 英文 说明 标志
JB below 小于 cf = 1
JNAE not greater or equal 不大于等于 cf = 1
JAE above or equal 大于等于 cf =0
JNB not below 不小于 cf =0
JBE below or equal 小于等于 cf = 1 或 zf = 1
JNA not above 不大于 同 JBE
JA above 大于 cf =0 或 zf =0
JNBE not below or equal 不小于等于 同 JA
有符号数判断
指令 英文单词 说明 标志
JL less 小于 SF != OF
JNGE not greater or equal 不大于等于 SF != OF
JG greater 大于 SF = OF 且
ZF = 0
JNLE not less or equal 不小于等于 同 JG
JLE less or equal 小于等于 ZF != OF 或
ZF = 1
JNG not greator 不大于 同 JLE
JGE greator orequal 大于等于 SF = OF
JNL not less 不小于 同 JGE

LOOP

格式:loop标号

只能用于短转移

指令 重复条件
LOOP cx != 0
LOOPZ/LOOPE cx != 0 且 zf = 1
LOOPNZ/LOOPNE cx != 0 且 zf = 0

函数

基础语法

call - push 下一条指令地址
jmp 目标地址

ret n - 弹出返回地址
jmp到返回地址
add sp, 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
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
stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

data_seg segment
g_sz1 db "1hello world$"
g_sz2 db "2hello world$"
g_sz3 db "3hello world$"
g_sz4 db "4hello world$"
data_seg ends

code_seg segment
assume ds:data_seg, ss:stack_seg

PUTS0:
push bp
mov bp, sp
push dx
mov dx, [bp+4]
mov ah, 09h
int 21h

mov dl, 0ah
mov ah, 02h
int 21h

pop dx
pop bp
ret 2 ;sp+2,一个参数+2

PUTS:
push bp
mov bp, sp
sub sp, 8 ;申请局部变量空间

;保存寄存器环境
push dx

mov dx, [bp+4]
mov ah, 09h
int 21h

mov word ptr [bp-4], 5566h
mov word ptr [bp-8], 7788h ;多个函数调用要手动规划栈

mov dx, offset g_sz1
push dx
call PUTS0 ;多个函数调用要手动规划栈

mov dl, 0ah
mov ah, 02h
int 21h

;恢复寄存器环境
pop dx
add sp, 8 ;释放局部变量空间 mov sp, bp
pop bp ;pop就直接更新bp的值吗
ret 2 ;sp+2,一个参数+2


StrLen:
push bp
mov bp, sp

push si
push bx

mov bx, [bp+4] ;获取参数
mov si, bx
FIND:
cmp byte ptr [si], '$'
je ENDFIND
inc si
jmp FIND
ENDFIND:
sub si, bx
;ax为函数默认返回值,默认不用入栈平栈
mov ax, si

pop bx
pop si

mov sp, bp
pop bp
ret

START:
mov ax, data_seg
mov ds, ax

mov ax, offset g_sz1
push ax
call StrLen
add sp, 2 ;此处如果是 ret 2就可以不用在外面平

mov ax, offset g_sz2
push ax
call StrLen
add sp, 2

mov dx, offset g_sz1
push dx ;参数入栈
call PUTS ;返回地址入栈

mov dx, offset g_sz2
push dx
call PUTS

mov dx, offset g_sz3
push dx
call PUTS

mov dx, offset g_sz4
push dx
call PUTS

;程序退出,退出码0
mov ax, 4c00h
int 21h
code_seg ends
end START ;-p参数

bp用的是ss段,bx用的是dx段,栈是【局部变量】【bp】【返回地址】【入参】

函数执行流程

  • 参数入栈
  • 返回地址入栈,跳转到函数
  • 保存栈帧(就是栈内给某个上下文环境临时规划的局部栈)
  • 申请局部变量空间
  • 保存寄存器环境
  • 执行函数功能
  • 恢复寄存器环境
  • 恢复栈帧
  • 弹出返回地址,返回[平栈]
  • [平栈]
1
agv -> ret -> bp ->局部变量
指令(可选) 说明 功能
call (near ptr) 标号 段内直接调用 push 返回地址
jmp 标号
call REG
call near ptr | word ptr [EA]
段内间接调用 push 返回地址
jmp 函数地址
call far ptr 标号
call dword ptr [EA]
段间调用 push
ret(n) 段内返回 pop ip
add sp,n
retf(n) 段间返回 pop ip
pop cs
add sp, n

masm函数语法

自动算偏移,bp,sp入栈平栈

1
2
3
4
5
6
7
8
9
10
11
函数名 proc[距离][调用约定][uses reg1 reg2..][参数:word, 参数名:word..] ;uses保存寄存器环境
local 变量:word
local 变量: word
ret
函数名 endp

示例
TestProc PROC far stdcall uses bx dx si di arg1:word
local btVal:byte
ret
TestProc ENDP
距离关键字 说明
near 函数只能段内调用
函数使用ret返回
调用时ip入栈
far 段内段间都可调用
函数使用retf返回
调用时都是ip和cs入栈
调用约定关键词 说明
C 调用方平栈
stdcall 被调用方平栈
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
include tool.inc

stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

data_seg segment
g_wVa11 dw 5566h
g_wVa12 dw 7788
g_szFileName db "test.txt", 0
g_buf db 256 dup(0)
g_hFile dw 0
data_seg ends

code_seg segment
assume ds:data_seg, ss:stack_seg
START:
mov ax, data_seg
mov ds, ax

invoke OpenFile, offset g_szFileName
cmp ax,-1 ;打开文件成功返回句柄,不成功返回错误码
je ERROR
mov g_hFile, ax
invoke Readfile, g_hFile, offset g_buf, size g_buf
cmp ax,0
je ERROR
invoke CloseFile, g_hFile
ERROR:
;宏汇编
invoke MySub, 45, 13 ;使用立即数时,会用ax中转

mov si, 9
mov di, 8
invoke MySub, si, di

invoke MySub, g_wVa12, g_wVa11

mov ax, 45
push ax
mov ax, 13
push ax
call MySub
add sp, 4

;程序结束,退出码0
mov ax, 4C00H
INT 21H
code_seg ends
end START
类型 局部变量类型 备注
db byte 可以直接赋值使用
dw word 可以直接赋值使用
dd dword 不可以直接赋值使用
dq qword 不可以直接赋值使用
dt tbyte 不可以直接赋值使用

assume、invoke:没有对应的机器码,给编译器看的,伪指令

invoke
1
invoke 函数名, 参数1, 参数2, 参数3

说明:

  • 会生成参数入栈代码
  • 如果是c调用约定,会生成平栈代码
  • 如果是局部变量取地址,需要使用addr伪指令
  • 使用addr的时候,注意ax的使用
伪指令 说明
offset 取段内偏移
addr 取局部变量的地址,使用LEA指令

文件引用

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
;tool.asm
include tool.inc

code_seg segment

CloseFile proc far stdcall uses bx hFile:word
mov bx, hFile
mov ah, 3eh
int 21h
ret
CloseFile ENDP

ReadFile proc far stdcall uses bx dx cx hFile:word, pBuff:word, nBufSize:word
mov bx, hFile
mov ah, 3fh
mov dx, pBuff
mov cx, nBufSize
int 21H
jnc SUCESS
;失败
mov ax,0
ret
SUCESS:
ret
ReadFile ENDP

OpenFile proc far stdcall uses dx szFileName:word
mov ah, 3dh
mov al, 02h ;可读,可写
mov dx, szFileName
int 21H
jnc SUCESS
;失败
mov ax,-1
ret
SUCESS:
;成功
ret
OpenFile ENDP

MySub PROC far c uses cx di es nVal1:word, nVal2:word
local @btVar1:byte ;局部变量标识,非必须
local @wVa11:word
local @dwVal1:dword
local @buf[256]:byte

xor ax, ax
mov @btvar1,al
mov @wVal1,ax
;mov @dwval1,ax

;lea bx, @buf
invoke MyZeroMem,ss,addr @buf,255 ;add 取局部变量的地址,I专用于invoke
;去局部变量地址用 offset ?

mov ax, nVal1
mov ax, nVal2
ret
MySub ENDP
MyZeroMem PROC far stdcall uses di cx es pSecBase:word, pBuff:word, nSize:word
xor ax, ax
mov es, pSecBase
mov cx, nSize
mov di, pBuff
rep stosb
ret
MyZeroMem ENDP
code_seg ends
ends ;每个asm文件都要以ends结尾

声明头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
;tool.inc
MySub PROTO far c :word, :word
MyZeroMem PROTO far stdcall :word, :word, :word
;功能 - 创建文件,如果文件存在,则清空
;参数 -
; szFileName - 文件名,0结尾字符串
;返回 -
; 失败 - 返回 -1
; 成功-返回文件句柄
CreateFile PROTO far stdcall szFileName:word
ReadFile PROTO far stdcall hFile:Word, pBuff:word, nBufSize:word
CloseFile PROTO far stdcall hFile:word
OpenFile PROTO far stdcall szFileName:word

需要同时编译两个文件

1
2
ml /c test.asm tool.asm
link test.obj tool.obj

PS:

调试小技巧,int 3 / db 0cch,直接 -g 会go到中断处,-p执行当前语句(不进入)

MASM扩展在这个扩展的设置中,有一个名为Working mode。默认情况下,它被设置为单个文件。如果您更改为工作区,它将挂载整个dir。

宏汇编

表达式 $ org @@

表达式需要能够在编译阶段计算结果

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
stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

data_seg segment
g_dbVal0 db 11h
org 10h
g_dbVal1 db 22h
org 20h
g_dbVal3 db 33h

g_wBuf dw 256 dup(0) ;相当于100个word,200个字节
;g_wBufLen dw offset g_wBufLen - offset g_wBuf ;?
g_wBufLen dw $ - offset g_wBuf

org 400h ;下个段偏移位置从400开始
g_wVal0 dw 5566h
data_seg ends

code_seg segment
assume ds:data_seg, ss:stack_seg
START:
mov ax, data_seg
mov ds, ax

mov ax, g_wBufLen
mov dx, size g_wBuf / size word ;看值
mov ax, g_wVal0 / 2 ;报错,无法在编译阶段计算结果
mov ax, offset g_wVal0 - offset g_wBuf

mov ax, 1122h and 5566h ;运算符
mov ax, 3344h or 7788h
mov ax, not 0
and g_wVal0, 5566h ;指令

mov ax,6 1e 4 ;关系为假,结果为0
mov ax,7 gt 1 ;关系为真,结果为全F

@@:
xor ax, ax
@@:
mov ax, ax
mov bx,bx
jmp @f ;就近向下跳
mov si, si
jmp @b ;就近向上跳
@@:
mov cx, cx
mov dx, dx
@@:
xor bx,bx

mov ax, offset NEXT
mov ax, $+3
NEXT:
mov bx, $
mov ax, $
mov ax, $

;程序结束,退出码0
mov ax, 4C00H
INT 21H
code_seg ends
end START
结构体
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
stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

MyTestTag struc
m_bval db θ
m_wVal dw 0
m_buf db 10 dup (0)
MyTestTag ends

data_seg segment
g_tag MyTestTag <55h, 7788h, "hello struct!">
data_seg ends

code_seg segment
MyTest proc far stdcall pTag:ptr MyTestTag
assume si:ptr MyTestTag
mov si, pTag
mov ax, [si].m_wVal
assume si:nothing

mov bx, pTag
mov ax, word ptr [bx+2]

ret
MyTest endp
assume ds:data_seg, ss:stack_seg
START:
mov ax, data_seg
mov ds, ax

mov al, g_tag.m_bVal
mov ax, g_tag.m_wVal
lea ax, g_tag.m_buf

mov g_tag.m_bval, 66h
mov g_tag.m_wVal, 1234h

invoke MyTest, offset g_tag

; local @stu:Students ;结构体局部变量
; mov @stu.m_id, 6 ;使用结构体局部变量

;程序结束,退出码0
mov ax, 4C00H
INT 21H
code_seg ends
end START
equ语句
  • 不可以重命名
  • 可用于常量和表达式
  • 可用于字符串
  • 可用于指令名,给指令取别名
  • 可用于类型,给类型取别名
  • 可用于操作数
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
stack_seg segment stack ;栈会自动加载
db 256 dup(0cch) ;为栈申请大小
stack_seg ends

VERSION equ 10
VERSTR equ "hello wrold"
MYMOV equ mov
MYPUSHAX equ push ax

data_seg segment
g_wVal dw VERSION
g_buf db VERSTR
data_seg ends

code_seg segment
assume ds:data_seg, ss:stack_seg
START:
mov ax, data_seg
mov ds, ax

MYMOV ax, g_wVal
MYPUSHAX

;程序结束,退出码0
mov ax, 4C00H
INT 21H
code_seg ends
end START
=(只能整数的equ)
macro语句
1
2
3
4
5
6
7
MYADD MACRO argl, arg2
mov ax, arg1
add ax, arg2
ENDM

MYADD 4, 5
MYADD 7, 8

字符串拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shift macro n, reg, d
mov cl, n
ro&d reg, cl
endm

shift 2,ax,1
shift 3,bx,r
shift 1,cx,1

.if ax < θ
mov ax, ax
.elseif ax == 0
mov bx, bx
.else
mov cx, cx
.endif

汇编手册中文(asm+masm).chm

补充

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
;test.asm
include tool.inc

stack_seg segment stack ;栈会自动加载
db 500H dup(0cch) ;一个段(256)的栈
stack_seg ends

data_seg segment
g_buf db 256 dup(0)
g_wBufLen dw $ - offset g_buf
g_bufCnt db5 4 dup(0), 0ah, "$"
data_seg ends

code_seg segment
assume ds:data_seg, ss:stack_seg

GetwordsCount proc far stdcall uses bx pBuff:word
local @bIsPreSapce:byte
mov @bIsPreSapce, 0

mov ax, 1
mov bx, pBuff
;空字符串
.if byte ptr [bx] == "$"
xor ax, ax
ret
.endif

;非空字符串
.while byte ptr[bx] != "$"
.if byte ptr [bx] == " "
.if @bIsPreSapce == 0
inc ax
.endif
mov @bIsPreSapce, 1
else
mov @bIsPreSapce, 0
.endif
inc bx
.endw
ret
GetwordsCount endp

START:
mov ax, data_seg
mov ds, ax

xor cx, cx
.while cx < 5
;获取一行
invoke GetLine, offset g_buf, g_wBufLen

;获取此行的单词个数
invoke GetwordsCount, offset g_buf

;个数转字符串
invoke IntToStr, offset g_bufCnt, ax

;输出个数
invoke Puts, offset g_bufCnt

inc cx
.endw
;程序结束,退出码0
mov ax, 4C00H
INT 21H
code_seg ends
end START
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
;tool.asm
include tool.inc
data_seg segment
g_bufHexChar db "0123456789abcdef"
data_seg ends

code_seg segment

Puts proc far stdcall uses dx pBuff:word
mov dx, pBuff
mov ah, 09h
int 21h

mov dl, odh
mov ah, 02h
int 21H

ret
Puts endp

IntToStr proc far stdcall uses si bx ds cx pBuff:word, n:word
local @wCnt:word
local @wOldDs:word

assumeds:data_seg

mov @wOldDs, ds

mov @wCnt, θ
.while @wCnt < 4
mov si, n
and si, 0f000h
mov cl, 0ch
shr si, cl

mov ax, data_seg
mov ds, ax
mov bx, offset g_bufHexChar
mov al, [si + bx]

mov ds, @wOldDs
mov bx, pBuff
add bx, @wCnt
mov [bx], al

mov cl, 4
shl n, cl

inc @wCnt
.endw


GetChar proc far stdcall
mov ah, 01h
int 21H
ret
GetChar ENDP

GetLine proc far stdcall uses bx PBuff:word, nBufSize:word
mov bx,pBuff
invoke Getchar
.while al != 0dh
mov [bx],al
inc bx
.if bx >= nBufSize
.break
.endif
invoke Getchar
.endw
mov byte ptr[bx], '$'
mov ax, bx
sub ax, pBuff
ret
GetLine endp

code_seg ends
1
2
3
4
;tool.inc
GetLine proto far stdcall PBuff:word, nBufSize:word
IntToStr proto far stdcall pBuff:word, n:word
Puts proto far stdcall pBuff:word

w32Dasm

机器码是十六进制,winHex打开的默认就是机器码

  • 通过地址搜机器码

32ASM汇编

masm32.com

直接指定文件夹安装

把缺少的头文件路径添加在环境变量的include里

1
2
ml /c /coff test.asm
link /subsystem:windows test.obj
1
2
3
4
5
6
7
8
9
10
11
12
13
;测试代码
.386
.model flat, stdcall
option casemap:NONE
include windows.inc
include user32.inc
includelib user32.lib
.data
g_szTitle db "hello world", 0
.code
ENTRY:
invoke MessageBoxA, NULL, offset g_szTitle, NULL, MB_OK
end ENTRY

与16位ASM区别

文件头三件套

1
2
3
.386 ;指令集 Processor
.model flat, stdcall
option casemap:NONE ;标识符是否大小写敏感,none是敏感

msdn宏汇编官网

https://docs.microsoft.com/en-us/cpp/assembler/masm/directives-reference?view=msvc-160

32位 = 4G

分段

32位汇编取消了分段,改用内存属性来划分,称作节(section)、内存区或内存块。

可读 可写 可执行 备注
.DATA true true false 初始化的全局变量
.CONST true false false 只读数据区
.DATA? true true false 未初始化的全局变量
CODE true false true 代码
1
2
3
4
5
6
7
8
9
10
.data
g_dwVal dd θ
g_dwVal1 dd 0, 0, 1
.data?
g_dwVal2 dd ?
.const
g_dwVa13 dd 9
TestEntry:
mov ax, ax
end TestEntry

enter leave

寻址

32位没用中断,调用API

安装MASM

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
;编码要用GBK
.386
.model flat, stdcall
option casemap:none

include windows.inc
include user32.inc
include gdi32.inc
include kernel32.inc

includelib user32.lib
includelib gdi32.lib
includelib kernel32.lib

.data
g_szClassName db "Asmwindowclass", 0
g_szTitle db "32位汇编的第一个窗口", 0
g_szTip db "窗口创建失败", 0
.code

MainWndProc proc hWnd:HWND, nMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF nMsg == WM_DESTROY
invoke PostQuitMessage, 0
.ENDIF
invoke DefWindowProc, hWnd, nMsg, wParam, lParam
ret
MainWndProc endp

WinMain proc hInstance:HINSTANCE
local @wc:WNDCLASS
local @hwnd:HWND
local @msg:MSG

;设计注册窗口类
mov @wc.style, CS_HREDRAW or CS_VREDRAW
mov @wc.lpfnWndProc, offset MainWndProc
mov @wc.cbClsExtra, 0
mov @wc.cbWndExtra, 0
mov eax, hInstance
mov @wc.hInstance, eax

invoke LoadIcon, NULL, IDI_APPLICATION
mov @wc.hIcon, eax

invoke LoadCursor, NULL, IDC_ARROW
mov @wc.hCursor, eax

invoke GetStockObject, WHITE_BRUSH
mov @wc.hbrBackground, eax

mov @wc.lpszMenuName, NULL
mov @wc.lpszClassName, offset g_szClassName

invoke RegisterClass, addr @wc

;创建窗口
invoke CreateWindowEx, NULL, offset g_szClassName,offset g_szTitle, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,\
NULL, NULL, hInstance, NULL
mov @hwnd, eax
.if eax == NULL
invoke MessageBox, NULL, offset g_szTip, offset g_szTitle, MB_OK
ret
.endif

;显示窗口
invoke ShowWindow, @hwnd, SW_SHOW

;更新窗口
invoke UpdateWindow, @hwnd

;消息循环
.WHILE TRUE
invoke GetMessage, addr @msg, NULL, 0, 0
.IF eax == 0
.break
.ENDIF
invoke TranslateMessage, addr @msg
invoke DispatchMessage, addr @msg
.ENDW
;过程函数

ret
WinMain endp

Cr41Entry:
invoke GetModuleHandle, NULL
invoke WinMain, eax
invoke ExitProcess, 0
end Cr41Entry

OD的简易使用

  • 跳转指定地址,右键->跳转表达式 / ctrl+G / 回车 / 减号回退
  • 右键汇编,右选可以撤销
  • 右键快速返回栈顶
  • F2 断点
  • F7 单步步入;F8 单步步过;F9 运行;F12 暂停

删除OD/UDD文件夹下面的文件

资源和联合编译

C & 汇编的相互调用

VC + ml + link + rc

汇编调用C - OBJ

C调用汇编 - OBJ

建议编译成 DLL/lib 调用

查看 DLL 内部函数,Dependency walker

汇编在链接时可以导出 DLL 文件,但需要指定对应的 def 文件,不然 DLL 文件中没有函数,不会生成 lib 文件

1
link /subsystem:windows /Dll /Def:Asmdll.def asmdll.obj

内联汇编(c/c++写汇编)

内联汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;不能使用宏汇编,比如invoke,.if等等可以使用size,type、length
intnAry[10] = {};

__asm xor eax, eax
__asm xor ebx, ebx
__asm{
xor edi, edi
xor esi, esi
mov ebx, nRet
mov nRet, eax
mov nAry[0], eax
mov nAry[5], eax

push NULL
push NULL
push NULL
push NULL
call MessageBox
;invoke MessageBox,NULL,NULL,NULL,NULL
}
1
2
裸函数
__declspec(naked)用于生成裸函数,编译器不会为这个函数生成任何代码,需要自已添加函数进入和返回的代码__LOCAL_SIZE - 内置宏,让编译器计算局部变量的大小

补丁

定位窗口的过程函数

1、根据窗口相关的 API 去跟踪调用

2、OD自带功能,查看->窗口->刷新

3、OD自带功能,查看->可执行模块(导入函数)->(搜函数)

条件断点功能

编程窗口程序思路

1、手动 create 窗口的

2、调用 dlgbox

搜索什么关键词要看具体什么应用,用什么语言开发(windows桌面程序)

1、查看->可执行模块
2、选择对应exe,[winmine.exe]右键查看名称,搜索 RegisterClass(导入函数),右键查看参考(调用),打断点
3、在输入函数内搜索 DIGLOGBOX ,可手动,可右键全部调用处打断点
4、执行,根据栈的跳转查看 pWndClass 栈结构内存,查手册可知,(pWndClass)的第二个参数为过程函数,内存跳转到汇编,打上断点,标注为主窗口的过程函数
5、pWndClass和RegisterClassW的关系是函数内部调用吗,此处是pWndClass已经调用,RegisterClassW还没调用吗
5、执行到过程函数处,可先禁用断点运行后再生效
6、寻找默认过程函数,点击Close时触发

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
01001BC9   .  55            push    ebp                              ;  主函数默认过程函数
01001BCA . 8BEC mov ebp, esp
01001BCC . 83EC 40 sub esp, 40
01001BCF . 8B55 0C mov edx, dword ptr [ebp+C] ; 获取mMsg
01001BD2 . 8B4D 14 mov ecx, dword ptr [ebp+14]
01001BD5 . 53 push ebx
01001BD6 . 56 push esi
01001BD7 . 33DB xor ebx, ebx
01001BD9 . 57 push edi
01001BDA . BE 00020000 mov esi, 200
01001BDF . 43 inc ebx
01001BE0 . 33FF xor edi, edi
01001BE2 . 3BD6 cmp edx, esi ; mMsg跟状态码200比较 move
01001BE4 . 0F87 75030000 ja 01001F5F ; >200
01001BEA . 0F84 95040000 je 01002085 ; =200
01001BF0 . B8 00010000 mov eax, 100
01001BF5 . 3BD0 cmp edx, eax
01001BF7 . 0F87 5C010000 ja 01001D59
01001BFD . 0F84 97000000 je 01001C9A
01001C03 . 8BC2 mov eax, edx
01001C05 . 48 dec eax ; Switch (cases 2..47)
01001C06 . 48 dec eax
01001C07 . 74 76 je short 01001C7F
01001C09 . 83E8 04 sub eax, 4
01001C0C . 74 57 je short 01001C65
01001C0E . 83E8 09 sub eax, 9
01001C11 . 74 2B je short 01001C3E
01001C13 . 83E8 38 sub eax, 38
01001C16 . 0F85 742E0000 jnz 01004A90 ; 此处要走默认过程函数
1
2
3
4
5
6
7
8
9
10
01004A90   > \83FA 10       cmp     edx, 10                          ;  Default case of switch 01001C05
01004A93 .^ 0F85 10D7FFFF jnz 010021A9 ; 01001C16修改前默认跳转
01004A99 . 6A 01 push 1 ; /Style = MB_OKCANCEL|MB_APPLMODAL
01004A9B . 6A 00 push 0 ; |Title = NULL
01004A9D . 68 5A4A0001 push 01004A5A ; |Text = "是",B7,"裥枰顺?",A1,"?,A8,"?3?",此处在01004A5A 编码写中文提示,push 只是作为参数入栈,可数据窗口编辑或汇编编辑
01004AA2 . FF75 08 push dword ptr [ebp+8] ; |hOwner
01004AA5 . FF15 B8100001 call dword ptr [<&USER32.MessageBoxW>>; \MessageBoxW
01004AAB . 83F8 01 cmp eax, 1
01004AAE .^ 0F84 F5D6FFFF je 010021A9
01004AB4 .^ E9 02D7FFFF jmp 010021BB
1
2
是否需要退出?
\u662f\u5426\u9700\u8981\u9000\u51fa\uff1f

F9运行程序

1、右键->窗口->刷新,可直接跟踪 ClassProc,可直接下断点 111 ,这就是过程函数,消息断点

1
2
3
4
5
6
7
8
9
10
01004A90   > \83FA 10       cmp edx,0x10                             ;  Default case of switch 01001C05
01004A93 .^ 0F85 10D7FFFF jnz winmine3.010021A9
01004A99 . 6A 01 push 0x1 ; /Style = MB_OKCANCEL|MB_APPLMODAL
01004A9B . 6A 00 push 0x0 ; |Title = NULL
01004A9D . 68 5A4A0001 push winmine3.01004A5A ; |Text = "是否需要退出?£í?3?"
01004AA2 . FF75 08 push dword ptr ss:[ebp+0x8] ; |hOwner = NULL
01004AA5 . FF15 B8100001 call dword ptr ds:[<&USER32.MessageBoxW>>; \MessageBoxW
01004AAB . 83F8 01 cmp eax,0x1
01004AAE .^ 0F84 F5D6FFFF je winmine3.010021A9
01004AB4 .^ E9 02D7FFFF jmp winmine3.010021BB

激活码类型:
1、点击按钮获取指定弹框内容(bp GetDlgItemTextA、GetDlgItemTextW)
2、消息弹框(MessageBoxA、MessageBoxW)
执行到返回,跳过系统自带功能
直接搜索错误关键词,运行后 Ultra String Reference
查看对比String

点击验证,重点是获取点击事件,触发后续的逻辑

两个框校验key值:
1、直接搜索错误关键词,运行后 Ultra String Reference
2、F4跳到某地址
3、找到corrent前一条(00401642)

根据算法逻辑写注册机:
1、新建Win32 App

RadASM

重定位

重定位:重新定位代码的变量或地址

代码注入exe文件,申请一块内存

用汇编写一个执行文件,注入到扫描.exe里面,代码注入后地址会变化,要动态计算地址

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
.586
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc

includelib user32.lib
includelib kernel32.lib


WinMain proto :DWORD,:DWORD,:DWORD,:DWORD


.data
g_szWinMineCap db "扫雷", 0 ;这里是窗口名
g_szUser32 db "user32.dll", 0
g_szMessageBox db "MessageBoxA", 0
.code

CODE_BEG:
jmp MSG_CODE
g_szText db "我注入你了", 0
g_szCaption db "温情提示", 0
g_pfnMessageBox dd 0

MSG_CODE:
int 3

call NEXT ;把pop ebx指令的地址压栈
NEXT:
pop ebx ;pop ebx指令的地址弹栈入ebx
sub ebx, offset NEXT

push MB_OK

mov eax, offset g_szCaption
add eax, ebx ;计算变量g_szCaption在新内存中的地址
push eax

mov eax, offset g_szText
add eax, ebx ;计算变量e_szText在新内存中的地址
push eax

push NULL

mov eax, offset g_pfnMessageBox
add eax, ebx
call dword ptr [eax]

;call MessageBox
;invoke MessageBox, NULL, offset g_szText, offset g_szCaption, MB_OK

CODE_END:
g_dwCodeSize dd offset CODE_END - offset CODE_BEG

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL @hWndWinmine:HWND
LOCAL @dwProcId:DWORD
LOCAL @hProc:HANDLE
LOCAL @pBuff:LPVOID
LOCAL @dwBytesWrited:DWORD
LOCAL @hUser32:HMODULE
LOCAL @d01dProc:DWORD ;内存权限

invoke LoadLibrary, offset g_szUser32
mov @hUser32, eax

invoke VirtualProtect, offset g_pfnMessageBox, size g_pfnMessageBox, PAGE_EXECUTE_READWRITE, addr @d01dProc

invoke GetProcAddress, @hUser32, offset g_szMessageBox
mov g_pfnMessageBox, eax

invoke VirtualProtect, offset g_pfnMessageBox, size g_pfnMessageBox, @d01dProc, addr @d01dProc

invoke FindWindow, NULL, offset g_szWinMineCap
mov @hWndWinmine, eax

invoke GetWindowThreadProcessId, @hWndWinmine, addr @dwProcId
invoke OpenProcess, PROCESS_ALL_ACCESS, FALSE , @dwProcId
mov @hProc, eax

invoke VirtualAllocEx, @hProc, NULL, 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov @pBuff, eax

invoke WriteProcessMemory, @hProc, @pBuff, offset CODE_BEG, g_dwCodeSize, addr @dwBytesWrited

invoke CreateRemoteThread, @hProc, NULL, 0, @pBuff, NULL, NULL, NULL

xor eax, eax
ret
WinMain endp

; ---------------------------------------------------------------------------


start:
invoke WinMain, NULL,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

end start

代码注入器

汇编库:
oddisasm
xedparse(x64)
asmjit(1)

植物大战僵尸加方格

API hock

comexplorer

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
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive

include windows.inc
include kernel32.inc
include user32.inc

includelib kernel32.lib
includelib user32.lib

.data
g_szUser32 db "user32", 0
g_szMessageBoxA db "MessageBoxA", 0
g_szNewTitle db "这个标题被我占用了,哈哈", 0
g_pfnMessageBoxA dd 0
g_bIsSelfCall dd FALSE
;1、注入原生user.dll的messageboxA函数,注入后messagebox第一跳就会变成跳转指定地址,要修改的指令地址赋给ebx,再通过ptr修改指定地址的值
;2、
.code

;要跳转的地址
HOOKCODE:
.if g_bIsSelfCall == TRUE
jmp OLDCODE
.endif

;重入问题 api hook就是调用程序原有的API 再弹出原先窗口前再弹一个 钩子库??detours
mov g_bIsSelfCall, TRUE
invoke MessageBox, NULL, offset g_szUser32, offset g_szMessageBoxA, MB_OK
mov g_bIsSelfCall, FALSE

mov dword ptr [esp+0ch], offset g_szNewTitle

OLDCODE:
;跳回去
mov eax, g_pfnMessageBoxA
add eax, 5 ;下一条指令

;调用被破坏掉的代码
push ebp
mov ebp, esp

jmp eax

InstallHook proc uses ebx
LOCAL @hUser32:HMODULE
LOCAL @dw01dProc:DWORD

;int 3

;获取User32
invoke GetModuleHandle, offset g_szUser32
mov @hUser32, eax

;获取User32的messagebox
invoke GetProcAddress, @hUser32, offset g_szMessageBoxA
mov g_pfnMessageBoxA, eax

;计算跳转偏移
mov eax, offset HOOKCODE
sub eax, g_pfnMessageBoxA
sub eax, 5

push eax
invoke VirtualProtect,g_pfnMessageBoxA,1,PAGE_EXECUTE_READWRITE,addr @dw01dProc
pop eax

;修改跳转,怎么指定修改的指令的messagebox的第一行
mov ebx, g_pfnMessageBoxA ; 保存基址信息
mov byte ptr[ebx], 0e9h ; 0xE9是JMP机器码 后面的四个字节是偏移
mov dword ptr[ebx+1], eax ; e9后面的四个字节是一个偏移地址 偏移地址=目的地址-跳转基地址(jmp的下一条指令的地址)

invoke VirtualProtect,g_pfnMessageBoxA,1,@dw01dProc,addr @dw01dProc

ret
InstallHook endp

DllMain proc hinstDLL:HINSTANCE, fdwReason:DWORD, lpvReserved:LPVOID
.if fdwReason == DLL_PROCESS_ATTACH ;首次加载dll自动就执行installHook了
invoke InstallHook
.endif
mov eax, TRUE
ret
DllMain endp
end DllMain

; remotedll
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
user.dll里面默认messagebox的实现
760C15F0 USER32.MessageBoxA 8BFF mov edi, edi
760C15F2 55 push ebp
760C15F3 8BEC mov ebp, esp
760C15F5 833D 946C0E76 0>cmp dword ptr [760E6C94], 0
760C15FC 74 22 je short 760C1620
760C15FE 64:A1 18000000 mov eax, dword ptr fs:[18]
760C1604 BA BC710E76 mov edx, 760E71BC
760C1609 8B48 24 mov ecx, dword ptr [eax+24]
760C160C 33C0 xor eax, eax
760C160E F0:0FB10A lock cmpxchg dword ptr [edx], ecx
760C1612 85C0 test eax, eax
760C1614 75 0A jnz short 760C1620
760C1616 C705 006D0E76 0>mov dword ptr [760E6D00], 1
760C1620 6A FF push -1
760C1622 6A 00 push 0
760C1624 FF75 14 push dword ptr [ebp+14]
760C1627 FF75 10 push dword ptr [ebp+10]
760C162A FF75 0C push dword ptr [ebp+C]
760C162D FF75 08 push dword ptr [ebp+8]
760C1630 E8 3B020000 call MessageBoxTimeoutA
760C1635 5D pop ebp
760C1636 C2 1000 retn 10

;注入后变成
760C15F0 USER32.MessageBoxA - E9 0BFAF399 jmp 10001000
760C15F5 833D 946C0E76 0>cmp dword ptr [760E6C94], 0
760C15FC 74 22 je short 760C1620
760C15FE 64:A1 18000000 mov eax, dword ptr fs:[18]
760C1604 BA BC710E76 mov edx, 760E71BC
760C1609 8B48 24 mov ecx, dword ptr [eax+24]
760C160C 33C0 xor eax, eax
760C160E F0:0FB10A lock cmpxchg dword ptr [edx], ecx
760C1612 85C0 test eax, eax
760C1614 75 0A jnz short 760C1620
760C1616 C705 006D0E76 0>mov dword ptr [760E6D00], 1
760C1620 6A FF push -1
760C1622 6A 00 push 0
760C1624 FF75 14 push dword ptr [ebp+14]
760C1627 FF75 10 push dword ptr [ebp+10]
760C162A FF75 0C push dword ptr [ebp+C]
760C162D FF75 08 push dword ptr [ebp+8]
760C1630 E8 3B020000 call MessageBoxTimeoutA
760C1635 5D pop ebp
760C1636 C2 1000 retn 10


10001000 8BC0 mov eax, eax
10001002 C74424 0C 13300>mov dword ptr [esp+C], 10003013
1000100A A1 2C300010 mov eax, dword ptr [1000302C]
1000100F 83C0 05 add eax, 5
10001012 55 push ebp
10001013 8BEC mov ebp, esp
10001015 FFE0 jmp eax
10001017 55 push ebp
10001018 8BEC mov ebp, esp
1000101A 83C4 F8 add esp, -8
1000101D 53 push ebx
1000101E 68 00300010 push 10003000; ASCII "user32"
10001023 E8 72000000 call 1000109A; jmp 到 KERNEL32.GetModuleHandleA
10001028 8945 FC mov dword ptr [ebp-4], eax
1000102B 68 07300010 push 10003007; ASCII "MessageBoxA"
10001030 FF75 FC push dword ptr [ebp-4]
10001033 E8 68000000 call 100010A0; jmp 到 KERNEL32.GetProcAddress
10001038 A3 2C300010 mov dword ptr [1000302C], eax
1000103D B8 00100010 mov eax, 10001000
10001042 2B05 2C300010 sub eax, dword ptr [1000302C]; USER32.MessageBoxA
10001048 83E8 05 sub eax, 5
1000104B 50 push eax
1000104C 8D45 F8 lea eax, dword ptr [ebp-8]
1000104F 50 push eax
10001050 6A 40 push 40
10001052 6A 01 push 1
10001054 FF35 2C300010 push dword ptr [1000302C]; USER32.MessageBoxA
1000105A E8 47000000 call 100010A6; jmp 到 KERNEL32.VirtualProtect
1000105F 58 pop eax
10001060 8B1D 2C300010 mov ebx, dword ptr [1000302C]; USER32.MessageBoxA
10001066 C603 E9 mov byte ptr [ebx], 0E9
10001069 8943 01 mov dword ptr [ebx+1], eax
1000106C 8D45 F8 lea eax, dword ptr [ebp-8]
1000106F 50 push eax
10001070 FF75 F8 push dword ptr [ebp-8]
10001073 6A 01 push 1
10001075 FF35 2C300010 push dword ptr [1000302C]; USER32.MessageBoxA
1000107B E8 26000000 call 100010A6; jmp 到 KERNEL32.VirtualProtect
10001080 5B pop ebx
10001081 C9 leave
10001082 C3 retn

钢琴和筛选器异常

筛选器异常 / 最终异常

setUnhand

dump文件

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
.586
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc

includelib user32.lib
includelib kernel32.lib

.data
g_szMsg db "异常来了,是否跳过异常?", 0
g_szTxt db "没有发现CC断点", 0
g_szTip db "发现CC断点", 0
.code

MyUnhandledExceptionFilter proc pEP:ptr EXCEPTION_POINTERS
LOCAL @pEr:ptr EXCEPTION_RECORD
LOCAL @pCtx:ptr CONTEXT

mov ebx, pEP
assume ebx:ptr EXCEPTION_POINTERS
mov eax, [ebx].pExceptionRecord
mov @pEr, eax

mov eax, [ebx].ContextRecord
mov @pCtx, eax

mov ebx, @pEr
assume ebx:ptr EXCEPTION_RECORD

mov esi, @pCtx
assume esi:ptr CONTEXT

.if [ebx].ExceptionCode == EXCEPTION_ACCESS_VIOLATION
add [esi].regEip, 2
;设置TF标志位,继续执行
or [esi].regFlag,100h
.elseif [ebx].ExceptionCode == EXCEPTION_SINGLE_STEP
;'判断是否有CC
mov eax, [esi].regEip
;invoke MessageBox, NULL,addr byte ptr [eax] , NULL, MB_OK
;ret
.if byte ptr [eax] == 033h ;0cch
invoke MessageBox, NULL,offset g_szTip, NULL, MB_OK
mov eax, EXCEPTION_EXECUTE_HANDLER
ret
.endif

;没有到达代码结束位置
.if[esi].regEip != offset CODE_END
;没有发现CC,继续单步
or [esi].regFlag, 100h
.endif
.endif

assume esi:nothing
assume ebx:nothing


mov eax, EXCEPTION_CONTINUE_EXECUTION ;程序继续执行

ret
MyUnhandledExceptionFilter endp

start:
;28
invoke SetUnhandledExceptionFilter, offset MyUnhandledExceptionFilter

xor eax, 8
mov eax, [eax]

xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax

CODE_END:
invoke MessageBox, NULL, offset g_szTxt, NULL, MB_OK
invoke ExitProcess,eax

end start

总结

exe程序
1、修改程序自身汇编代码(加个弹窗提示)
2、根据程序的算法逻辑写注册机(编写ASM程序)
3、编写汇编直接注入对应程序(代码/工具),汇编库就是写C程序调用汇编对应API
4、编写DLL(汇编),用DLL注入工具

windbg

一般断点,把打断点处的第一个字节替换成 cc

代码同上

TF标志位置1断点

异常错误码

配置环境变量

1
2
_NT_SYMBOL_PATH
srv*C:\symbolslocal*https://msdl.microsoft.com/download/symbols

1、源码调试(汇编源码/C语言源码)必须有pdb文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bl 查看断点
bc 3-255 取消断点
bp user32!CreateWindowExA
bm user32!Create*A
bu 延迟断点,未加载的DLL函数
$exentry 程序入口点

teb thread environment block(结构体)
dt _teb ;名称粉碎
dt 00338000 _teb

dt _PEB

!teb
!peb

sehf

结构化异常处理

1
2
3
异常处理:
-> 调试器 -> SEH -> 调试器 -> 筛选器 ->系统
SEH在筛选器前处理
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
.586
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc

includelib user32.lib
includelib kernel32.lib

Node struct
next dd 0
handler dd 0
Node ends

.data
g_sz0 db " 0 异常来了", 0
g_sz1 db "1 异常来了", 0
.code

Handler1 proc pER:ptr EXCEPTION_RECORD, pEstablisherFrame:DWORD, pCtx:ptr CONTEXT, pDispatcherContext:DWORD
mov ebx, pER
assume ebx:ptr EXCEPTION_RECORD

mov esi,pCtx
assume esi:ptr CONTEXT

invoke MessageBox,NULL,offset g_sz1, NULL,MB_OK

.if [ebx].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO
add[esi].regEip,2
.elseif[ebx].ExceptionCode == EXCEPTION_ACCESS_VIOLATION
mov eax,ExceptionContinueSearch
ret
.endif

assume ebx:nothing
assume esi:nothing

mov eax, ExceptionContinueExecution
ret
Handler1 endp

Func1 proc
LOCAL @node:Node

mov @node.handler, offset Handler1

;安装SEH
assume fs:nothing
mov eax,fs:[0]
mov @node.next,eax

lea eax,@node
mov fs:[0], eax

xor esi, esi
div esi ;赋0异常

xor eax,eax
mov [eax],eax ;内存访问异常

;卸掉SEH
mov eax, @node.next
mov fs:[0], eax

ret
Func1 endp



Handler0 proc pER:ptr EXCEPTION_RECORD, pEstablisherFrame:DWORD, pCtx:ptr CONTEXT, pDispatcherContext:DWORD
mov ebx, pER
assume ebx:ptr EXCEPTION_RECORD

mov esi,pCtx
assume esi:ptr CONTEXT

invoke MessageBox,NULL, offset g_sz0,NULL,MB_OK

.if [ebx].ExceptionCode == EXCEPTION_ACCESS_VIOLATION
add [esi].regEip,2
.endif

assume ebx:nothing
assume esi:nothing

mov eax,ExceptionContinueExecution
;mov eax,ExceptionContinueSearch:交给调试器或者筛选器处理
ret
Handler0 endp

Func0 proc
LOCAL @node:Node

mov @node.handler, offset Handler0

;安装SEH
assume fs:nothing
mov eax, fs:[0]
mov @node.next, eax

lea eax, @node
mov fs:[0],eax ;安装异常,安装在这个函数

invoke Func1

xor eax,eax
mov [eax],eax ;内存访问异常

;卸掉SEH
mov eax, @node.next
mov fs:[0], eax

ret
Func0 endp

start:

invoke Func0
invoke MessageBox,NULL,NULL,NULL,MB_OK

xor eax, eax
invoke ExitProcess,eax
end start

RadASM 工程选项
-> 编译 /Zi
-> 链接 /debug /pdb:”TestSEH.pdb”

生成pdb能在调试过程中更清楚地看函数

OD插件

OD插件都是DLL文件,创建动态链接库
比如:OD开启时加载插件DLL,修改某个异常判断的地址,从而修改异常处理机制
修改异常派发
OD插件都是DLL文件,创建动态链接库

异常处理插件

1
2
3
4
5
6
kernelbase -> UnhandledExceptionFilter 
(UnhandledExceptionFilter)
76F722A0
找到异常处理分支,查找跳,不抛给调试工具处理,返回给程序本身处理
(关键跳)
76F7235D /0F84 E6000000 je 76F72449
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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "Plugin.h"

#pragma comment(lib,"ollydbg.lib")

int ODBG_Plugindata(char* shortname)
{
strcpy_s(shortname, 31, "od测试插件");
return PLUGIN_VERSION;
}

int ODBG_Plugininit(int ollydbgversion, HWND hw, ulong* features)
{
return 0;
}

int ODBG_Paused(int reason, t_reg* reg)
{
if (reason == PP_EVENT)
{
//获取unhandle的地址
//字符集问题,项目->项目名->高级->字符集
HMODULE hKernel = GetModuleHandle("kernelbase.dll");
LPBYTE pAddr = (LPBYTE)GetProcAddress(hKernel, "UnhandledExceptionFilter");
//定位跳转
//76F7235D - 76F722A0 + 1
pAddr += 0xBE;
BYTE btCode = 0x84;
Writememory(&btCode, (ulong)pAddr, sizeof(btCode), MM_SILENT);
}
return 0;
}

/*
*
//修改内存属性需要申请权限
DWORD dwOldProC = 0;
VirtualProtect(pAddr, 1, PAGE_EXECUTE_READWRITE, &dwOldProC);
//jnz修改为jz
//调试问题,项目->项目名->调试->命令->ODexe
*pAddr = 0x84;
*
*/
//改错进程,改成OD自己的地址,要改被调试程序
//还要注意调用时机,选合适的回调(触发)函数
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

PS:右键分析代码

进程跟踪地址错误

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "Plugin.h"
#pragma comment(lib,"ollydbg.lib")

DWORD
WINAPI
MyGetClassLongA(_In_ HWND hWnd,
_In_ int nIndex)
{
if (IsWindowUnicode(hWnd))
{
return GetClassLongW(hWnd, nIndex);
}
else
{
return GetClassLongA(hWnd, nIndex);
}
}

// 01.42

int ODBG_Plugindata(char* shortname)
{
strcpy_s(shortname, 31, "od测试插件2");
return PLUGIN_VERSION;
}

int ODBG_Plugininit(int ollydbgversion, HWND hw, ulong* features)
{
DWORD dwOldProc = 0;
LPDWORD pAddrToGetCL = (LPDWORD)0x0050D858;
VirtualProtect(pAddrToGetCL,sizeof(DWORD),PAGE_EXECUTE_READWRITE, &dwOldProc);

*pAddrToGetCL = (DWORD)MyGetClassLongA;

VirtualProtect(pAddrToGetCL, sizeof(DWORD),dwOldProc,&dwOldProc);
return 0;

return 0;
}

int ODBG_Paused(int reason, t_reg* reg)
{

return 0;
}

调试框架

为什么写汇编代码调用的是 win32的 API?(win32是桌面应用程序的底层接口吗)

事件驱动,消息响应

1、建立调试会话
CreateProcess
DebugActiveProcess
2、循环接受调试事件
waitForDebugEvent
3、处理调试事件
4、提交处理结果
ContinueDebugEvent

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
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive

include DBG.inc

.data
g_szExePath db "winmine.exe", 0
g_szEXCEPTION_DEBUG_EVENT db "EXCEPTION_DEBUG_EVENT", 0dh,0ah,0
g_szCREATE_THREAD_DEBUG_EVENT db "CREATE_THREAD_DEBUG_EVENT",0dh,0ah,0
g_szCREATE_PROCESS_DEBUG_EVENT db "CREATE_PROCESS_DEBUG_EVENT",0dh,0ah,0
g_szEXIT_THREAD_DEBUG_EVENT db "EXIT_THREAD_DEBUG_EVENT",0dh,0ah,0
g_szEXIT_PROCESS_DEBUG_EVENT db "EXCEPTION_DEBUG_EVENT",0dh,0ah,0
g_szLOAD_DLL_DEBUG_EVENT db "LOAD_DLL_DEBUG_EVENTO", 0dh,0ah,0
g_szUNLOAD_DLL_DEBUG_EVENT db "UNLOAD_DLL _DEBUG_EVENT",0dh,0ah,0
g_szOUTPUT_DEBUG_STRING_EVENT db "OUTPUT_DEBUG_STRING_EVENT",0dh,0ah,0

g_szFmtCreateProcess db "BaseOfImage:%08X, StartAddress:%08X", 0dh,0ah,"ImageName:%s",0dh, 0ah, 0
g_szFmtLoadDlls db "Base0fDll:%08X, ImageName:%s",0dh,0ah,0
g_szFmtLoadDllx db "Base0fDll:%08X, ImageName:%08X",0dh,0ah,0
.code

OnLoadDll proc pDE:ptr DEBUG_EVENT
LOCAL @dwAddrOfName:DWORD
LOCAL @hProcess:HANDLE
LOCAL @dwPid:DWORD
LOCAL @dwBytesReaded:DWORD
LOCAL @szwBuf[MAX_PATH]:word
LOCAL @szBuf[MAX_PATH]:byte

mov esi, pDE
assume esi:ptr DEBUG_EVENT
mov eax,[esi].dwProcessId
mov @dwPid, eax

lea esi,[esi].u.LoadDll
assume esi:ptr LOAD_DLL_DEBUG_INFO

invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,@dwPid
.if eax == NULL
mov eax,DBG_CONTINUE
ret
.endif
mov @hProcess, eax

invoke ReadProcessMemory, @hProcess,[esi]. lpImageName, addr @dwAddrOfName,sizeof @dwAddrOfName,addr @dwBytesReaded
.if eax == FALSE
invoke crt_printf, offset g_szFmtLoadDllx, [esi].lpBaseOfDll, [esi].lpImageName
mov eax,DBG_CONTINUE
ret
.endif

.if [esi].fUnicode
invoke ReadProcessMemory, @hProcess, @dwAddrOfName, addr @szwBuf, MAX_PATH, addr @dwBytesReaded
invoke WideCharToMultiByte, CP_ACP,0, addr @szwBuf, MAX_PATH, addr @szBuf,MAX_PATH, NULL, NULL
.else
invoke ReadProcessMemory,@hProcess, @dwAddrOfName, addr @szBuf, MAX_PATH, addr @dwBytesReaded
.endif

invoke crt_printf, offset g_szFmtLoadDlls, [esi].lpBaseOfDll, addr @szBuf

assume esi:nothing

mov eax,DBG_CONTINUE

ret
OnLoadDll endp

OnCreateProcess proc pDE:ptr DEBUG_EVENT
mov esi, pDE
assume esi:ptr DEBUG_EVENT
lea esi,[esi].u.CreateProcessInfo
assume esi:ptr CREATE_PROCESS_DEBUG_INFO

invoke crt_printf, offset g_szCREATE_PROCESS_DEBUG_EVENT
invoke crt_printf,offset g_szFmtCreateProcess,[esi].lpBaseOfImage,[esi].lpStartAddress,[esi].lpImageName

assume esi:nothing

mov eax, DBG_CONTINUE
ret
OnCreateProcess endp

main proc
LOCAL @si:STARTUPINFO
LOCAL @pi:PROCESS_INFORMATION
LOCAL @de:DEBUG_EVENT
LOCAL @dwContinueStatus:DWORD

invoke RtlZeroMemory,addr @si,sizeof @si
invoke RtlZeroMemory,addr @pi,sizeof @pi

mov @si. cb, sizeof @si

mov @dwContinueStatus, DBG_CONTINUE

;建立调试会话
invoke CreateProcess,offset g_szExePath, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr @si, addr @pi
.if eax == FALSE
ret
.endif

.while TRUE
invoke WaitForDebugEvent,addr @de,INFINITE
.if eax == FALSE
.continue
.endif

.if @de.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
invoke crt_printf,offset g_szEXCEPTION_DEBUG_EVENT


.elseif @de.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
invoke crt_printf, offset g_szCREATE_THREAD_DEBUG_EVENT

.elseif @de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
invoke OnCreateProcess, addr @de
mov @dwContinueStatus, eax

.elseif @de.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT
invoke crt_printf, offset g_szEXIT_THREAD_DEBUG_EVENT

.elseif @de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
invoke crt_printf, offset g_szEXIT_PROCESS_DEBUG_EVENT

.elseif @de.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT
;invoke crt_printf, offset g_szLOAD_DLL_DEBUG_EVENT
invoke OnLoadDll, addr @de
mov @dwContinueStatus, eax

.elseif @de.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT
invoke crt_printf, offset g_szUNLOAD_DLL_DEBUG_EVENT

.elseif @de.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT
invoke crt_printf, offset g_szOUTPUT_DEBUG_STRING_EVENT
.endif
;没这行就GG
invoke ContinueDebugEvent, @de.dwProcessId, @de.dwThreadId, @dwContinueStatus
.endw
xor eax, eax
ret

main endp

start:
invoke main
xor eax,eax
invoke ExitProcess,0
end start

PE

1
2
3
#修改对齐值
ml /c /coff pe.asm
link /merge:.rdata=.text /align:16 /subsystem:windows user32.lib kernel32.lib pe.obj
1
2
3
4
5
6
7
8
/*
IMAGE_DOS_HEADER
IMAGE_NT_HEADERS 32位真正头的偏移
IMAGE_FILE_HEADER
IMAGE_OPTIONAL_HEADER
IMAGE_DATA_DIRECTORY[1]
IMAGE_SECTION_HEADER[1] 文件映射 选项头的地址 + 选项头的大小 = 节表的位置
*/

其他都是16位残留

30 word + long = 64字节

IMAGE_SECTION_HEADER文件映射 选项头的地址 + 选项头的大小 = 节表的位置

  • 从文件偏移(位置),映射数据(大小),到内存地址,在内存中所占大小
  • 节表位置:NT的选项头之后,选项头的地址+选项头大小。就是.text之类的
  • 节大小:两行半=16*2+8,union大小为最大元素
  • 文件对齐和内存对齐不一致导致加载后地址偏移

winhex查看exe,od查看内存对比

规律:节与节是连续的

要求:PointerToRawData 和 sizeofRawData 都是跟 FileAlign 对齐,virtualAddress是跟 SectionAlign 对齐

sizeofImage:各节内存大小+PE头内存大小;最后一个节的virtualAddress+最后一个节的内存大小

地址转换

VA - virtual Address,内存中的虚拟地址,绝对地址

RVA - relative virtual address,相对虚拟地址,相对于模块基址的偏移

FA - file address,文件偏移,文件中所在的地址

virtualsize virtualAddress SizeofRawData PointerToRawData
0x28 0x1000 0x200 0x400
0x92 0x2000 0x200 0x600
0x10 0x3000 0x200 0x800
0x10 0x4000 0x200 0xa00

00402145(VA) –> 2145(RVA) –> 145 + 600 –> 745(FA)

812(FA) –> 12 + 3000 -> 3012 + 00400000 –> 00403012(VA)

程序中的绝对地址映射到内存中的地址

修改winhex的745地址值,OD里的内存中00402145的改变

virtualsize virtualAddress SizeofRawData PointerToRawData
0xBA 0x210 0xC0 0x210
0x10 0x2D0 0x10 0x2D0

从文件偏移 0x2D0 处复制 0x10 个字节的数据到内存 0x2D0处,占0x10

00400000 + 210 = 00400210

00400000 + 2D0 = 004002D0

dump文件的编写

PE头相同,把内存和文件偏移的赋值位置调换

dump是读取内存数据?运行时从文件内映射到内存,dump是从内存映射到文件?

默认还是按照从文件中拿数据映射到内存,把映射地址调换,exe运行机制又没改变?
运行程序,再用winhex打开,看此时的运行内存,即文件偏移复制到内存中的地址
把复制在内存中的节数据拷贝到新的exe

  • 生成dump文件可能会因为全局变量初始化问题出问题
  • dump未初始化的文件

解答:

  • 映射:按照节表的内容,将PE文件按照节表信息和节数据依次映射进入内存。
  • DUMP:将内存中的数据按节表数据将目标数据拷贝出来成为一个PE.exe文件
  • dump的先决条件在OEP处dump,此时全局变量里的值未被初始化,不会存在全局变量的访问异常。

调试器默认都有dump功能

脱壳用dump

节表注入

添加节

  1. 节表添加一项
  2. 添加节数据
  3. 节表个数增1
  4. 修改SizeOfImage

扩展最后一个节

  1. 修改节表
  2. 添加节数据
  3. 修改SizeOfImage

CFF工具可以辅助

导入表

IMAGE_DATA_DIRECTORY

  • CFF Explorer,自带地址转换
  • Dependency Walker
  • DLL文件占20个字节,1行+4
  • 节表往上8行就是导入表
  • PE往下7行半就是导出表、导入表
  • 导入表20个字节

415c - 4000 + 5000 = 515c

https://www.cnblogs.com/iBinary/p/9740757.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) 指向IAT结构注释表明了
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间戳.
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)

DWORD ForwarderChain; // -1 if no forwarders 导入名称?
DWORD Name;               //指向DLL名字的 RVA
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) 导入地址?
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

可选头后的导入表(地址RVA、大小)跳转导入列表 IMAGE_IMPORT_DESCRIPTOR,每个导入表五个字节。

重点关注第一个(OriginalFirstThunk/INT)、第四个(Name)、最后一个(FirstThunk/IAT)

  • 如果高位位1,说明是序号导入,低WORD 位导入函数的序号值。
  • 如果最高位为 0,说明是名字导入,该DWORD 指向结构体 IMAGE_IMPORT_BY_NAME
1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

IAT 和 INT都指向 IMAGE_THUNK_DATA32,分别指向 Ordinal 和 AddressOfData,它们再指向IMAGE_IMPORT_BY_NAME

1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //编译器决定,不是空的话,就是函数在导出表中的 函数地址表的导出索引.
CHAR Name[1]; //函数名称,0结尾.
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

PE加载到进程之前

PE加载到进程之后

415C(RVA) - 1000 + 400 = 355c(FOA)

4484(RVA) - 1000 + 400 = 3884(FOA)

直接内存里面看

PEload

https://www.52pojie.cn//thread-1077397-1-1.html

导出表

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 不加红的不重要
DWORD TimeDateStamp; //时间戳. 编译的时间. 把秒转为时间.可以知道这个DLL是什么时候编译出来的.
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;           //指向该导出表文件名的字符串,也就是这个DLL的名称 辅助信息.修改不影响 存储的RVA 如果想在文件中查看.自己计算一下FOA即可.
DWORD Base;           // 导出函数的起始序号
DWORD NumberOfFunctions; //所有的导出函数的个数
DWORD NumberOfNames; //以名字导出的函数的个数
DWORD AddressOfFunctions; // 导出的函数地址的 地址表 RVA 也就是 函数地址表
DWORD AddressOfNames; // 导出的函数名称表的 RVA 也就是 函数名称表
DWORD AddressOfNameOrdinals; // 导出函数序号表的RVA 也就是 函数序号表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

https://www.cnblogs.com/iBinary/p/9739031.html

https://bbs.kanxue.com/thread-269947.htm

https://github.com/DonaldTrump0/LordPE/releases/tag/0.2Soblesky.2

基地重定向表

IMAGE_BASE_RELOCATION

分页地址,页面偏移 -8/2=word的个数

API模拟:把dll的接口复制在新内存,调用新内存里的接口

TLS表

thread local storage 线程局部存储

显示TLS

  • 线程 API

隐式TLS

  • 变量

资源表

函数转发

Import REConstructor