汇编语言调试

runwu2204 Lv6

寄存器

在动态调试中需知道各寄存器意义及作用

寄存器分为两类,通用寄存器和特殊寄存器。

通用寄存器

  • EAX(寄存器寄存器-累加器):最常用于算术,逻辑和数据传输指令、乘法和除法运算使用此寄存器。对于Windows API函数,函数的返回值通常将存储在EAX寄存器中。
  • EBX(基址寄存器):EBX寄存器可以直接访问存储器数据,它也是一个通用寄存器。
  • ECX(计数寄存器):ECX是一个共享寄存器,可以用作各种命令的计数器。它还可能包含内存中的数据未对齐。使用计数器的命令是顺序,循环和LOOP / LOOPD指令。
1
2
3
4
5
6
7
8
//LOOP/LOOPD
//格式如下
LOOP 标号()
//可以实现函数的循环,
//具体如下
ECX=ECX-1
(ECX)<>0//就转移至所跟的标号处执行,相当于又把EIP移到标号处
ECX=0//就将EIP移向下一个指令(跟指令正常执行一样)

image-20221224210257432

具体模式如下网址

[实模式、保护模式、三种地址、分段、分页 - 克拉默与矩阵 - 博客园 (cnblogs.com)](https://www.cnblogs.com/kelamoyujuzhen/p/10555924.html#:~:text=实模式(real mode),也称为实地址模式(real address mode),是所有x86兼容CPU下的一种操作模式。 实模式的特点是20 bit分段内存(segmented memory)地址空间(精确到1,MB的可寻址内存)以及 对所有可寻址内存,I%2FO地址和外设硬件的无限制直接软件访问。 实模式不支持内存保护(memory protection),多任务处理(multitasking)或 代码权限级别(code privilege levels)。)

可能以后要用先在这写着

  • EDX(数据寄存器):是一个通用寄存器,用于包含乘法结果或除法结果的一部分。它还可以直接访问内存中的数据地址。
  • EDI(目标索引):EDI通常用于处理字符串或数组的工作。该寄存器将指向目标字符串。此外它也是一个通用寄存器。
  • ESI(源索引):与EDI一样,ESI也经常用于处理字符串或数组的操作。该寄存器将指向源字符串。
  • EBP(基本指针):EBP指向内存位置,除了被共享外,还用作访问函数堆栈中的参数和局部变量的帧指针。
  • ESP(堆栈指针):该寄存器始终指向当前堆栈顶部。根据堆栈的工作原理,该寄存器将被定向到低位地址。

特殊寄存器

  • EIP(指令指针):这是一个特殊的寄存器,它始终指向要执行的下一条指令。与其他寄存器不同,EIP不受指令直接影响。

  • EFLAGS(32位时,如果为64位程序,则高32保留低32位跟32时的一致)(标志寄存器),每个位都是用来反映操作的特定状态,根据计算结果启用这些标志寄存器,并根据这些标志来执行程序的执行分支。

1
该寄存器第1,3,5,15,22~31位被保留(总是设定为指定的值)(可能是为以后扩展所用)

标志寄存器(未保留的位)可分为三部分

1
2
3
4
5
6
7
8
9
10
11
1、状态标志(Status Flags)
EFLAGS寄存器的状态标志(0、2、4、6、7以及11位)指示算术指令(如ADD, SUB, MUL以及DIV指令)的结果,这些状态标志的作用如下:

CF(bit 0) [Carry flag] 若算术操作产生的结果在最高有效位(most-significant bit)发生进位或借位则将其置1,反之清零。这个标志指示无符号整型运算的溢出状态,这个标志同样在多倍精度运算(multiple-precision arithmetic)中使用。
PF(bit 2) [Parity flag] 如果结果的最低有效字节(least-significant byte)包含偶数个1位则该位置1,否则清零。
AF(bit 4) [Adjust flag] 如果算术操作在结果的第3位发生进位或借位则将该标志置1,否则清零。这个标志在BCD(binary-code decimal)算术运算中被使用。
ZF(bit 6) [Zero flag] 若结果为0则将其置1,反之清零。
SF(bit 7) [Sign flag] 该标志被设置为有符号整型的最高有效位。(0指示结果为正,反之则为负)
OF(bit 11) [Overflow flag] 如果整型结果是较大的正数或较小的负数,并且无法匹配目的操作数时将该位置1,反之清零。这个标志为带符号整型运算指示溢出状态。

在这些状态标志中,只有CF标志能够通过使用STC, CLC以及CMC指令被直接修改,或者通过位指令(BT, BTS, BTR以及BTC)将指定的位拷贝至CF标志中。
1
2
2、DF标志(DF flag)
这个方向标志(位于EFLAGS寄存器的第10位)控制串指令(MOVS, CMPS, SCAS, LODS以及STOS)。设置DF标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志则使得串指令自动递增。STD以及CLD指令分别用于设置以及清除DF标志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
3、系统标志以及IOPL域(System Flags and IOPL Field)

EFLAGS寄存器中的这部分标志用于控制操作系统或是执行操作,它们不允许被应用程序所修改。这些标志的作用如下:

TF(bit 8) [Trap flag] 将该位设置为1以允许单步调试模式,清零则禁用该模式。
IF(bit 9) [Interrupt enable flag] 该标志用于控制处理器对可屏蔽中断请求(maskable interrupt requests)的响应。置1以响应可屏蔽中断,反之则禁止可屏蔽中断。
IOPL(bits 12 and 13) [I/O privilege level field] 指示当前运行任务的I/O特权级(I/O privilege level),正在运行任务的当前特权级(CPL)必须小于或等于I/O特权级才能允许访问I/O地址空间。这个域只能在CPL为0时才能通过POPF以及IRET指令修改。
NT(bit 14) [Nested task flag] 这个标志控制中断链和被调用任务。若当前任务与前一个执行任务相关则置1,反之则清零。
RF(bit 16) [Resume flag] 控制处理器对调试异常的响应。
VM(bit 17) [Virtual-8086 mode flag] 置1以允许虚拟8086模式,清除则返回保护模式。
AC(bit 18) [Alignment check flag] 该标志以及在CR0寄存器中的AM位置1时将允许内存引用的对齐检查,以上两个标志中至少有一个被清零则禁用对齐检查。
VIF(bit 19) [Virtual interrupt flag] 该标志是IF标志的虚拟镜像(Virtual image),与VIP标志结合起来使用。使用这个标志以及VIP标志,并设置CR4控制寄存器中的VME标志就可以允许虚拟模式扩展(virtual mode extensions)
VIP(bit 20) [Virtual interrupt pending flag] 该位置1以指示一个中断正在被挂起,当没有中断挂起时该位清零。【Software sets and clears this flag; the processor only reads it.】与VIF标志结合使用。
ID(bit 21) [Identification flag] 程序能够设置或清除这个标志指示了处理器对CPUID指令的支持。
  • 段寄存器

CS-指向用于存放指令的段,与IP(偏移量结合),得到下一次要执行的指令

SS-指向用于堆栈的段,SP指向栈顶,同时可用BP访问整个栈

DS-指向数据段,与偏移量结合可以指向对应的数据

ES-指向附加段

汇编指令

public start-用于声明一个函数,其是公用的(可被其他函数调用),其他函数也可类似声明。

start proc near-表示函数开头,此处start表明了是函数入口,对其他的函数来说这个”start”可以换成对应的函数名。

start endp-表示函数结尾,对其他的函数来说这个”start”可以换成对应的函数名。

jmp-无条件跳转(移动eip)至后面的标号,格式为 jmp 标号

push-压栈,常用于传参,保存数据

pop-跟pop相对,将栈顶弹出

call-将call指令的下一个指令压入栈中,同时移动eip至标号处 call 标号

offset-相当于取地址

retn-与call搭配使用先将压入栈中的指令取回放入eip中,同时将esp+n 格式为retn n

inc-将后面对应的寄存器自增1,会引起SF值,ZF值的修改,具体见上面ZF,SF的作用

add-将后项加前项赋给前项,格式 add a,b

dec-将后面对应的寄存器自减1,会引起SF值,ZF值的修改,具体见上面ZF,SF的作用

rep-重复一条指令,每执行一次ECX-1,ECX=0时停止

loop/LOOPD-重复指令块,每执行一次ECX-1,ECX=0时停止

汇编调试

HeloWorld.exe

1
2
3
4
.text:00401005 public start
.text:00401005 start proc near
.text:00401005 jmp sub_401010
.text:00401005 start endp

public start-用于声明一个函数,其是公用的(可被其他函数调用),其他函数也可类似声明。

start proc near-表示函数开头,此处start表明了是函数入口,对其他的函数来说这个”start”可以换成对应的函数名。

start endp-表示函数结尾,对其他的函数来说这个”start”可以换成对应的函数名。

jmp-jmp sub_401010-运行结果如下

image-20221224221201527

image-20221224221142009

只修改了EIP值相当于指向了第一个函数

1
2
3
4
5
6
7
8
9
.text:00401010 sub_401010 proc near                    ; CODE XREF: start↑j
.text:00401010 push 0 ; uType
.text:00401012 push offset Caption ; "D0g3!"
.text:00401017 push offset Text ; "Welcome to Re"
.text:0040101C push 0 ; hWnd
.text:0040101E call MessageBoxA
.text:00401023 push 0 ; uExitCode
.text:00401025 call ExitProcess
.text:00401025 sub_401010 endp

image-20221224222915631

image-20221224222931903

当前ip指向push 0,ida常以”;”作注释,搜索后发现其应该是

MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)的第一个参数。

push-push 0结果如下

执行此命令后

image-20221224223515193

变为了

image-20221224223545415

将0压入了栈内相当于将栈顶提高了一位(4字节 32bit)

image-20221224223722949

剩下的push都是类似的变化

此处应该都是对MessageBox传参

image-20221224223829476

定义了弹窗类型,传递了字符串的指针,也定义了按钮和图标

image-20221224223927998

这几个push执行后栈内也多了两个字符串的地址和两个数字0

image-20221224224025205

栈顶也被抬高了4位,EIP也向后移动了15

1
2
3
4
.text:0040101E call    MessageBoxA
.text:00401023 push 0 ; uExitCode
.text:00401025 call ExitProcess
.text:00401025 sub_401010 endp

call-此处call函数将

call函数的下一个指令.text:00401023 push 0 的地址放入了栈中

image-20221224232526961

image-20221224232540977

ESP也因此抬高了一位,EIP指向了所call的函数MessageBoxA的函数头

image-20221224234025573

dword ptr n 就是ESP + n处栈中存取的数据

同时也定义了对应参数在栈内的位置

如dword ptr 4 就是ESP + 4 = 0019FF60 + 4 = 0019FF64

正对应了压入栈中的参数0image-20221224234349159

其他参数获取原理类似。

offset-__imp_MessageBoxA dd offset user32_MessageBoxA

image-20221225000500211

offset 指令相当于把对应函数“user32_MessageBoxA”的指针(指向函数的头部)给了这个”__imp_MessageBoxA“让其有了等效的作用

所以在上文的jmp中直接jmp到了user32_MessageBoxA这个函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user32.dll:772519E0 user32_MessageBoxA:
user32.dll:772519E0 mov edi, edi
user32.dll:772519E2 push ebp
user32.dll:772519E3 mov ebp, esp
user32.dll:772519E5 cmp dword_77276C94, 0
user32.dll:772519EC jz short loc_77251A10
user32.dll:772519EE mov eax, large fs:18h
user32.dll:772519F4 mov edx, offset unk_772771A4
user32.dll:772519F9 mov ecx, [eax+24h]
user32.dll:772519FC xor eax, eax
user32.dll:772519FE lock cmpxchg [edx], ecx
user32.dll:77251A02 test eax, eax
user32.dll:77251A04 jnz short loc_77251A10
user32.dll:77251A06 mov dword_77276D00, 1

mov edi, edi

mov是将后项覆盖给前项,但此处自己覆盖自己是不会改变的

此处这么做的目的在网上搜了一下:

1.为了实现hot-patching技术,即运行时修改一个函数的行为。

2.为了提高效率。执行一条MOV指令比执行两条NOP指令花费更少的时间。

其他的mov指令也是类似的覆盖

push ebp

原来ebp如下图

将EBP的所存的

image-20221225001444463

压入了栈中,抬高了一位ESP

image-20221225001732773

同时EBP中所存的不会因push而被抛弃掉

mov ebp, esp将esp指向的栈顶位置赋给ebp

原ebp

image-20221225002625179

赋值后的ebp

image-20221225002651473

cmp dword_77276C94, 0

将dword_77276c94(0)与0比较

CMP结果 ZF CF
目的操作数 < 源操作数 0 1
目的操作数 > 源操作数 0 0
目的操作数 = 源操作数 1 0

因为相等 所以ZF=1,CF=0但初始状态下ZF=1,CF=0

image-20221225003354313

所以此处ZF,CF值没有变化

jz short loc_77251A10

根据ZF为1就跳转,ZF为0就不跳转

此处在执行cmp dword_77276C94, 0后ZF为1

其会跳转(将EIP移向)short loc_77251A10

1
2
3
4
5
6
7
8
9
10
11
user32.dll:77251A10 loc_77251A10:                           ; CODE XREF: user32.dll:user32_MessageBoxA+C↑j
user32.dll:77251A10 ; user32.dll:user32_MessageBoxA+24↑j
user32.dll:77251A10 push 0FFFFFFFFh
user32.dll:77251A12 push 0
user32.dll:77251A14 push dword ptr [ebp+14h]
user32.dll:77251A17 push dword ptr [ebp+10h]
user32.dll:77251A1A push dword ptr [ebp+0Ch]
user32.dll:77251A1D push dword ptr [ebp+8]
user32.dll:77251A20 call near ptr user32_MessageBoxTimeoutA
user32.dll:77251A25 pop ebp
user32.dll:77251A26 retn 10h

前面的push为一如既往的传参操作

call也是调用对应的弹窗函数,可见前文有相对应的。

pop ebp

将栈顶的弹出给ebp

原栈顶和ebp:

image-20221225004823350

image-20221225004829852

执行后:

原栈顶的数据并不会被清除只是将栈顶下移了一位

image-20221225005011800

ebp也变为了原栈顶所对应的数据

image-20221225005018288

retn 10h

原ESP和EIP及原栈顶

image-20221225005136127

image-20221225005143222

执行后

image-20221225005232923

相当于将原栈顶的数据弹出给了EIP使得ESP+4h

又让ESP+10h(retn后面所跟的数值)

所以retn n=

1
2
pop eip
add esp 0xn

strcmp.exe

1
2
3
4
.text:00401005 public start
.text:00401005 start proc near
.text:00401005 jmp sub_40101C
.text:00401005 start endp

jmp sub_40101C

EIP 00401005(start函数头)->0040101C(sub_40101C函数头)

1
2
3
4
5
6
7
sub_40101C proc near                    ; CODE XREF: start↑j
.text:0040101C push offset aAbcdabcd ; "abcdabcd"
.text:00401021 push offset aAbcdabcd_0 ; "abcdABCD"
.text:00401026 call sub_401032
.text:0040102B push 0 ; uExitCode
.text:0040102D call ExitProcess
.text:0040102D sub_40101C endp

push offset aAbcdabcd

ESP 0019FF74->0019FF70

EIP 0040101C(sub_40101C)->00401021

栈顶:

image-20221225141731899

变为了

image-20221225142145042

下面的push也类似

ESP 0019FF70->0019FF6C

EIP 00401021->00401026

栈顶变为

image-20221225142305984

call sub_401032

EIP 00401026->00401032(sub_401032的函数头部)

ESP 0019FF6C->0019FF68

栈顶变为

image-20221225143653981

存放了原call函数所在的EIP 00401026加上该call函数所占存储位5变为了0040102B

所call的函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.text:00401032 sub_401032 proc near                    ; CODE XREF: .text:0040100A↑j
.text:00401032 ; sub_40101C+A↑p
.text:00401032
.text:00401032 arg_0= dword ptr 8
.text:00401032 arg_4= dword ptr 0Ch
.text:00401032
.text:00401032 push ebp
.text:00401033 mov ebp, esp
.text:00401035 push eax
.text:00401036 push edx
.text:00401037 push esi
.text:00401038 push edi
.text:00401039 mov esi, [ebp+arg_0]
.text:0040103C mov edi, [ebp+arg_4]
.text:0040103F

arg_0= dword ptr 8=ESP+8
arg_4= dword ptr 0Ch=ESP+12

push ebp

ESP 0019FF68->0019FF64

EIP 00401032->00401033

将EBP中所存的压入了栈中

image-20221225150217063

mov ebp, esp

image-20221225150501034

将esp中存放的移给ebp(不改变栈顶)

image-20221225150540065

push eax

push edx

push esi

push edi

image-20221225151006067

image-20221225151012367

image-20221225151019532

image-20221225151026351

将其都压入了栈中

image-20221225151057476

mov esi, [ebp+arg_0]

image-20221225151019532

ESI变为了

EBP+8(arg_0= dword ptr 8此处定义了arg_0的偏移量)所指向的数据

EBP为 0019FF64

其加8就对应了

0019FF6C 00404000 .data:aAbcdabcd_0

下面的

mov edi, [ebp+arg_4]

也是同理将edi变为了

0019FF70 00404009 .data:aAbcdabcd

image-20221225152236920

1
2
3
4
5
6
7
8
.text:0040103F loc_40103F:                             ; CODE XREF: sub_401032+22↓j
.text:0040103F mov al, [esi]
.text:00401041 mov dl, [edi]
.text:00401043 cmp al, 0
.text:00401045 jnz short loc_40104E
.text:00401047 cmp dl, 0
.text:0040104A jnz short loc_40104E
.text:0040104C jmp short loc_401056

mov al, [esi]

al是ax的低八位,EAX又在ax的基础上扩展了16位

相当于修改了eax的低八位

image-20221225154057893

image-20221225154109739

image-20221225154208364

此处方括号相当于将esi所指向的解引用后传给al,因为esi指向的是”abcdABCD”解引用后是a其ascii码为61所以

EAX的低八位变为了61

image-20221225155113585

mov dl, [edi]

edi指向的是”abcdabcd”

也是同理

image-20221225155201148

edx变为了

image-20221225155213193

cmp al, 0

al目前值为61

CMP结果 ZF CF
目的操作数 < 源操作数 0 1
目的操作数 > 源操作数 0 0
目的操作数 = 源操作数 1 0

所以原ZF=1,CF=0

变为了

image-20221225155525194

jnz short loc_40104E

条件跳转指令在ZF=0情况下跳转 其他类似jmp,只是将eip移向了后面的标号处

1
2
3
4
5
6
7
8
.text:0040104E loc_40104E:                             ; CODE XREF: sub_401032+13↑j
.text:0040104E ; sub_401032+18↑j
.text:0040104E cmp al, dl
.text:00401050 pushf
.text:00401051 inc esi
.text:00401052 inc edi
.text:00401053 popf
.text:00401054 jz short loc_40103F

cmp道理同上

al=dl

zf=1

pushf

将EFLAGS中的放入了栈中(保护其不受改变)

其16进制表达如下

image-20221225161040239

inc esi

inc edi

image-20221225161125997

image-20221225161135824

相当于将ESI,EDI都向后移动一位

popf

因为算数时也会影响到EFLAGS数据所以将在栈中的EFLAGS数据又覆盖回EFLAGS寄存器起到备份作用

jz short loc_40103F

此处相当于如果al=dl就返回原函数继续比较相当于该字符串的该字符相等就继续比较下一个字符

然后cmp al, 0 的操作是验证是否到了字符串末尾,

直到不相等(ZF=0)或到达末尾(ZF=1,因比较的函数如果不跳转回去其下一个函数就是用来退出的函数loc_401056)时

1
2
3
4
5
6
7
8
.text:00401056 loc_401056:                             ; CODE XREF: sub_401032+1A↑j
.text:00401056 pop edi
.text:00401057 pop esi
.text:00401058 pop edx
.text:00401059 pop eax
.text:0040105A leave
.text:0040105B retn 8
.text:0040105B sub_401032 endp

这几个pop都类似

image-20221225162850053

image-20221225162901007

image-20221225162907372

变为了

image-20221225162926939

image-20221225162934661

image-20221225162950621

leave

观察得出EBP,ESP和栈顶发生了变化

image-20221225163417850

image-20221225163434471

执行指令后

image-20221225163501075

image-20221225163511164

所以leave相当于

1
2
mov esp, ebp
pop ebp

retn 8

image-20221225164933264

image-20221225164949756

执行后eip变为了原栈顶所指向的函数

image-20221225165012100

ESP也增加了8+4

所以retn 8=

1
2
pop eip
add esp, 8

retn后eip指向的位置是

1
2
text:0040102B push    0                               ; uExitCode
.text:0040102D call ExitProcess

push 0相当于传参数给了要call的ExitProcess函数

此处ExitProcess是退出的函数相当于直接退出程序

strcpy.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
.text:00401032 sub_401032 proc near                    ; CODE XREF: .text:0040100A↑j
.text:00401032 ; sub_40101C+A↑p
.text:00401032
.text:00401032 arg_0= dword ptr 8
.text:00401032 arg_4= dword ptr 0Ch
.text:00401032
.text:00401032 push ebp
.text:00401033 mov ebp, esp
.text:00401035 push eax
.text:00401036 push ecx
.text:00401037 push esi
.text:00401038 push edi
.text:00401039 mov ecx, 4
.text:0040103E mov esi, [ebp+arg_0]
.text:00401041 mov edi, [ebp+arg_4]
.text:00401044 cld
.text:00401045 rep movsb
.text:00401047 pop edi
.text:00401048 pop esi
.text:00401049 pop ecx
.text:0040104A pop eax
.text:0040104B leave
.text:0040104C retn 8
.text:0040104C sub_401032 endp

cld指令

通过搜索发现,其会更改DF为0值

因为原本DF就为0,所以我在运行时并未发现变化,此指令应该是用来访问字符串的

注:DF决定了内存访问的方向(DF=0,向高地址增加)(DF=1,向地地址减小)

rep movsb

其更改了ecx,esi和edi的值

image-20221225171012597

image-20221225171026472

ECX 4->0

通过搜索发现

movsb是移动一字节从ESI往EDI(不更改原数据,同时使ESI+1 EDI+1)

rep movsb

rep就是重复的意思上面的指令就是重复移动,通过对ECX赋值来确定移动的次数

strlen.exe

字符串长度的主要代码部分

image-20221225175311965

将字符串每个字符与\0比较,不为0就让edi,eax(用于存储长度)自增1,直到为0时就停止

递归求和.exe

dec ecx

image-20221225173710614

image-20221225173721298

使ecx自减了1

递归求和的主要代码部分:

image-20221225175016173

相当于从ECX的值一直加到0,从大到小,用EAX作为累加器,直到ECX=0时停止

字符串大写.exe

1
2
3
4
5
6
7
8
9
loc_401035:                             ; CODE XREF: sub_40102D+1A↓j
.text:00401035 mov al, [esi]
.text:00401037 cmp al, 0
.text:00401039 jz short loc_401049
.text:0040103B cmp al, 61h ; 'a'
.text:0040103D jb short loc_401046
.text:0040103F cmp al, 7Ah ; 'z'
.text:00401041 ja short loc_401046
.text:00401043 and byte ptr [esi], 0DFh

jz相等就跳转,jb小于就跳转,ja大于就跳转

等于0就代表字符串结束就不用更改大小写直接可以进入程序的结尾

而符合a~z则就需要更改大小写

  • 标题: 汇编语言调试
  • 作者: runwu2204
  • 创建于 : 2023-01-05 17:05:19
  • 更新于 : 2023-01-05 17:05:19
  • 链接: https://runwu2204.github.io/2023/01/05/Re/实验室报告/汇编语言调试/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论