XCHG#
xchg A,B #指令用于将A和B的值互换
将鼠标放置到0x4013d8
,这里有个xor eax,eax
指令,此处演示修改0x4013d8
地址出的指令,点击菜单栏Edit-Patch program-Assemble
。
弹出如下窗口。
输入需要修改的指令,即可修改
xchg eax,esi
写入了新的指令后,原先的函数被打断了。
将0xc0
改写为NOP
,NOP(NO OPERATION)
指令什么也不做,没有任何操作,修改后内容如下:
上述例子中,只涉及了一个字节,原先的数据类型是 db,值为0xc0h
。
但修改后,此时函数处于打断状态,可以在函数起始地址 0x4013d8 右键Create function(创建函数)
。
函数创建完成后,内容如下:
IDA 将前置的loc_(表示普通指令)
改为sub_(表示函数起点)
。现在它已经重新识别为一个函数,按空格键切换到图形视图。
再重新关注这里的 XCHG 指令。假设EAX = 12345678,ESI = 55
, 那么指令执行之后 EAX = 55 而 ESI = 12345678。
在PATCH
菜单上有一个PATCHED BYTES
功能,能够显示所有修改的字节,同时也可以将修改取消。
xchg 指令也可以对一个寄存器的值和另一个寄存器指向的内存存储的值进行交换。
如指令:xchg eax,[esi]
如果 eax=55,esi=0x10000,程序会查找 0x10000 存储的值,如果地址有写入权限的话,就存入 55,原先 0x10000 的值写入 eax。
4.3 栈操作指令#
Stack(栈)
是由于函数运行而临时占用的内存区域,内存存取模式是先进后出(FILO,FIRST IN LAST OUT)
,通过这种方式来存储和读取数据。
Stack(栈)
的特点就是,最晚入栈的帧最早出栈(因为最内层的函数调用,最先结束运行),这就叫做后进先出
的数据结构。
对于栈的数据操作有 2 个基本的操作命令
-
PUSH 将一个对象保存在栈的顶部 (压栈)
-
POP 将最后存入栈顶的对象取出。
-
在任何时刻,只能对栈的顶部或者最后一个压栈的对象进行操作。
POP 操作将最后存入栈顶的对象取出,然后这个对象下方 (倒数第二次压栈操作存入) 的对象变成最后一次压栈操作存入的对象,能够进行后续操作。
Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配。比如,内存区域的结束地址是 0x00001000 ,第一帧假定是 4 字节,那么下一次分配的地址就会从 0x00000FFC 开始;如上面的图所示 。
push#
通常在 32 位程序中,PUSH 用于在调用函数之前向栈上传递参数,例如在上图0x40104f
处的指令。PUSH 64
指令将 64 (dword) 压入栈顶,PUSH EAX
将 EAX 的值压栈,存储在之前 64 (dword) 的上方,现在 EAX 的值在栈的最顶部。
PUSH 可以操作不同的对象,除常数外,还可以对地址进行操作。
可以发现字符串名称前面的 OFFSET 关键词,这条指令会将这个字符串或者字符数组的地址压栈。
双击字符串名称,跳转到下图中。
一般 C 语言源代码中,字符数组是这样定义的: Char mystring[] = "Hello"
按D键
会改变windowname
变量的数据类型,强制 IDA 不将它视作字符数组而作为 db 或者说字节。
修改之后,引用这个地址的一些指令会随之改变,在下图中,offset 关键字确保传入的还是 0x004020e7 这个地址,但地址上存储的不再是字符数组,而是字节。
整个指令变成了push offset byte_4020E7
,查找 `byte_4020E7`` 的内容,变成了 db 类型。
按A键
将其转换为 ASCII 字符串,恢复到初始显示。
当发现其他作为单独宇节显示的宇符串时,都可以进行该操作。找到宇符串的起点按 “A” 键,使显示更加易读。
如下内容:
按 A 键之后,变成如下内容:
在0x402110
处按D键
, 将 amenu
字符串分成单个字节。再按A键
重新组合成字符串。
普通字符串示例
按X键
查找引用字符串的位置。
一般在向函数传递参数的时候,一般都会使用PUSH offset xxxxx
指令,将字符串的地址传递给函数。如果没有 offset 关键词,传递进去的就是0x402110
地址上存储的内容,就是字符串的具体字节55 4E 45 4D
。但是 API 函数不是这样运行的,他们一般接受指针或者字符串的起始地址作为参数。
在以上的指令中,DS: 这个标记表示程序将在数据区块 (DS=DATA) 的内存上写入。
pop 指令#
POP 指令会读取栈顶上的值,并且将它传输到目标寄存器上。如下图所示,POP EDI
将读取栈顶上的第一个值并将其传给 EDI 寄存器,并将 ESP 的值指向已读取值下方的那个值,并把它作为栈的顶部。
通过 Text 搜索 pop,发现 pop 指令的使用方式没什么改变,而且没有将值 pop 存入一个地址中。