健全 發表於 2024-10-23 22:47:00

汇编语言

<p>pwn.college 网络安全学院 --- pwn.college</p>
<h1 id="寄存器">寄存器</h1>
<h2 id="正经理解">正经理解</h2>
<table>
<thead>
<tr>
<th style="text-align: left">寄存器</th>
<th style="text-align: left">主要用途</th>
<th style="text-align: left">编号</th>
<th style="text-align: left">存储数据的范围</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">EAX</td>
<td style="text-align: left">累加器</td>
<td style="text-align: left">0</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">ECX</td>
<td style="text-align: left">计数</td>
<td style="text-align: left">1</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">EDX</td>
<td style="text-align: left">I/O指针</td>
<td style="text-align: left">2</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">EBX</td>
<td style="text-align: left">DS段的数据指针</td>
<td style="text-align: left">3</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">ESP</td>
<td style="text-align: left">堆栈指针</td>
<td style="text-align: left">4</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">EBP</td>
<td style="text-align: left">SS段的数据指针</td>
<td style="text-align: left">5</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">ESI</td>
<td style="text-align: left">字符串操作的源指针;SS段的数据指针</td>
<td style="text-align: left">6</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
<tr>
<td style="text-align: left">EDI</td>
<td style="text-align: left">字符串操作的目标指针;ES段的数据指针</td>
<td style="text-align: left">7</td>
<td style="text-align: left">0 - 0xFFFFFFFF</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align: left">寄存器</th>
<th style="text-align: left"></th>
<th style="text-align: left">编号(二进制)</th>
<th style="text-align: left">编号(十进制)</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">32位</td>
<td style="text-align: left">16位</td>
<td style="text-align: left">8位</td>
<td style="text-align: left"></td>
<td></td>
</tr>
<tr>
<td style="text-align: left">EAX</td>
<td style="text-align: left">AX</td>
<td style="text-align: left">AL</td>
<td style="text-align: left">000</td>
<td>0</td>
</tr>
<tr>
<td style="text-align: left">ECX</td>
<td style="text-align: left">CX</td>
<td style="text-align: left">CL</td>
<td style="text-align: left">001</td>
<td>1</td>
</tr>
<tr>
<td style="text-align: left">EDX</td>
<td style="text-align: left">DX</td>
<td style="text-align: left">DL</td>
<td style="text-align: left">010</td>
<td>2</td>
</tr>
<tr>
<td style="text-align: left">EBX</td>
<td style="text-align: left">BX</td>
<td style="text-align: left">BL</td>
<td style="text-align: left">011</td>
<td>3</td>
</tr>
<tr>
<td style="text-align: left">ESP</td>
<td style="text-align: left">SP</td>
<td style="text-align: left">AH</td>
<td style="text-align: left">100</td>
<td>4</td>
</tr>
<tr>
<td style="text-align: left">EBP</td>
<td style="text-align: left">BP</td>
<td style="text-align: left">CH</td>
<td style="text-align: left">101</td>
<td>5</td>
</tr>
<tr>
<td style="text-align: left">ESI</td>
<td style="text-align: left">SI</td>
<td style="text-align: left">DH</td>
<td style="text-align: left">110</td>
<td>6</td>
</tr>
<tr>
<td style="text-align: left">EDI</td>
<td style="text-align: left">DI</td>
<td style="text-align: left">BH</td>
<td style="text-align: left">111</td>
<td>7</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align: left"><strong>寄存器</strong></th>
<th style="text-align: left"><strong>32 位 (IA-32)</strong></th>
<th style="text-align: left"><strong>64 位 (x86-64)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><strong>通用寄存器</strong></td>
<td style="text-align: left">EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP</td>
<td style="text-align: left">RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8-R15</td>
</tr>
<tr>
<td style="text-align: left"><strong>段寄存器</strong></td>
<td style="text-align: left">CS, DS, SS, ES, FS, GS</td>
<td style="text-align: left">CS, DS, SS, ES, FS, GS</td>
</tr>
<tr>
<td style="text-align: left"><strong>控制寄存器</strong></td>
<td style="text-align: left">EFLAGS, EIP</td>
<td style="text-align: left">RFLAGS, RIP</td>
</tr>
<tr>
<td style="text-align: left"><strong>指针寄存器</strong></td>
<td style="text-align: left">ESP, EBP</td>
<td style="text-align: left">RSP, RBP</td>
</tr>
</tbody>
</table>
<h2 id="个人理解">个人理解</h2>
<h3 id="rip">RIP</h3>
<p>存放下一条指令的偏移地址</p>
<h3 id="rsp">RSP</h3>
<p>存放当前栈帧的栈顶偏移地址</p>
<h3 id="rbp">RBP</h3>
<p>存放当前栈帧的栈底偏移地址</p>
<h3 id="rax">RAX</h3>
<p>通用寄存器。存放函数的返回值</p>
<h2 id="寄存器的关系">寄存器的关系</h2>
<pre><code>MSB                                    LSB
+----------------------------------------+
|                   rax                  |
+--------------------+-------------------+
                     |      eax      |
                     +---------+---------+
                               |   ax    |
                               +----+----+
                               | ah | al |
                               +----+----+
</code></pre>
<h1 id="汇编">汇编</h1>
<p><strong>实际中执行指令时,EIP已经移动到下一行了</strong></p>
<h2 id="标志位">标志位</h2>
<p>更新标志位的操作:</p>
<ul>
<li>大多数算术指令。</li>
<li>比较指令cmp(相减,但丢弃结果)。</li>
<li>比较指令test(想与,但丢弃结果)。</li>
</ul>
<h2 id="mov">mov</h2>
<p><code>mov</code> 指令要求目标寄存器必须是 <code>rax</code>、<code>rbx</code>、<code>rcx</code>、<code>rdx</code>、<code>r8</code>、<code>r9</code>、<code>r10</code>、<code>r11</code>、<code>r12</code>、<code>r13</code>、<code>r14</code> 或 <code>r15</code> 中的一个。</p>
<p>在 x86-64 汇编中,<strong>不能直接将大于 1 字节的数据</strong>(如 <code>qword</code> 或 <code>dword</code>)直接存储到内存地址。必须先将数据加载到寄存器中,然后再通过 <code>qword ptr</code>、<code>dword ptr</code> 等操作符将寄存器中的值存储到指定的内存地址。</p>
<p>所以想要执行 <code> = 0xdeadbeef00001337</code> <code> = 0xc0ffee0000</code>操作就要先赋值给额外的寄存器,再把额外的寄存器给目标寄存器的地址。<br>
而不是赋值给地址再把地址给目标寄存器的地址</p>
<pre><code class="language-assembly">mov r8,0xdeadbeef00001337
mov r9,0xc0ffee0000
mov ,r8
mov ,r9
</code></pre>
<h2 id="add">add</h2>
<p><code>add</code> 指令的两个操作数必须满足以下条件之一:</p>
<ol>
<li>两个操作数都是寄存器。</li>
<li>一个操作数是寄存器,另一个操作数是立即数。</li>
<li>一个操作数是寄存器,另一个操作数是内存地址。</li>
</ol>
<p>但不能是:</p>
<ul>
<li>一个操作数是寄存器,另一个操作数是内存地址的<strong>直接内容</strong>。</li>
</ul>
<p>也就是如果要实现<strong><code>add rsi, </code></strong><br>
就要先将内存中的值加载到一个寄存器中,然后再进行加法操作。</p>
<pre><code class="language-assembly">    ; 将 rdi + 8 处的四字数据加载到 rbx
    mov rbx,
    ; 将 rbx 的值加到 rsi 中
    add rsi, rbx
</code></pre>
<h2 id="mulimul">mul/imul</h2>
<p>表示乘法</p>
<p><code>imul rax rdx</code> <code>rax*=rdx</code></p>
<h2 id="div">div</h2>
<p>表示除法</p>
<p><code>div</code> 是一种特殊指令,可以在一个寄存器操作数的情况下,将 128 位被除数除以 64 位除数,同时存储商和余数。</p>
<p>对于<code>div rag</code>等效于</p>
<ul>
<li><code>rax = rdx:rax / reg</code></li>
<li><code>rdx = remainder</code></li>
</ul>
<p><code>rdx:rax</code> 表示 <code>rdx</code> 将是 128 位被除数的最高 64 位,而 <code>rax</code> 将是 128 位被除数的最低 64 位。</p>
<p>也就是计算完成后<code>rax</code>存放商,<code>rdx</code>存放余数</p>
<p>所以在调用 <code>div</code> 之前必须小心查看 <code>rdx</code> 和 <code>rax</code> 中的内容。</p>
<h2 id="rep">rep</h2>
<p><code>rep</code> 前缀会重复执行后续的字符串操作指令,重复次数由 <code>ECX</code> 寄存器指定。每次执行后,<code>ECX</code> 减 1,直到 <code>ECX</code> 为 0 时停止。</p>
<pre><code>rep &lt;字符串操作指令&gt;
</code></pre>
<p>以下是 <code>rep</code> 常见的组合指令及其作用:</p>
<table>
<thead>
<tr>
<th style="text-align: left">指令</th>
<th style="text-align: left">功能描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code>rep movsb</code></td>
<td style="text-align: left">按字节从源地址 (<code>DS:ESI</code>) 复制到目标地址 (<code>ES:EDI</code>),重复 <code>ECX</code> 次</td>
</tr>
<tr>
<td style="text-align: left"><code>rep movsw</code></td>
<td style="text-align: left">按字(2字节)复制,地址每次增减 2</td>
</tr>
<tr>
<td style="text-align: left"><code>rep movsd</code></td>
<td style="text-align: left">按双字(4字节)复制,地址每次增减 4</td>
</tr>
<tr>
<td style="text-align: left"><code>rep stosb</code></td>
<td style="text-align: left">将 <code>AL</code> 的值填充到 <code>ES:EDI</code> 指向的内存,重复 <code>ECX</code> 次</td>
</tr>
<tr>
<td style="text-align: left"><code>rep stosw</code></td>
<td style="text-align: left">将 <code>AX</code> 的值填充到内存,地址每次增减 2</td>
</tr>
<tr>
<td style="text-align: left"><code>rep stosd</code></td>
<td style="text-align: left">将 <code>EAX</code> 的值填充到内存,地址每次增减 4</td>
</tr>
<tr>
<td style="text-align: left"><code>rep cmpsb</code></td>
<td style="text-align: left">逐字节比较 <code>DS:ESI</code> 和 <code>ES:EDI</code> 的内容,直到不匹配或 <code>ECX</code> 归零</td>
</tr>
<tr>
<td style="text-align: left"><code>rep scasb</code></td>
<td style="text-align: left">在 <code>ES:EDI</code> 指向的内存中搜索 <code>AL</code> 的值,直到找到或 <code>ECX</code> 归零</td>
</tr>
</tbody>
</table>
<p>字符串操作指令的地址增减方向由 <strong>方向标志位(DF)</strong> 决定:</p>
<ul>
<li><strong><code>cld</code> 指令</strong>:清除方向标志(<code>DF=0</code>),使地址递增(<code>ESI</code>/<code>EDI</code> 增加)。</li>
<li><strong><code>std</code> 指令</strong>:设置方向标志(<code>DF=1</code>),使地址递减(<code>ESI</code>/<code>EDI</code> 减少)。</li>
</ul>
<h2 id="cmp">cmp</h2>
<table>
<thead>
<tr>
<th>语句</th>
<th>cmp a1,0x61</th>
<th>sub a1,0x61</th>
</tr>
</thead>
<tbody>
<tr>
<td>本质</td>
<td>a1-0x61</td>
<td>a1=a1-0x61</td>
</tr>
<tr>
<td>不同</td>
<td>a1不会赋值</td>
<td>a1会赋值</td>
</tr>
</tbody>
</table>
<p>cmp只会判断减去后值是不是为0</p>
<h2 id="lea">lea</h2>
<p>把寄存器存储的地址进行加减操作后赋值给另外一个寄存器</p>
<p>lea rax, :rax=rbp-0x18</p>
<h3 id="为什么不用sub">为什么不用sub</h3>
<ol>
<li>如果用sub,需要sub rbp,0x18;mov rax,rbp;add rbp,0x18,指令太长了。</li>
<li>mov rax,会直接把存储的信息赋值给rax,而不是地址</li>
</ol>
<h2 id="xor">xor</h2>
<p>xor eax,eax:让eax等于0</p>
<h3 id="不用mov-ebx-0">不用mov ebx 0</h3>
<p>xor eax,eax短</p>
<h2 id="test">test</h2>
<p>test eax,eax =&gt; cmp eax,0    exa=0 -&gt; 0    eax!=0 -&gt; !0</p>
<p>相当于and eax,eax,但是不赋值给eax</p>
<pre><code class="language-assembly">test rax, rax; jnz STAY_LEET # rax != 0
</code></pre>
<h2 id="push">push</h2>
<p>push ebp : esp-4,把ebp放入esp所指的位置</p>
<h2 id="pop">pop</h2>
<p>pop ebp :把esp指向的地方赋给ebp,esp+4。esp往下挪</p>
<h2 id="push和pop其他相关指令">push和pop其他相关指令</h2>
<p>pusha:将所有的16位通用寄存器压入堆栈</p>
<p>popa:将所有的16位通用寄存器取出堆栈</p>
<p>pushf::将的16位标志寄存器EFLAGS压入堆栈</p>
<p>popf:将16位标志寄存器EFLAGS取出堆栈</p>
<p>pushad:将所有的32位通用寄存器压入堆栈,方面后面随意使用寄存器,用于保护现场</p>
<h2 id="jump">jump</h2>
<p>junp 0x11111 -&gt; mov eip 0x11111</p>
<h3 id="绝对跳跃">绝对跳跃</h3>
<h4 id="方式-1通过寄存器实现绝对跳转">方式 1:通过寄存器实现绝对跳转</h4>
<p>这种方式是将目标地址先放入一个寄存器,然后使用 <code>jmp</code> 指令跳转到该寄存器中的地址。这种方式在某些情况下(比如目标地址是动态计算的)非常有用。</p>
<h4 id="方式-2直接在-jmp-指令中使用绝对地址"><strong>方式 2:直接在 <code>jmp</code> 指令中使用绝对地址</strong></h4>
<p>这种方式是直接在 <code>jmp</code> 指令中指定目标地址。这种方式更简洁,适用于目标地址是已知的常量的情况。</p>
<h3 id="相对跳跃">相对跳跃</h3>
<p>相对跳转到0x51字节字后的位置</p>
<pre><code class="language-assembly">.intel_syntax noprefix
.global _start
_start:

jmp .target

.rept 0x51 ;.rept指令,重复以下指令0x51次
nop
.endr

.target:
mov rax,0x1
</code></pre>
<h2 id="call">call</h2>
<p>call : push call的下一条指令(返回地址); jump func;</p>
<h2 id="leave">leave</h2>
<p>leave : mov esp,ebp;pop ebp;</p>
<p>把esp移到ebp的位置,ebp再到此时栈顶(esp)的地址上,然后esp再加4。也就是往下挪</p>
<h2 id="ret">ret</h2>
<p>pop eip   ; 从栈中弹出返回地址并存入 EIP</p>
<p>到ret命令时,esp在哪,就把哪里的地址存储的参数给eip</p>
<p>把esp给eip,同时esp加4</p>
<h2 id="对应关系">对应关系</h2>
<p>push ebp; --- pop ebp;</p>
<p>push ebp;pop ebp; --- leave</p>
<p>call; --- ret</p>
<h2 id="cdqcdqe">cdq/cdqe</h2>
<p>cdq是32位,cdqe是64位</p>
<p>作用都是将一个32位有符合数扩展为64位有符合数,数据能表示的数不变</p>
<p>具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。</p>
<h1 id="传参">传参</h1>
<h2 id="64函数调用传参">64函数调用传参</h2>
<p>rdi-&gt;rsi-&gt;rdx-&gt;rcx-&gt;r8-&gt;r9</p>
<h2 id="系统调用参数">系统调用参数</h2>
<p>rax:系统调用号,返回值</p>
<p>rdi-&gt;rsi-&gt;rdx-&gt;r10-&gt;r8-&gt;r9</p>
<h1 id="字节大小">字节大小</h1>
<ul>
<li>Quad Word = 8 Bytes = 64 bits</li>
<li>Double Word = 4 bytes = 32 bits</li>
<li>Word = 2 bytes = 16 bits</li>
<li>Byte = 1 byte = 8 bits</li>
</ul>
<h1 id="地址表示">地址表示</h1>
<p>段寄存器:在 x86 架构中,内存地址是由 <strong>段寄存器</strong> 和 <strong>偏移量</strong> 共同组成的。</p>
<h3 id="默认段寄存器的规则">默认段寄存器的规则</h3>
<p>在 x86 汇编中,内存操作数的默认段寄存器取决于基址寄存器:</p>
<ul>
<li>如果使用 <code>ebp</code> 或 <code>esp</code> 作为基址寄存器,默认段寄存器是 <code>ss</code>(栈段)。</li>
<li>如果使用其他寄存器(如 <code>eax</code>、<code>ebx</code> 等),默认段寄存器是 <code>ds</code>(数据段)。</li>
</ul>
<p>比如,对于 <code></code>,默认段寄存器是 <code>ss</code>,而不是 <code>ds</code>。如果你明确想访问 <code>ds</code> 段中的数据,则需要加上 <code>ds:</code> 前缀。</p>
<h3 id="对于dword-ptr-dsebp-4">对于<code>dword ptr ds:</code></h3>
<p>ds是段寄存器</p>
<p>是否可以去掉 <code>ds:</code>?</p>
<ul>
<li>大多数情况下可以去掉:
<ul>
<li>在 <strong>现代操作系统</strong>(如 Windows、Linux)的 <strong>保护模式</strong> 下,<code>ds</code>(数据段寄存器)和 <code>ss</code>(栈段寄存器)的值 <strong>通常是相同的</strong>。这是因为现代操作系统使用 <strong>平坦内存模型(Flat Memory Model)</strong>,所有段寄存器都被设置为指向同一个线性地址空间。</li>
<li>因此,<code>ds:</code> 通常是默认的,可以省略不写。</li>
</ul>
</li>
<li>某些情况下不能去掉:
<ul>
<li>如果程序中使用了多个段(例如在实模式或某些特殊场景下),则需要明确指定段寄存器(如 <code>ds:</code>、<code>cs:</code>、<code>es:</code> 等)。</li>
<li>如果汇编器无法确定默认的段寄存器,则需要显式加上 <code>ds:</code></li>
</ul>
</li>
</ul>
<h1 id="位置无关代码pic">位置无关代码(PIC)</h1>
<p>拿这个创建socket然后bind的汇编语言的<code>lea rsi, </code>代码举例子</p>
<pre><code class="language-assembly">.intel_syntax noprefix
.globl _start

.section .text

_start:
    # Socket syscall
    mov rdi, 2
    mov rsi, 1
    mov rdx, 0
    mov rax, 0x29
    syscall

    # Bind syscall
    mov rdi, 3
    lea rsi,
    mov rdx, 16
    mov rax, 0x31
    syscall

    # Exit syscall
    mov rdi, 0
    mov rax, 0x3c
    syscall

.section .data
sockaddr:
    .2byte 2
    .2byte 0x5000
    .4byte 0
    .8byte 0
</code></pre>
<h2 id="为什么要使用">为什么要使用</h2>
<ul>
<li>
<p>位置无关代码(PIC)的需求</p>
<p>现代操作系统和程序(尤其是共享库、动态链接库)通常需要支持 <strong>位置无关代码</strong>。这意味着代码可以被加载到内存中的任意位置运行,而不需要修改代码中的地址。</p>
<ul>
<li>如果直接使用绝对地址(例如 <code>mov rsi, sockaddr</code>),代码只能在固定的内存地址运行。</li>
<li>使用 <code>rip + sockaddr</code> 的相对寻址方式,代码可以在内存中的任何位置运行,因为地址是动态计算的。</li>
</ul>
</li>
<li>
<p>动态加载和共享库</p>
<p>在动态链接库(<code>.so</code> 文件)或共享库中,代码和数据的内存地址在编译时是未知的,只有在运行时才能确定。使用 <code>rip + sockaddr</code> 的方式,可以避免硬编码绝对地址,从而支持动态加载。</p>
</li>
<li>
<p>安全性</p>
<p>现代操作系统通常使用 <strong>地址空间布局随机化(ASLR)</strong> 技术,将程序的代码和数据加载到随机的内存地址,以增加安全性。如果代码中使用了绝对地址,ASLR 将无法正常工作。而使用 <code>rip + sockaddr</code> 的相对寻址方式,可以兼容 ASLR。</p>
</li>
</ul>
<h2 id="相对寻址">相对寻址</h2>
<h3 id="原理">原理</h3>
<p>在 x86-64 汇编中,<code>rip + offset</code> 是一种相对寻址方式。它的计算方式是:</p>
<pre><code>实际地址 = 下一条指令的地址 + offset
</code></pre>
<p>这里的 <code>offset</code> 是一个固定的偏移量,表示从下一条指令的地址到目标地址的距离。</p>
<p>在这个例子中,汇编器和链接器会根据 <code>sockaddr</code> 的地址和 <code>rip</code> 的值,计算出正确的偏移量,并将其编码到指令中。</p>
<ul>
<li><code>rip</code> 是指令指针寄存器,指向下一条指令的地址。</li>
<li><code>sockaddr</code> 是一个标签,表示 <code>sockaddr</code> 结构体的地址。</li>
<li><code>rip + sockaddr</code> 中的 <code>sockaddr</code> 实际上是一个偏移量,表示从下一条指令的地址到 <code>sockaddr</code> 地址的距离。</li>
</ul>
<p>当 CPU 执行指令时,会根据 <code>rip</code> 的值和偏移量动态计算出 <code>sockaddr</code> 的地址。</p>
<p>因此,即使 <code>rip</code> 的值在每个地方都不一样,<code>rip + sockaddr</code> 仍然能够正确计算出 <code>sockaddr</code> 的地址。</p>
<h3 id="总结">总结</h3>
<p>开启 ASLR 时,需要使用 <code>rip + offset</code> 动态计算地址。</p>
<p>未开启 ASLR 时,可以直接使用绝对地址。</p><br><br>
来源:https://www.cnblogs.com/r0xy/p/18498527
頁: [1]
查看完整版本: 汇编语言