汇编笔记

1
2
3
<!--零散地记些笔记-->

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

环境准备:ubuntu20.04安装 | xiaoxiaoxy (xiaoxiaoxy1.github.io)

零散知识

1
2
3
4
5
6
7
1、汇编语言的组成
1):汇编指令 机器码助记符,有对应的机器码
2):伪指令 没有对应的机器码,由编译器识别,计算机并不执行
3):其他符号 如+、-、*、/等,由编译器识别,没有对应的机器码



运行一个汇编程序代码

1
2
3
4
5
四步骤:
编程:要用到文本编辑器去编写汇编程序代码
编译:要下载编译器及会用编译指令
链接:要会用链接指令
跟踪:即Debug,要会利用工具Debug

汇编指令风格

        两种主流风格:AT&T格式Intel格式

        据了解:Intel风格适用得更加广泛,所以本章内容以Intel风格为主导

两种风格写出来的 hello world :

AT&T格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#hello.s 
.data # 数据段声明
msg : .string "Hello, world!\\n" # 要输出的字符串
len = . - msg # 字串长度
.text # 代码段声明
.global _start # 指定入口函数

_start: # 在屏幕上显示一个字符串
movl $len, %edx # 参数三:字符串长度
movl $msg, %ecx # 参数二:要显示的字符串
movl $1, %ebx # 参数一:文件描述符(stdout)
movl $4, %eax # 系统调用号(sys_write)
int $0x80 # 调用内核功能

# 退出程序
movl $0,%ebx # 参数一:退出代码
movl $1,%eax # 系统调用号(sys_exit)
int $0x80 # 调用内核功能

Intel格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; hello.asm 
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能

个人观点:Intel风格更加简洁

基于Intel格式的文件汇编步骤

1、汇编器的安装

1
2
3
4
5
sudo apt-get install nasm
不成功则执行:
sudo rm <提示中给的锁文件路径>

参考链接:https://blog.csdn.net/weixin_44121966/article/details/118143296

image-20220701231406644

2、汇编器及链接器的使用

1
2
3
4
5
6
7
8
9
10
11
12
编译指令:
nasm -f elf64 <编辑器编辑的.asm文件>

链接指令:
ld -s -o <生成文件的文件名> <编译出来的.o文件>
or
gcc -fPIC -no-pie -o <生成文件的文件名> <编译出来的.o文件>

为什么gcc的链接指令不是 gcc -o <生成文件的文件名> <编译出来的.o文件> ?
从Ubuntu16.10开始默认启用PIE,而makefile的库不支持PIE。
详请查看以下链接
链接:https://blog.csdn.net/weixin_43360707/article/details/124272319

image-20220701234352070

image-20220702143157855

3、跟踪(Debug)

        Debug的话,基本步骤就是下断点(break)、跑程序(run)以及单步调试(ni/si)

        使用的工具:gdb

                ubuntu20.04安装 | xiaoxiaoxy (xiaoxiaoxy1.github.io)(6、7点讲的是pwndbg的安装,也可以自行找别的安装教程)

                gdb插件安装与配置:pwndbg,peda,gef_byerose的博客-CSDN博客_gdb插件安装

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
#个人记录的一些pwndbg使用的指令
#常用的
pwndbg> b main #break ,main函数下断点
pwndbg> r #run ,运行程序,其会自动在第一个断点处暂停执行
pwndbg> ni #next ,单步执行,不进入函数体
pwndbg> si #step ,单步执行,进入函数内部
pwndbg> p #print ,打印指定变量的值。例:p &buf2,查找buf2的存储地址,
pwndbg> i b #info break,查看所有断点
pwndbg> vmmap #查看当前程序的系统调用库,常用来查看可执行段位置
pwndbg> cyclic 200 #按一定的规则生成200个字符串
pwndbg> cyclic -l <数据> # 查询<数据>在生成的字符串中的位置,<数据>例子:0x62616164
pwndbg> b *<地址> # 指定地址处下断点,<地址>例子:0x08048648
pwndbg> x/130wx <地址> # 打印栈上信息,此处<地址>常用esp指向的栈地址
pwndbg> i all-registers # 查看所有寄存器信息

#不常用
pwndbg> c #continue,运行至下一断点
pwndbg> d #delete ,通常用来删除所有断点,也可以删除指定编号的各类型断点
pwndbg> disass #~~emble ,反汇编,disassemble main,disassemble /r main

#暂未用到过的
pwndbg> l #list ,显示源程序代码的内容,包括各行代码所在的行号。
pwndbg> fi #finish ,结束当前正在执行的函数,并在跳出函数后暂停程序的执行。
pwndbg> j #jump ,使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
pwndbg> q #quit ,终止调试。
pwndbg> u #until ,百度去搜

#相对高级一点的用法
# http://c.biancheng.net/view/8238.html
# https://www.cnblogs.com/zuoanfengxi/p/12763350.html
pwndbg> set $eip = 0x8048300 #设置寄存器eip的值
pwndbg> p $eip #打印
pwndbg> x/i $eip #把地址处存的值按照汇编来解析,打印出来

感觉讲的话有点多,不太想讲,但还是讲一下吧,因为我刚接触的时候简单的指令我也老是记不住

首先 ,$ gdb <二进制文件名> ,进入debug界面

其次,pwndbg> b main ,在main函数出下断点

image-20220702145200553

然后就可以,pwndbg> r ,运行程序

image-20220702150100582

image-20220702150201016

最后单步,pwndbg> ni ,逐步调试即可

image-20220702150219395

这就是一个简单的debug流程,复杂的debug过程中你可以看程序的走向,寄存器的值、栈信息、堆信息等

编程(写代码)、编译、链接、跟踪

先对这个hello world程序浅浅地做下分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; hello.asm 
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能
1
2
3
4
5
6
7
section .data            ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度

#数据段的定义,什么是数据段?存储字符串、常量等信息,对应五大分区的常量区
#链接:https://www.jianshu.com/p/a3e80cb5198b
#这里不能这么讲,换个方式吧,这里也不删了,暂时留在这,也不分析这个hello world了

1、ida反编译一个二进制文件,我这是以ctfwiki中的ret2text为例,文件下载地址:ret2text

这是main函数

image-20220702152227493

我们查看main函数的汇编:

image-20220702152701909

.text段对应内存五大分区中的代码区

image-20220702154257524

拉动滚动条,在最左端我们会查看到LOAD、.fini、.eh_frame_hdr、got、got.plt、.plt、.rodata、.bss……等很多字段

image-20220702153044662

image-20220702153210284

image-20220702153317342

image-20220702153417786

然后我主要要讲的就是.data、.rodata、.bss、.text,.text前面讲了,接下来讲一下前三个

.data是代码段.rodata是只读代码段,详见下面的链接

链接:bss、data和rodata区别与联系_brlee的博客-CSDN博客_rodata

image-20220702153039971

image-20220702154424740

image-20220702154100740

.bss段是指静态代码段,程序中未赋值的变量就会存储在这,例如C代码中定义了一个 int a;但为给a赋值,a就会存到.bss段

image-20220702153359957

image-20220702154040757

这张图还是贴一下吧 来源:内存五大分区 - 简书 (jianshu.com)

image-20220702154839416

现在回头看一下这个hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
; hello.asm 
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能

#global 全局变量
#_start 对比main函数去理解
#int 0x80 系统调用(进入内核模式),在这条指令前,先往指定寄存器中压入值,进入内核模式后,
#系统根据指定寄存器中的值判断做什么样的操作(操作的例子:退出程序、文件读写打开关闭等一系列操作)

对于汇编代码中的指令符,以下是我个人记录的一些

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
数据传送与访问:mov

算术运算与逻辑运算:
inc/dec #操作数±1
add/sub #长度相同的操作数相加减
and/or #按位逻辑与/或
mul #乘法操作
xor #异或操作,常用做寄存器值置零
cmp #比较两个值
neg #将操作数转换为二进制补码,并将操作数的符号位取反

跳转指令与循环指令:
jmp #无条件跳转指令,一般需要使用一个标号来标识,可以实现循环
LOOP #循环指令,每循环一次循环计数寄存器减1
je #条件跳转,链接:https://blog.csdn.net/ssihc0/article/details/5215044
test #与AND命令有相同效果,只是Test指令不改变AX和BX的内容,而AND指令会把结果保存到AX中

栈与函数调用:
push #入栈,详细操作P41
pop #从栈中pop一个值,给ebp

使用栈保存函数返回地址:
call #call调用子函数时,下一条指令的地址作为返回地址存入栈中
#相当于 push IP
# jmp near ptr 标号 这两条汇编
#https://blog.csdn.net/u013018721/article/details/51264199
ret #往栈上高地址一个单位取地址当做跳转地址 *******
#链接:https://blog.csdn.net/qq_37340753/article/details/81585083

其他:
lea #官方解释Load Effective Address,即装入有效地址的意思,它的操作数就是地址
nop #空操作,链接:https://www.cnblogs.com/shangzhijian/p/4994028.html
leave #mov esp,ebp和pop ebp
#链接:https://blog.csdn.net/striver1205/article/details/25216699
#链接:https://blog.csdn.net/zhangxinrun/article/details/5888425
int 0x80 #系统调用

emm…暂时打住,这篇暂时就到这吧

QAQ