[PwnCourse:从0开始的PWN教程] 0-1-2 Computer 101

2026-04-11 13:031阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

佬友们好,本次为大家带来Pwn基础计算机知识的讲解。

Computer Component

一台简单的PC大概由CPU,显卡,内存,硬盘,主板,电源等零件组装而成。这是计算机的硬件组成。而我们想要正常地使用,还需要一些必要的预装程序,来告诉计算机它的硬件可以被用来做哪些事。硬件和软件结合,才是一台完整的PC。

Hardware

  • CPU 中央处理器:计算机的大脑,负责执行指令处理数据。包含ALU 算术逻辑单元,CU 控制单元和REG 寄存器
  • 主板:所有硬件的连接中心,主板上有大量复杂的集成电路,提供了很多插槽和接口,用于连接CPU,内存,显卡等硬件。
  • RAM 内存:运行时存储,也叫运行内存,是计算机用来储存运行时数据和程序的地方。寄存器,内存,硬盘(外存)都可以看作存储器,但是读写速度有很大的差别,寄存器最快,内存次之,外存速度最慢。
  • 硬盘:硬盘是用来永久存储数据的设备,包括机械硬盘(HDD)和固态硬盘(SSD)两种。机械硬盘通过旋转盘片和磁头读写数据,而固态硬盘通过闪存芯片实现。
  • 显卡:显卡负责处理电脑的图形显示,它包括图形处理器(GPU)和显存。对于需要进行图形处理的应用程序,如游戏和图像编辑软件,显卡的性能至关重要。
  • 输入输出设备:电脑的输入设备包括键盘、鼠标、触摸屏等,用于输入数据和指令。输出设备包括显示器、打印机、音箱等,用于显示结果和输出数据。

Software

  • 操作系统(OS):操作系统是电脑的核心软件,它管理硬件资源、提供用户接口和运行应用程序。常见的操作系统有Windows、MacOS和Linux等。
  • 应用软件:应用软件是由程序员开发的,用于完成特定任务的软件。例如,办公软件、图像处理软件、视频编辑软件等。
  • 开发工具:开发工具是用于编写、调试和测试代码的软件,包括集成开发环境(IDE)和编译器等。常见的开发工具有Visual Studio、Eclips和Xcode等。
  • 驱动程序:驱动程序是用于控制硬件设备的软件,它们与操作系统紧密配合,使硬件能够正常工作。
  • 网络协议:网络协议是用于在计算机网络中传输数据的规则和标准。常见的网络协议有TCP/IP、HTTP、FTP等。

在UserMode的利用中,我们主要关注主要关注应用软件的底层运作,分析其漏洞并实现利用方法,最终得到系统的Shell或者敏感信息。而在Kernel Mode中,我们转向对OS的直接利用,并尝试获取系统最高权限root

Binary Data

Pwn 属于 二进制Binary安全的一部分,为什么是“二进制”?众所周知,计算机只能处理“0”和“1”的二进制信息。在二进制中,逢二进一,每一位都只有0和1两种状态,称之为比特bit,8比特为1字节byte。字节通常是计算机处理信息的最小单位,计算机中的数据一般都以二进制,连续的字节形式进行存储。我们输入给计算机的所有信息,包括文字,图像,音频,视频等,都可以被编码成二进制的形式存储进入计算机,而输出时只需要进行解码即可。

Base conversion

为了方便阅读编码后的信息,我们通常会将二进制的内容转为八进制或十六进制。这里最重要的,你需要掌握的内容是简单的二进制,十进制和十六进制的相互转换。十六进制是Pwn的常用进制,在阅读原始数据时,一位十六进制等价于4bit,两位十六进制数就是一字节的内容

在表示不同进制的数时,除了数学上用括号+下标的方式以外,计算机中有以下常见的表达方式

0b10: b表示Binary,指二进制数10 0d10: d表示decimal,指十进制数10 0x10: x表示hex,指十六进制数10 10b:二进制数10 10h:十六进制数10

Endian

有时候为了方便运算和存储,计算机会以一字节为单位,将原始的信息编码后以反方向的形式进行存储,这就是小端序little endian,与之相反的,如果信息存储的高低位和人类阅读顺序相同,那么就是大端序big endian。端序取决于CPU的架构,像x86,RISC-V等架构就是小端序,SPARC,System370等架构是大端序。

人类阅读: 0xDEADBEEF 小端序: EFh BEh ADh DEh 大端序: DEh ADh BEh EFh

Virtual Memeory Space

我们知道,程序在操作系统中运行时会创建一个进程。操作系统会保存进程运行时所需要的所有状态信息,也叫做上下文Context。一般情况下,操作系统在同一时间会运行很多进程,为了防止内存泄露,我们应该将各个进程的上下文保存在不同的地方,让各个进程相互独立,如何实现这样的构想呢?这就是虚拟内存空间Virtual Memory Space所做的事

User Spaces1138×513 58.6 KB

上图很好地展示了用户空间相互隔离的概念。多个进程相互之间隔离,都具有自己的虚拟内存空间,但是都共享同一个内核空间。

用户空间中保存着相关进程的所有信息,如下

User Virtual Memory Space775×874 56.7 KB

我们来了解下图中涉及的诸多成分。

首先是以0x开头的十六进制数,这叫作地址Address,表示内存空间的位置。一个地址对应着一字节的数据

程序在未被加载为一个进程时,是作为二进制文件被保存在硬盘中的。在Linux下,这种文件叫作ELF文件。当我们在Shell中启动这个程序, 首先操作系统会在一个用户空间中加载程序的所有信息,加载时是按照ELF文件本身的视图来加载的,可以简单理解为把一个ELF文件分割成多个区段进行加载。表现在内存上,就是不同的区段。

  • BSS段:bss segment通常是指用来存放程序中未初始化的全局变量的一块内存区域,这一段区域属于静态内存分配,在程序被完全加载进入内存后不会改变其区段大小。
  • DATA段:即数据段。data segment通常是指用来存放程序中已初始化的全局变量的一块内存区域,同样属于静态内存分配
  • TEXT段:即代码段,text segment通常是指用来存放程序中可执行代码的一块内存区域,这部分区域的大小通常在程序运行前就已经确定,并且权限通常属于只读可执行。
  • Heap:堆 ,用于存放在进程运行时动态分配的内存段。大小不固定,可以动态变化。当程序第一次调用malloc等函数时会被创建。
  • Stack:栈,是相对最重要的内存结构。这里存放程序创建的局部变量。除此之外,在程序的执行过程中,栈是实现复杂函数调用的基础。栈是一个寄存,交换临时数据的缓存区

Experiment - Pwndbg查看程序虚拟内存空间

我们以一个实验来直观地理解进程的内存。

编译以下代码

#include <stdio.h> int main() { printf("Hello World!\n"); return 0; } // gcc exper0121.c -o exper0121

这是一个经典的Hello World程序,尝试执行一下。

程序正常被加载进内存后执行了打印函数,我们使用gdb进入调试界面。

gdb exp0121

pwndbg1313×888 298 KB

我们上一节配置的pwndbg已经正常加载。使用start启动程序。

pwndbg> start

pwngdb context1920×1085 446 KB

可以看到出现了复杂的调试界面。第一次看可能确实有点难懂,这里我们详细讲解一下这些界面的含义。

这个部分是高亮颜色的含义

这样的十六进制数就是程序中的一串地址,可以看到这个地址只有6字节,而我们64位的PC理应是8字节的地址。这是因为用户空间的地址都是以0x0000开头,所以这里就省略掉了。上图的地址是黄色的,对应到我们的高亮规则,这一串是Stack段的地址,也就是栈地址

registers1920×402 185 KB

接下来是REGISTERS界面,就是寄存器部分,这里显示的是寄存器中的值。后文我们会对寄存器详细进行介绍。

disasm1920×247 90.7 KB

这是反编译界面。gdb读取程序的机器码后还原成了人类可读的汇编语言代码。后文我们也会介绍汇编语言的相关内容。

stack2560×1440 244 KB

这是栈界面,显示栈区段的内容。在后续的学习中,我们重点关注栈上的变化,所以要熟悉。

你可能在C语言中学习了指针的概念,这里的表示方式可以类比起来理解
0x7fffffffdfe0 —▸ 0x7fffffffe090 —▸ 0x7fffffffe0f0 ◂— 0
这串的意思就是在0x7fffffffdfe0中存储着0x7fffffffe090,0x7fffffffe090中存储着0x7fffffffe0f0,0x7fffffffe0f0中存储着0。
用指针来解释就是0x7fffffffe090是一个指向0x7fffffffe0f0的指针,而0x7fffffffdfe0是指向指针的指针。

backtrace2560×1440 93.8 KB

这部分是函数调用栈回溯的显示,表示程序的执行位置。

现在看不懂没关系,看得多了就会越来越顺眼的~

书归正传,我们要想查看整个进程用户空间的全貌,就需要这条指令

pwndbg> vmmap

vmmap1261×753 318 KB

可以观察到完整的映射表,程序以0x1000为单位分出了很多内存段。实际上这就是页对齐

Assembly Language

提到二进制的学习,最基础,也最绕不开的话题就是汇编语言

汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。 在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。 在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令

如果没有特殊说明,本系列的所有汇编语言都是x86架构,Intel语法

汇编语言看着很唬人,一看到要读汇编了就掌心冒汗气喘吁吁。其实简单理解的话,如果你想要让某人去做某事,那么就给他下个指令,让他去做某事就好——这就是汇编的底层逻辑,给计算机的CPU直接下达指令。所谓的汇编,其实就是一条条指令。

简单举个例子。

mov eax, 8

这条指令表示将数值8赋值给eax寄存器。如果将eax看作是一个变量value那么这条指令等价于

int value = 8;

汇编语言可以无缝转换成计算机能读懂的二进制码。在接近底层研究方向的PWN,我们就是和汇编语言打交道。


x86 Register

在继续进行你的汇编之旅之前你需要先了解下我们的研究对象之一——x86寄存器。

寄存器实际上是计算机CPU的储存元件,我们直接摘取Wiki上的描述

寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。

x86寄存器实际上就是在该架构下,CPU对寄存器做了一个角色分化,让特定的寄存器用于特定的用途。值得一提的是,x86架构CPU走的是复杂指令集(CISC) 路线,提供了丰富的指令来实现强大的功能,与此同时也提供了大量寄存器来辅助功能实现。

由于x86架构在16位,32位,64位上均可用,导致不同位数下的寄存器有些许区别,这里我找了一张图

Pasted image 20260324181225565×625 70.2 KB

这张图表示的是不同位数下寄存器的大小差别。如果我们以16位的为基准,以AX寄存器为例,EAX表示Extend AX,RAX表示Register AX。寄存器的大小就是对应位数的大小。

这里图上的寄存器都有%作为前缀,这是AT语法的特征

通用寄存器

首先要介绍通用寄存器,这些的寄存器是程序执行代码最最常用,也最最基础的寄存器,程序执行过程中,绝大部分时间都是在操作这些寄存器来实现指令功能。

  • rax:通常用来执行加法,函数调用的返回值一般也放在这里面。
  • rbx:数据存取
  • rcx:通常作为计数器,例如在for循环的汇编实现
  • rdx:读写IO端口,该寄存器存放端口号
  • rsp:存放指向栈顶的指针
  • rbp:存放指向栈底的指针,通常用rbp + offset的形式来定位栈中的局部变量
  • rsi: 字符串操作时,用于存放数据源的地址
  • rdi: 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

值得注意的是,在64位中,也使用寄存器来传递参数,前六个参数rdi, rsi, rdx, rcx, r8, r9从左向右依次传递,后面的参数在栈上传递。

标志寄存器

标志寄存器,里面有众多标记位,记录了CPU执行指令过程中的一系列状态,这些标志大都由CPU自动设置和修改

  • CF 进位标志
  • PF 奇偶标志
  • ZF 0标志
  • SF 符号标志
  • OF 补码溢出标志
  • TF 跟踪标志
  • IF 中断标志

指令寄存器

rip: 指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址,CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令,如此不断重复,这就是CPU工作的基本日常。

我们主要通过修改IP寄存器的内容控制程序执行流。

段寄存器

段寄存器与CPU的内存寻址技术紧密相关。

  • cs:代码段
  • ds:数据段
  • ss:栈段
  • es:拓展段
  • fs:数据段
  • gs:数据段

这里我们列举一些基础的汇编指令,记住这些你就能勉强看懂一些程序的基础逻辑,以及方便我们进行接下来的学习。

数据传输指令

  • MOV 传送指令
    MOV dest, src :将数据从src移动到dest
  • PUSH压栈指令
    PUSH eax :将eax寄存器里的数据放入栈顶
  • POP弹栈指令
    POP ebx :将栈顶的数据放入ebx的寄存器。
  • LEA计算有效地址
    LEA rdi, LABEL :将LABEL的地址赋予rdi

算术运算指令

  • ADD加法指令
    ADD eax, ebx :将eax里的数据与ebx里的数据相加,结果放入前一个中。
  • SUB 减法指令
    SUB ecx, edxecx中的数据的基础上减去edx中的数据。
  • INC +1指令
    INC edxedx寄存器中的数据自增1
  • DEC -1指令
    DEX edxedx寄存器中的数据自减1

逻辑运算指令

  • NOT取反指令
    NOT eax :将eax中的数据的二进制按位取反%%比如数据为2,二进制表示为10,取反就是01%%
  • AND与运算
    AND eax, ebx :将eax中的数据与ebx中的数据做与运算后放回前一个。
  • OR 或运算
    OR eax, ebx :将eax中的数据与ebx中的数据做或运算后放回前一个。
  • XOR 异或运算
    XOR eax, ebx :将eax中的数据与ebx中的数据做异或运算后放回前一个。

循环控制指令

  • LOOP 计数循环指令
    LOOP Label :使ecx的值减1,当ecx的值不为0的时候跳转至label,否则执行LOOP之后的语句。

转移指令

  • JMP 无条件跳转指令
    JMP Label :无条件跳转到label的位置
  • CALL 过程调用指令
    CALL Label :等价于PUSH ip;JMP Label
  • JE & JNE :条件跳转指令
    JE Label:考察前一条指令的执行结果,JE为真跳转,JNE为假跳转

我们再看刚才的实验给出的程序

#include <stdio.h> int main{ printf("Hello World!"); return 0; }

学习了c语言,我们可以了解到c语言在编译执行时会生成一个.s文件,这就是c语言程序转换成的
汇编语言文件。

.LC0: .string "Hello World!" main: lea rdi, .LC0[rip] mov eax, 0 call printf@PLT mov eax, 0 ret

当然,实际的.s不会如此简洁,这里我们只保留了需要的部分。

  • .LC0可以看作一个储存着字符串常量的指针。
  • lea rdi, .LC0[rip]:将.LC0的指针地址赋予rdi,此时rdi装着一个指针,实际指向.LC0
  • mov eax, 0:将0赋予eax
  • call printf@plt :调用.plt表中记载的printf(),打印字符串。
  • ret :返回上一个函数。

下一节,我们介绍函数调用约定的相关内容,以及一些常用工具的使用技巧。

Have Fun​我们下个帖子再见!

网友解答:
--【壹】--:

又是俺,第一排支持,立即学习


--【贰】--:

支持支持


--【叁】--:

要长脑子了,真得严肃学习,是不是可以放到 文档共建 板块?


--【肆】--:

太强了,大佬


--【伍】--:

师傅写的很详细很贴心了,前排支持


--【陆】--:

前排支持大佬,太强了


--【柒】--:

在0-0-0的话题里有详细解释过。Pwn的本意是“二进制漏洞挖掘与利用”,是靠近计算机底层的安全技术。至于其名称的由来,一说是指在早期的黑客比赛中,Pwn由Own演变而来,意思是“砰”的一声,你的系统被攻克了!最早期的CTF比赛只有Pwn一种题型,某种意义上来说,Pwn是最原始的最Oldschool的一种安全技术。


--【捌】--:

大佬好厉害


--【玖】--:

pwn是什么意思?


--【拾】--:

有道理,我考虑再更几节就放进去


--【拾壹】--:

前排支持下教程

问题描述:

佬友们好,本次为大家带来Pwn基础计算机知识的讲解。

Computer Component

一台简单的PC大概由CPU,显卡,内存,硬盘,主板,电源等零件组装而成。这是计算机的硬件组成。而我们想要正常地使用,还需要一些必要的预装程序,来告诉计算机它的硬件可以被用来做哪些事。硬件和软件结合,才是一台完整的PC。

Hardware

  • CPU 中央处理器:计算机的大脑,负责执行指令处理数据。包含ALU 算术逻辑单元,CU 控制单元和REG 寄存器
  • 主板:所有硬件的连接中心,主板上有大量复杂的集成电路,提供了很多插槽和接口,用于连接CPU,内存,显卡等硬件。
  • RAM 内存:运行时存储,也叫运行内存,是计算机用来储存运行时数据和程序的地方。寄存器,内存,硬盘(外存)都可以看作存储器,但是读写速度有很大的差别,寄存器最快,内存次之,外存速度最慢。
  • 硬盘:硬盘是用来永久存储数据的设备,包括机械硬盘(HDD)和固态硬盘(SSD)两种。机械硬盘通过旋转盘片和磁头读写数据,而固态硬盘通过闪存芯片实现。
  • 显卡:显卡负责处理电脑的图形显示,它包括图形处理器(GPU)和显存。对于需要进行图形处理的应用程序,如游戏和图像编辑软件,显卡的性能至关重要。
  • 输入输出设备:电脑的输入设备包括键盘、鼠标、触摸屏等,用于输入数据和指令。输出设备包括显示器、打印机、音箱等,用于显示结果和输出数据。

Software

  • 操作系统(OS):操作系统是电脑的核心软件,它管理硬件资源、提供用户接口和运行应用程序。常见的操作系统有Windows、MacOS和Linux等。
  • 应用软件:应用软件是由程序员开发的,用于完成特定任务的软件。例如,办公软件、图像处理软件、视频编辑软件等。
  • 开发工具:开发工具是用于编写、调试和测试代码的软件,包括集成开发环境(IDE)和编译器等。常见的开发工具有Visual Studio、Eclips和Xcode等。
  • 驱动程序:驱动程序是用于控制硬件设备的软件,它们与操作系统紧密配合,使硬件能够正常工作。
  • 网络协议:网络协议是用于在计算机网络中传输数据的规则和标准。常见的网络协议有TCP/IP、HTTP、FTP等。

在UserMode的利用中,我们主要关注主要关注应用软件的底层运作,分析其漏洞并实现利用方法,最终得到系统的Shell或者敏感信息。而在Kernel Mode中,我们转向对OS的直接利用,并尝试获取系统最高权限root

Binary Data

Pwn 属于 二进制Binary安全的一部分,为什么是“二进制”?众所周知,计算机只能处理“0”和“1”的二进制信息。在二进制中,逢二进一,每一位都只有0和1两种状态,称之为比特bit,8比特为1字节byte。字节通常是计算机处理信息的最小单位,计算机中的数据一般都以二进制,连续的字节形式进行存储。我们输入给计算机的所有信息,包括文字,图像,音频,视频等,都可以被编码成二进制的形式存储进入计算机,而输出时只需要进行解码即可。

Base conversion

为了方便阅读编码后的信息,我们通常会将二进制的内容转为八进制或十六进制。这里最重要的,你需要掌握的内容是简单的二进制,十进制和十六进制的相互转换。十六进制是Pwn的常用进制,在阅读原始数据时,一位十六进制等价于4bit,两位十六进制数就是一字节的内容

在表示不同进制的数时,除了数学上用括号+下标的方式以外,计算机中有以下常见的表达方式

0b10: b表示Binary,指二进制数10 0d10: d表示decimal,指十进制数10 0x10: x表示hex,指十六进制数10 10b:二进制数10 10h:十六进制数10

Endian

有时候为了方便运算和存储,计算机会以一字节为单位,将原始的信息编码后以反方向的形式进行存储,这就是小端序little endian,与之相反的,如果信息存储的高低位和人类阅读顺序相同,那么就是大端序big endian。端序取决于CPU的架构,像x86,RISC-V等架构就是小端序,SPARC,System370等架构是大端序。

人类阅读: 0xDEADBEEF 小端序: EFh BEh ADh DEh 大端序: DEh ADh BEh EFh

Virtual Memeory Space

我们知道,程序在操作系统中运行时会创建一个进程。操作系统会保存进程运行时所需要的所有状态信息,也叫做上下文Context。一般情况下,操作系统在同一时间会运行很多进程,为了防止内存泄露,我们应该将各个进程的上下文保存在不同的地方,让各个进程相互独立,如何实现这样的构想呢?这就是虚拟内存空间Virtual Memory Space所做的事

User Spaces1138×513 58.6 KB

上图很好地展示了用户空间相互隔离的概念。多个进程相互之间隔离,都具有自己的虚拟内存空间,但是都共享同一个内核空间。

用户空间中保存着相关进程的所有信息,如下

User Virtual Memory Space775×874 56.7 KB

我们来了解下图中涉及的诸多成分。

首先是以0x开头的十六进制数,这叫作地址Address,表示内存空间的位置。一个地址对应着一字节的数据

程序在未被加载为一个进程时,是作为二进制文件被保存在硬盘中的。在Linux下,这种文件叫作ELF文件。当我们在Shell中启动这个程序, 首先操作系统会在一个用户空间中加载程序的所有信息,加载时是按照ELF文件本身的视图来加载的,可以简单理解为把一个ELF文件分割成多个区段进行加载。表现在内存上,就是不同的区段。

  • BSS段:bss segment通常是指用来存放程序中未初始化的全局变量的一块内存区域,这一段区域属于静态内存分配,在程序被完全加载进入内存后不会改变其区段大小。
  • DATA段:即数据段。data segment通常是指用来存放程序中已初始化的全局变量的一块内存区域,同样属于静态内存分配
  • TEXT段:即代码段,text segment通常是指用来存放程序中可执行代码的一块内存区域,这部分区域的大小通常在程序运行前就已经确定,并且权限通常属于只读可执行。
  • Heap:堆 ,用于存放在进程运行时动态分配的内存段。大小不固定,可以动态变化。当程序第一次调用malloc等函数时会被创建。
  • Stack:栈,是相对最重要的内存结构。这里存放程序创建的局部变量。除此之外,在程序的执行过程中,栈是实现复杂函数调用的基础。栈是一个寄存,交换临时数据的缓存区

Experiment - Pwndbg查看程序虚拟内存空间

我们以一个实验来直观地理解进程的内存。

编译以下代码

#include <stdio.h> int main() { printf("Hello World!\n"); return 0; } // gcc exper0121.c -o exper0121

这是一个经典的Hello World程序,尝试执行一下。

程序正常被加载进内存后执行了打印函数,我们使用gdb进入调试界面。

gdb exp0121

pwndbg1313×888 298 KB

我们上一节配置的pwndbg已经正常加载。使用start启动程序。

pwndbg> start

pwngdb context1920×1085 446 KB

可以看到出现了复杂的调试界面。第一次看可能确实有点难懂,这里我们详细讲解一下这些界面的含义。

这个部分是高亮颜色的含义

这样的十六进制数就是程序中的一串地址,可以看到这个地址只有6字节,而我们64位的PC理应是8字节的地址。这是因为用户空间的地址都是以0x0000开头,所以这里就省略掉了。上图的地址是黄色的,对应到我们的高亮规则,这一串是Stack段的地址,也就是栈地址

registers1920×402 185 KB

接下来是REGISTERS界面,就是寄存器部分,这里显示的是寄存器中的值。后文我们会对寄存器详细进行介绍。

disasm1920×247 90.7 KB

这是反编译界面。gdb读取程序的机器码后还原成了人类可读的汇编语言代码。后文我们也会介绍汇编语言的相关内容。

stack2560×1440 244 KB

这是栈界面,显示栈区段的内容。在后续的学习中,我们重点关注栈上的变化,所以要熟悉。

你可能在C语言中学习了指针的概念,这里的表示方式可以类比起来理解
0x7fffffffdfe0 —▸ 0x7fffffffe090 —▸ 0x7fffffffe0f0 ◂— 0
这串的意思就是在0x7fffffffdfe0中存储着0x7fffffffe090,0x7fffffffe090中存储着0x7fffffffe0f0,0x7fffffffe0f0中存储着0。
用指针来解释就是0x7fffffffe090是一个指向0x7fffffffe0f0的指针,而0x7fffffffdfe0是指向指针的指针。

backtrace2560×1440 93.8 KB

这部分是函数调用栈回溯的显示,表示程序的执行位置。

现在看不懂没关系,看得多了就会越来越顺眼的~

书归正传,我们要想查看整个进程用户空间的全貌,就需要这条指令

pwndbg> vmmap

vmmap1261×753 318 KB

可以观察到完整的映射表,程序以0x1000为单位分出了很多内存段。实际上这就是页对齐

Assembly Language

提到二进制的学习,最基础,也最绕不开的话题就是汇编语言

汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。 在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。 在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令

如果没有特殊说明,本系列的所有汇编语言都是x86架构,Intel语法

汇编语言看着很唬人,一看到要读汇编了就掌心冒汗气喘吁吁。其实简单理解的话,如果你想要让某人去做某事,那么就给他下个指令,让他去做某事就好——这就是汇编的底层逻辑,给计算机的CPU直接下达指令。所谓的汇编,其实就是一条条指令。

简单举个例子。

mov eax, 8

这条指令表示将数值8赋值给eax寄存器。如果将eax看作是一个变量value那么这条指令等价于

int value = 8;

汇编语言可以无缝转换成计算机能读懂的二进制码。在接近底层研究方向的PWN,我们就是和汇编语言打交道。


x86 Register

在继续进行你的汇编之旅之前你需要先了解下我们的研究对象之一——x86寄存器。

寄存器实际上是计算机CPU的储存元件,我们直接摘取Wiki上的描述

寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。

x86寄存器实际上就是在该架构下,CPU对寄存器做了一个角色分化,让特定的寄存器用于特定的用途。值得一提的是,x86架构CPU走的是复杂指令集(CISC) 路线,提供了丰富的指令来实现强大的功能,与此同时也提供了大量寄存器来辅助功能实现。

由于x86架构在16位,32位,64位上均可用,导致不同位数下的寄存器有些许区别,这里我找了一张图

Pasted image 20260324181225565×625 70.2 KB

这张图表示的是不同位数下寄存器的大小差别。如果我们以16位的为基准,以AX寄存器为例,EAX表示Extend AX,RAX表示Register AX。寄存器的大小就是对应位数的大小。

这里图上的寄存器都有%作为前缀,这是AT语法的特征

通用寄存器

首先要介绍通用寄存器,这些的寄存器是程序执行代码最最常用,也最最基础的寄存器,程序执行过程中,绝大部分时间都是在操作这些寄存器来实现指令功能。

  • rax:通常用来执行加法,函数调用的返回值一般也放在这里面。
  • rbx:数据存取
  • rcx:通常作为计数器,例如在for循环的汇编实现
  • rdx:读写IO端口,该寄存器存放端口号
  • rsp:存放指向栈顶的指针
  • rbp:存放指向栈底的指针,通常用rbp + offset的形式来定位栈中的局部变量
  • rsi: 字符串操作时,用于存放数据源的地址
  • rdi: 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作

值得注意的是,在64位中,也使用寄存器来传递参数,前六个参数rdi, rsi, rdx, rcx, r8, r9从左向右依次传递,后面的参数在栈上传递。

标志寄存器

标志寄存器,里面有众多标记位,记录了CPU执行指令过程中的一系列状态,这些标志大都由CPU自动设置和修改

  • CF 进位标志
  • PF 奇偶标志
  • ZF 0标志
  • SF 符号标志
  • OF 补码溢出标志
  • TF 跟踪标志
  • IF 中断标志

指令寄存器

rip: 指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址,CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令,如此不断重复,这就是CPU工作的基本日常。

我们主要通过修改IP寄存器的内容控制程序执行流。

段寄存器

段寄存器与CPU的内存寻址技术紧密相关。

  • cs:代码段
  • ds:数据段
  • ss:栈段
  • es:拓展段
  • fs:数据段
  • gs:数据段

这里我们列举一些基础的汇编指令,记住这些你就能勉强看懂一些程序的基础逻辑,以及方便我们进行接下来的学习。

数据传输指令

  • MOV 传送指令
    MOV dest, src :将数据从src移动到dest
  • PUSH压栈指令
    PUSH eax :将eax寄存器里的数据放入栈顶
  • POP弹栈指令
    POP ebx :将栈顶的数据放入ebx的寄存器。
  • LEA计算有效地址
    LEA rdi, LABEL :将LABEL的地址赋予rdi

算术运算指令

  • ADD加法指令
    ADD eax, ebx :将eax里的数据与ebx里的数据相加,结果放入前一个中。
  • SUB 减法指令
    SUB ecx, edxecx中的数据的基础上减去edx中的数据。
  • INC +1指令
    INC edxedx寄存器中的数据自增1
  • DEC -1指令
    DEX edxedx寄存器中的数据自减1

逻辑运算指令

  • NOT取反指令
    NOT eax :将eax中的数据的二进制按位取反%%比如数据为2,二进制表示为10,取反就是01%%
  • AND与运算
    AND eax, ebx :将eax中的数据与ebx中的数据做与运算后放回前一个。
  • OR 或运算
    OR eax, ebx :将eax中的数据与ebx中的数据做或运算后放回前一个。
  • XOR 异或运算
    XOR eax, ebx :将eax中的数据与ebx中的数据做异或运算后放回前一个。

循环控制指令

  • LOOP 计数循环指令
    LOOP Label :使ecx的值减1,当ecx的值不为0的时候跳转至label,否则执行LOOP之后的语句。

转移指令

  • JMP 无条件跳转指令
    JMP Label :无条件跳转到label的位置
  • CALL 过程调用指令
    CALL Label :等价于PUSH ip;JMP Label
  • JE & JNE :条件跳转指令
    JE Label:考察前一条指令的执行结果,JE为真跳转,JNE为假跳转

我们再看刚才的实验给出的程序

#include <stdio.h> int main{ printf("Hello World!"); return 0; }

学习了c语言,我们可以了解到c语言在编译执行时会生成一个.s文件,这就是c语言程序转换成的
汇编语言文件。

.LC0: .string "Hello World!" main: lea rdi, .LC0[rip] mov eax, 0 call printf@PLT mov eax, 0 ret

当然,实际的.s不会如此简洁,这里我们只保留了需要的部分。

  • .LC0可以看作一个储存着字符串常量的指针。
  • lea rdi, .LC0[rip]:将.LC0的指针地址赋予rdi,此时rdi装着一个指针,实际指向.LC0
  • mov eax, 0:将0赋予eax
  • call printf@plt :调用.plt表中记载的printf(),打印字符串。
  • ret :返回上一个函数。

下一节,我们介绍函数调用约定的相关内容,以及一些常用工具的使用技巧。

Have Fun​我们下个帖子再见!

网友解答:
--【壹】--:

又是俺,第一排支持,立即学习


--【贰】--:

支持支持


--【叁】--:

要长脑子了,真得严肃学习,是不是可以放到 文档共建 板块?


--【肆】--:

太强了,大佬


--【伍】--:

师傅写的很详细很贴心了,前排支持


--【陆】--:

前排支持大佬,太强了


--【柒】--:

在0-0-0的话题里有详细解释过。Pwn的本意是“二进制漏洞挖掘与利用”,是靠近计算机底层的安全技术。至于其名称的由来,一说是指在早期的黑客比赛中,Pwn由Own演变而来,意思是“砰”的一声,你的系统被攻克了!最早期的CTF比赛只有Pwn一种题型,某种意义上来说,Pwn是最原始的最Oldschool的一种安全技术。


--【捌】--:

大佬好厉害


--【玖】--:

pwn是什么意思?


--【拾】--:

有道理,我考虑再更几节就放进去


--【拾壹】--:

前排支持下教程