汇编语言调试
寄存器
在动态调试中需知道各寄存器意义及作用
寄存器分为两类,通用寄存器和特殊寄存器。
通用寄存器
- EAX(寄存器寄存器-累加器):最常用于算术,逻辑和数据传输指令、乘法和除法运算使用此寄存器。对于Windows API函数,函数的返回值通常将存储在EAX寄存器中。
- EBX(基址寄存器):EBX寄存器可以直接访问存储器数据,它也是一个通用寄存器。
- ECX(计数寄存器):ECX是一个共享寄存器,可以用作各种命令的计数器。它还可能包含内存中的数据未对齐。使用计数器的命令是顺序,循环和LOOP / LOOPD指令。
1 | //LOOP/LOOPD |
具体模式如下网址
[实模式、保护模式、三种地址、分段、分页 - 克拉默与矩阵 - 博客园 (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 | 1、状态标志(Status Flags) |
1 | 2、DF标志(DF flag) |
1 | 3、系统标志以及IOPL域(System Flags and IOPL Field) |
- 段寄存器
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 | .text:00401005 public start |
public start-用于声明一个函数,其是公用的(可被其他函数调用),其他函数也可类似声明。
start proc near-表示函数开头,此处start表明了是函数入口,对其他的函数来说这个”start”可以换成对应的函数名。
start endp-表示函数结尾,对其他的函数来说这个”start”可以换成对应的函数名。
jmp-jmp sub_401010-运行结果如下
只修改了EIP值相当于指向了第一个函数
1 | .text:00401010 sub_401010 proc near ; CODE XREF: start↑j |
当前ip指向push 0,ida常以”;”作注释,搜索后发现其应该是
MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)的第一个参数。
push-push 0结果如下
执行此命令后
变为了
将0压入了栈内相当于将栈顶提高了一位(4字节 32bit)
剩下的push都是类似的变化
此处应该都是对MessageBox传参
定义了弹窗类型,传递了字符串的指针,也定义了按钮和图标
这几个push执行后栈内也多了两个字符串的地址和两个数字0
栈顶也被抬高了4位,EIP也向后移动了15
1 | .text:0040101E call MessageBoxA |
。
call-此处call函数将
call函数的下一个指令.text:00401023 push 0 的地址放入了栈中
ESP也因此抬高了一位,EIP指向了所call的函数MessageBoxA的函数头
dword ptr n 就是ESP + n处栈中存取的数据
同时也定义了对应参数在栈内的位置
如dword ptr 4 就是ESP + 4 = 0019FF60 + 4 = 0019FF64
正对应了压入栈中的参数0
其他参数获取原理类似。
offset-__imp_MessageBoxA dd offset user32_MessageBoxA
offset 指令相当于把对应函数“user32_MessageBoxA”的指针(指向函数的头部)给了这个”__imp_MessageBoxA“让其有了等效的作用
所以在上文的jmp中直接jmp到了user32_MessageBoxA这个函数中。
1 | user32.dll:772519E0 user32_MessageBoxA: |
mov edi, edi
mov是将后项覆盖给前项,但此处自己覆盖自己是不会改变的
此处这么做的目的在网上搜了一下:
1.为了实现hot-patching技术,即运行时修改一个函数的行为。
2.为了提高效率。执行一条MOV指令比执行两条NOP指令花费更少的时间。
其他的mov指令也是类似的覆盖
push ebp
原来ebp如下图
将EBP的所存的
压入了栈中,抬高了一位ESP
同时EBP中所存的不会因push而被抛弃掉
mov ebp, esp将esp指向的栈顶位置赋给ebp
原ebp
赋值后的ebp
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
所以此处ZF,CF值没有变化
jz short loc_77251A10
根据ZF为1就跳转,ZF为0就不跳转
此处在执行cmp dword_77276C94, 0后ZF为1
其会跳转(将EIP移向)short loc_77251A10
1 | user32.dll:77251A10 loc_77251A10: ; CODE XREF: user32.dll:user32_MessageBoxA+C↑j |
前面的push为一如既往的传参操作
call也是调用对应的弹窗函数,可见前文有相对应的。
pop ebp
将栈顶的弹出给ebp
原栈顶和ebp:
执行后:
原栈顶的数据并不会被清除只是将栈顶下移了一位
ebp也变为了原栈顶所对应的数据
retn 10h
原ESP和EIP及原栈顶
执行后
相当于将原栈顶的数据弹出给了EIP使得ESP+4h
又让ESP+10h(retn后面所跟的数值)
所以retn n=
1 | pop eip |
strcmp.exe
1 | .text:00401005 public start |
jmp sub_40101C
EIP 00401005(start函数头)->0040101C(sub_40101C函数头)
1 | sub_40101C proc near ; CODE XREF: start↑j |
push offset aAbcdabcd
ESP 0019FF74->0019FF70
EIP 0040101C(sub_40101C)->00401021
栈顶:
变为了
下面的push也类似
ESP 0019FF70->0019FF6C
EIP 00401021->00401026
栈顶变为
call sub_401032
EIP 00401026->00401032(sub_401032的函数头部)
ESP 0019FF6C->0019FF68
栈顶变为
存放了原call函数所在的EIP 00401026加上该call函数所占存储位5变为了0040102B
所call的函数如下
1 | .text:00401032 sub_401032 proc near ; CODE XREF: .text:0040100A↑j |
arg_0= dword ptr 8=ESP+8
arg_4= dword ptr 0Ch=ESP+12
push ebp
ESP 0019FF68->0019FF64
EIP 00401032->00401033
将EBP中所存的压入了栈中
mov ebp, esp
将esp中存放的移给ebp(不改变栈顶)
push eax
push edx
push esi
push edi
将其都压入了栈中
mov esi, [ebp+arg_0]
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
1 | .text:0040103F loc_40103F: ; CODE XREF: sub_401032+22↓j |
mov al, [esi]
al是ax的低八位,EAX又在ax的基础上扩展了16位
相当于修改了eax的低八位
此处方括号相当于将esi所指向的解引用后传给al,因为esi指向的是”abcdABCD”解引用后是a其ascii码为61所以
EAX的低八位变为了61
mov dl, [edi]
edi指向的是”abcdabcd”
也是同理
edx变为了
cmp al, 0
al目前值为61
CMP结果 | ZF | CF |
---|---|---|
目的操作数 < 源操作数 | 0 | 1 |
目的操作数 > 源操作数 | 0 | 0 |
目的操作数 = 源操作数 | 1 | 0 |
所以原ZF=1,CF=0
变为了
jnz short loc_40104E
条件跳转指令在ZF=0情况下跳转 其他类似jmp,只是将eip移向了后面的标号处
1 | .text:0040104E loc_40104E: ; CODE XREF: sub_401032+13↑j |
cmp道理同上
al=dl
zf=1
pushf
将EFLAGS中的放入了栈中(保护其不受改变)
其16进制表达如下
inc esi
inc edi
相当于将ESI,EDI都向后移动一位
popf
因为算数时也会影响到EFLAGS数据所以将在栈中的EFLAGS数据又覆盖回EFLAGS寄存器起到备份作用
jz short loc_40103F
此处相当于如果al=dl就返回原函数继续比较相当于该字符串的该字符相等就继续比较下一个字符
然后cmp al, 0 的操作是验证是否到了字符串末尾,
直到不相等(ZF=0)或到达末尾(ZF=1,因比较的函数如果不跳转回去其下一个函数就是用来退出的函数loc_401056)时
1 | .text:00401056 loc_401056: ; CODE XREF: sub_401032+1A↑j |
这几个pop都类似
变为了
![image-20221225162934661](C:\Users\wrwrw\AppData\Roaming\Typora\typora-user-images\image-20221225162934661.png)
leave
观察得出EBP,ESP和栈顶发生了变化
执行指令后
所以leave相当于
1 | mov esp, ebp |
retn 8
执行后eip变为了原栈顶所指向的函数
ESP也增加了8+4
所以retn 8=
1 | pop eip |
retn后eip指向的位置是
1 | text:0040102B push 0 ; uExitCode |
push 0相当于传参数给了要call的ExitProcess函数
此处ExitProcess是退出的函数相当于直接退出程序
strcpy.exe
1 | .text:00401032 sub_401032 proc near ; CODE XREF: .text:0040100A↑j |
cld指令
通过搜索发现,其会更改DF为0值
因为原本DF就为0,所以我在运行时并未发现变化,此指令应该是用来访问字符串的
注:DF决定了内存访问的方向(DF=0,向高地址增加)(DF=1,向地地址减小)
rep movsb
其更改了ecx,esi和edi的值
ECX 4->0
通过搜索发现
movsb是移动一字节从ESI往EDI(不更改原数据,同时使ESI+1 EDI+1)
rep movsb
rep就是重复的意思上面的指令就是重复移动,通过对ECX赋值来确定移动的次数
strlen.exe
字符串长度的主要代码部分
将字符串每个字符与\0比较,不为0就让edi,eax(用于存储长度)自增1,直到为0时就停止
递归求和.exe
dec ecx
使ecx自减了1
递归求和的主要代码部分:
相当于从ECX的值一直加到0,从大到小,用EAX作为累加器,直到ECX=0时停止
字符串大写.exe
1 | loc_401035: ; CODE XREF: sub_40102D+1A↓j |
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 进行许可。