汇编寄存器和常见指令
https://www.freesion.com/article/71371199944/ (opens new window)
# x86寄存器命名
64位有16个寄存器,r开头 32位只有8个,e开头
AX、BX、CX、DX、SI、DI、SP、BP 等是16位寄存器 有时候也用SP表示SP、ESP、RSP的统称。
sp 栈指针寄存器 esp和ebp是32位 (extend) rsp和rbp是64位 (register)
rsp是完整的64位; esp是rsp的低32位; sp 是rsp的低16位;
pc 程序计数器
# 寄存器用途
%rax 作为函数返回值使用
%rsp 栈指针寄存器,指向栈顶
%rdi %rsi %rdx %rcx %r8 %r9 用作函数参数,依次对应第1-6个参数. 最多有六个整形或指针型参数通过寄存器传递,超过六个只能通过栈来传递
一般寄存器:AX、BX、CX、DX AX:累积暂存器,BX:基底暂存器,CX:计数暂存器,DX:资料暂存器
索引暂存器:SI、DI SI:来源索引暂存器,DI:目的索引暂存器
堆叠、基底暂存器:SP、BP SP:堆叠指标暂存器,BP:基底指标暂存器
EAX、ECX、EDX、EBX:为ax,bx,cx,dx的延伸,各为32位元 ESI、EDI、ESP、EBP:为si,di,sp,bp的延伸,32位元
eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
比方说:add eax,-2 ; //可以认为是给变量eax加上-2这样的一个值。
这些32位寄存器有多种用途,但每一个都有“专长”,有各自的特别之处。
- EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
- EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
- ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
- EDX 则总是被用来放整数除法产生的余数。
# 汇编语法和编译器
http://t.zoukankan.com/zpcdbky-p-14837963.html (opens new window)
# AT&T语法 和 Intel语法
这是两种不同的语法,AT&T主要用于UNIX,Intel语法主要用于Windows
# GAS编译器 和 NASM编译器
这是两种不同的编译器, GAS是GNU Assembler的简写,基于AT&T syntax指令,生成.s文件 NASM是Netwide Assembler的简写,基于Intel syntax指令,生成.asm文件
# 源操作数和目的操作数的前后
https://blog.csdn.net/wanglx_/article/details/7474070 (opens new window)
x86汇编一直存在两种不同的语法,intel语法和AT&T语法,在intel的官方文档中使用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一直使用AT&T语法。
unix下使用gas编译器的AT&T语法主要有以下特点:
- 寄存器名前缀%
- 操作数是源在前,目的在后,如
mov %src %tar
, 与Intel语法刚好相反。 - 操作数长度在指令名后缀,b表示8位,w表示16位,l表示32位,如
movl %ebx,%eax
。 - 立即操作数(常量)用$标示,如addl $5,%eax
- 变量加不加$有区别。如movl $foo, %eax表示把foo变量地址放入寄存器%eax。movl foo,%eax表示把foo变量值放入寄存器%eax。
操作数前后的记忆方法小技巧:对于mov指令,假设src在前des在后是比较方便符合直觉的,寄存器加%是不方便的,那么这两种语法总是都有方便和不方便的地方。下面假设src和tar是寄存器,则:
mov %src %tar # unix下使用GAS编译器的AT&T语法,src和tar前后方便,但是加%不方便
`mov tar src # windows下使用NASM编译器的INtel语法, src和tar前后不方便,但是不加%方便
2
# gdb用法
title: gdb用法-ida逆向-friad_hook date: 2021-10-06 01:44:49 permalink: /pages/1b0902/ categories:
- Linux tags:
# 设置参数
进入gdb后: set args 参数1 参数2
或者启动gdb时 gdb --args a.out arg1 arg2
# 回车重复执行上一次命令
# layout显示代码,tui模式
参考: https://blog.csdn.net/zhangjs0322/article/details/10152279 (opens new window)
进入gdb后, 执行layout src
layout: 用于分割窗口,可以一边查看代码,一边测试。主要有以下几种用法:
- layout src: 显示源代码窗口
- layout asm: 显示汇编窗口
- layout regs: 显示源代码/汇编和寄存器窗口
- layout split: 显示源代码和汇编窗口
- layout next: 显示下一个layout
- layout prev: 显示上一个layout
layout窗口控制和退出:
- Ctrl + x,再按1: 单窗口模式,显示一个窗口
- Ctrl + x,再按2: 双窗口模式,显示两个窗口
- Ctrl + x,再按a: 回到传统模式,即退出layout,回到执行layout之前的调试窗口。
- 如果Ctrl+x不生效,则先执行
set keymap vi
或者set editing-mode vi
,就可以生效了。 - layout 模式显示多个窗口时,如果焦点不在cmd窗口,则上下按键不起作用, 输入
fs next
可切换窗口焦点至cmd
有时候layout asm显示的代码窗口太小看不全,可以在非layout模式下用disassemble
直接显示反汇编代码,
# 指定源文件搜索路径
(gdb) dir <xxx>
gdb停到断点的时候,会显示当前断点的源文件路径
# 查看内存
(gdb) x /nfu addr
- n,n个单元,数字
- f,显示格式为f,x十六进制,d十进制,c字符,f浮点数
- u,单元u的形式,bhwg, 单双四八字节
(gdb) x/20xw 0x7fffffffe230 显示20个单元,16进制显示,4字节每单元
(gdb) x/20xb 0x7fffffffe230 显示20个单元,16进制显示,1字节每单元
(gdb) x/s 0x7fffffffe230 显示指针处的字符串
2
3
# 查看寄存器的值
函数入参:
(gdb) x/s $rdi 查看rdi寄存器处字符串的值, (函数入口 rdi表示第一个参数,6个参数分别为 rdi rsi rdx rcx r8 r9)
(gdb) x/64xb $rdi+0x36 将rdi寄存器处的值作为地址,打印其+36处地址的内存
(gdb) p *(int32_t *)($rdi+0x60) 将rdi寄存器处的值作为地址,将其+60处地址转为int32_t类型打印出来
(gdb) p $pc 查看程序计数器的值(当前程序代码运行的地址)
(gdb) p $sp 查看栈指针就寄存器的值
2
3
4
5
6
# 设置变量的值
(gdb) set a=1
# 设置临时变量
(gdb) set $i="hello"
2
3
4
# 在so的某个偏移量处打断点
# 查看mapping信息,找到so的加载地址
(gdb) i proc mapping
# 创建临时变量so_base
(gdb) set $so_base= 0x7ffff71d8000
# 在so指定偏移量处打断点
(gdb) b *($so_base +0x7B05A)
2
3
4
5
6
7
8
# 监视值的变化
四、监视值变化
1、使用watch variable设置监视点,当value变化时,gdb会中断。
2、当离开variable的定义范围时,可能会使监视点不起作用。 可以使用地址监视:
print &variable找出地址address,然后watch *(int*)address。
# dprint 动态打印
# 在指定行打印
dprint sometest.cpp:13,"in dprint a=%d, i=%d\n",a,i
# 在指定函数打印
dprintf perf_msgr_client.cc:ready,"In fun ready:c= %d,jobs=%d\n",c,jobs
2
3
4
5
6
这种动态打印是特殊的断点,可以通过i b
或者info break
查看
dprintf的本质是断点,因此目标程序并不是持续执行的,目标程序因为断点而暂停执行,等dprintf打印消息后再继续
dprintf输出到文件: https://sourceware.org/gdb/current/onlinedocs/gdb/Dynamic-Printf.html (opens new window)
# 执行
(gdb) run: 重新开始运行文件(run-text: 加载文本文件,run-bin: 加载二进制文件),简写r
(gdb) start: 单步执行,运行程序,停在第一执行语句
(gdb) next: 单步调试(逐过程,函数直接执行),简写n
(gdb) step: 单步调试(逐语句: 跳入自定义函数内部执行),简写s
(gdb) finish: 结束当前函数,返回到函数调用点,相当于跳出
(gdb) continue: 继续运行,简写c
2
3
4
5
6
正在运行时,按Ctrl+C暂停
# 切换线程
(gdb) info threads 查看线程信息 (gdb) thread n 切换线程
# 断点
设置断点 break 简写b 设置临时断点: tbreak 简写tb
(gdb) b *(0x7ffff71d806) 在函数地址处打断点 (gdb) b send 在send函数地址处打断点 (gdb) b _ZN7dce_api10common_imp12on_sync_dataEiPv 在_ZN7dcxxx函数地址处打断点 (gdb) b xx.cpp:26 在xx.cpp第26行设置断点
(gdb) info breakpoints: 查看当前设置的所有断点 (gdb) delete breakpoints num: 删除第num个断点,简写d
# 其他命令
(gdb) info proc mapping 查看内存映射
(gdb) info reg 查看寄存器的值,简写为 i r
(gdb) help: 查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h
(gdb) list: 查看原代码(list-n,从第n行开始查看代码。list+ 函数名: 查看具体函数),简写l
(gdb) backtrace: 查看函数的调用的栈帧和层级关系,简写bt
(gdb) frame: 切换函数的栈帧,简写f
(gdb) info: 查看函数内部局部变量的数值,简写i
(gdb) print: 打印值及地址,简写p
(gdb) quit: 退出gdb,简写q
(gdb) display: 追踪查看具体变量值
(gdb) undisplay: 取消追踪观察变量
(gdb) watch: 被设置观察点的变量发生修改时,打印显示
(gdb) i watch: 显示观察点
(gdb) enable breakpoints: 启用断点
(gdb) disable breakpoints: 禁用断点
(gdb) run argv[1] argv[2]: 调试时命令行传参
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 增加源代码的查找路径
# 如果debug信息中的文件路径是相对路径,则用dir增加对应的folder
(gdb) dir path_to_src_folder
# 如果debug信息中的文件路径是绝对路径,则重定向
(gdb) set substitute-path old_path new_path
2
3
4
5
# hook
- 动态库hook: 一种简单的hook方式是preload
- 静态库hook: 编译时指定_warp_
- 任意地址hook: inline hook,可去GitHub上找相关的实现。其实就是修改call指令的目的地址,跳到自己定义的函数处执行。
- 也有一些专业的hook库,如friad等。
# ida中逆向常用的宏定义
https://blog.csdn.net/huiguixian/article/details/52026710 (opens new window)
在ida安装目录,找到defs.h,包含即可。一般在plugins目录下
# 典型函数调用过程中栈指针寄存器的变化
# System V AMD64 函数调用约定:callee-saved寄存器
- System V AMD64 是基于x86_64架构Linux系统上广泛使用的一种调用约定, 我们在Linux系统上用gcc编译的代码默认都是遵循这种调用约定。其他还有:
- cdecl (C declaration):是32位平台常见的一种约定,GCC、Clang、Visual Studio的C编译器都默认使用这种调用约定。
- Microsoft x64:微软提出的基于x86_64架构的Windows系统上的一种调用约定
- stdcall:它是用于32位Windows上的一种调用约定。
https://blog.csdn.net/qq_29328443/article/details/107232025 (opens new window)
caller-saved register, 调用者保存寄存器,也叫易失寄存器。因为被调用函数可以随便用这些寄存器,用完了也不需要恢复,caller需要自己负责保存。
callee-saved register,被调用者保存寄存器,也叫非易失寄存器,包括%rbx, %rsp, %rbp, %r12-r14, %r15, x87 CW,其中%rsp和%rbp是重点;
# demo查看反汇编
将下面的程序编译,查看反汇编
test_call.c:
#include <iostream>
#include <unistd.h>
#include <stdio.h>
int foo()
{
int a = 5;
int b = 8;
char s[16] = "hello\n";
printf("a=%d\n", a);
return a+b;
}
int main()
{
char a[16] = "hello";
int p = foo();
return p;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
编译: g++ -o a.out -g -O0 test_call.c
查看反汇编:objdump -d a.out
# main 部分
00000000004006c7 <main>:
4006c7: 55 push %rbp
4006c8: 48 89 e5 mov %rsp,%rbp
4006cb: 48 83 ec 20 sub $0x20,%rsp
4006cf: 48 b8 68 65 6c 6c 6f movabs $0x6f6c6c6568,%rax
4006d6: 00 00 00
4006d9: 48 89 45 e0 mov %rax,-0x20(%rbp)
4006dd: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
4006e4: 00
4006e5: e8 93 ff ff ff callq 40067d <_Z3foov> # 调用foo
# callq指令,q表示8字节。 e8就是call指令
# call指令会push当前ip寄存器的下一条指令地址,然后跳转到目标函数地址。
# 对于64位程序,push rip后,sp指针会自动-8
4006ea: 89 45 fc mov %eax,-0x4(%rbp)
4006ed: 8b 45 fc mov -0x4(%rbp),%eax
4006f0: c9 leaveq
4006f1: c3 retq
# foo部分
000000000040067d <_Z3foov>:
40067d: 55 push %rbp # 进入函数后,首先push rbp寄存器入栈,保存父函数的rbp到栈里
40067e: 48 89 e5 mov %rsp,%rbp # 然后将父函数的rsp作为当前的rbp
400681: 48 83 ec 20 sub $0x20,%rsp #栈指针下移,分配栈空间。
400685: c7 45 fc 05 00 00 00 movl $0x5,-0x4(%rbp)
40068c: c7 45 f8 08 00 00 00 movl $0x8,-0x8(%rbp)
400693: 48 b8 68 65 6c 6c 6f movabs $0xa6f6c6c6568,%rax
40069a: 0a 00 00
40069d: 48 89 45 e0 mov %rax,-0x20(%rbp)
4006a1: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
4006a8: 00
4006a9: 8b 45 fc mov -0x4(%rbp),%eax
4006ac: 89 c6 mov %eax,%esi
4006ae: bf e0 07 40 00 mov $0x4007e0,%edi
4006b3: b8 00 00 00 00 mov $0x0,%eax
4006b8: e8 73 fe ff ff callq 400530 <printf@plt>
4006bd: 8b 45 f8 mov -0x8(%rbp),%eax
4006c0: 8b 55 fc mov -0x4(%rbp),%edx
4006c3: 01 d0 add %edx,%eax
4006c5: c9 leaveq # leaveq相当于两条指令:
# `movq %rbp, %rsp`(恢复父函数的rsp)
# `popq %rbp`(恢复父函数的rbp)
4006c6: c3 retq # retq相当于:popq %rip (将rip恢复为父函数中call指令的下一条指令地址)
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
[ATT汇编]后缀字母:
- b字节=1字节
- w字=2字节
- l双字=4字节
- q, Quad Word, 四字=8字节
可尝试gdb运行并查看寄存器的变化
$ gdb a.out
(gdb) start # 进入main函数
(gdb) layout split # 显示源码
(gdb) layout reg # 显示寄存器窗口
(gdb) si # 单步执行
2
3
4
5
# 总结
1. push %rbp # 调用call进入函数后,首先push rbp寄存器入栈,保存父函数的rbp到栈里
2. mov %rsp,%rbp # 然后将父函数的rsp作为当前的rbp
3. sub $0x20,%rsp #栈指针下移,分配栈空间。不一定有
... # 执行函数内容
* leaveq # leaveq相当于两条指令:
# `movq %rbp, %rsp`(恢复父函数的rsp)
# `popq %rbp`(恢复父函数的rbp)
* retq # retq相当于:popq %rip (将rip恢复为父函数中call指令的下一条指令地址)
2
3
4
5
6
7
8
9
10