i 春秋月刊第六期:Linux pwn 零基础入门 (it-ebooks) (z-library.sk, 1lib.sk, z-lib.sk)
Author: it-ebooks
Linux
No Description
📄 File Format:
PDF
💾 File Size:
11.7 MB
7
Views
0
Downloads
0.00
Total Donations
📄 Text Preview (First 20 pages)
ℹ️
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
📄 Page
1
(This page has no text content)
📄 Page
2
1 Linux pwn入门教程(0)——环境配置 02 Linux pwn入门教程(1)——栈溢出基础 09 Linux pwn入门教程(2)——shellcode的使用,原理与变形 19 Linux pwn入门教程(3)——ROP技术 32 Linux pwn入门教程(4)——调整栈帧的技巧 51 Linux pwn入门教程(5)——利用漏洞获取libc 60 Linux pwn入门教程(6)——格式化字符串漏洞 67 Linux pwn入门教程(7)——PIE与bypass思路 77 Linux pwn入门教程(8)——SROP 94 Linux pwn入门教程(9)——stack canary与绕过的思路 101 Linux pwn入门教程(10)——针对函数重定位流程的几种攻击 117 Tangerine@SAINTSEC表哥历时三月,耗费无数心血,经 历无数催稿的时刻方才完成此系列作品,在此我代表i春秋社区以 及所有学到知识的人对你致敬!感谢你的奉献,顺便祝你早日发财 23333。 特别感谢sp4ce表哥不辞辛苦,代作者在论坛发布本系列作品。
📄 Page
3
2 Linux pwn入门教程(0)——环境配置 作者:Tangerine@SAINTSEC 前言 作为一个毕业一年多的辣鸡CTF选手,一直苦于pwn题目的入门难,入了门更难的问题。本来网上关于pwn的 资料就比较零散,而且经常会碰到师傅们堪比解题过程略的writeup和没有注释,存在大量硬编码偏移的脚 本,还有练习题目难找,调试环境难搭建,GDB没有IDA好操作等等问题。作为一个老萌新(雾),决定依据 Atum师傅在i春秋上的pwn入门课程中的技术分类,结合近几年赛事中出现的一些题目和文章整理出一份自己 心目中相对完整的Linux pwn教程。 本系列教程仅针对i386/amd64下的Linux pwn常见的pwn手法,如栈,堆,整数溢出,格式化字符串,条件竞 争等进行介绍。为了方便和我一样的萌新们进行学习,所有环境都会封装在docker镜像当中,并提供调试用 的教学程序,来自历年赛事的原题和带有注释的python脚本。教程欢迎各位师傅吐槽,若对题目和脚本的使 用有不妥之处,会在当事师傅反馈之后致歉并应要求进行处理。 0x01 docker容器的使用与简单操作 在搭建环境之前我们需要准备一个装有docker的64位Linux系统,内核版本高于3.10(可以通过uname -r查 看),可以运行在实体机或者是虚拟机中。关于docker的安装与启动此处不再赘述,读者可以根据自己的 Linux发行版本自行搜索。此处提供两个链接,供Ubuntu和Kali使用者参考: Kali:《kali Rolling安装docker》http://www.cnblogs.com/Roachs/p/6308896.html Ubuntu:《Ubuntu 16.04安装Docker》http://blog.csdn.net/qq_27818541/article/details/73647797 在成功安装了docker并验证其可用性后,我们就可以定制自己的实验用容器了。这部分内容可以在各个地方 找到教程,且与pwn的学习不相关,此处不再赘述。为了方便实验,我把实验环境打包成了几个容器快照, 可以直接导入成镜像使用。 以ubuntu.17.04.amd64为例,导入的命令为 cat ubuntu.17.04.amd64 | docker import - ubuntu/17.04.amd64 导入成功后使用命令docker images会看到镜像仓库中出现了一个新的镜像。 运行docker run -it -p 23946:23946 ubuntu/17.04.amd64 /bin/bash 就可以以这个镜像创建一个容器,开启一个shell,并且将IDA调试服务器监听的23946端口转发到本地的 23946端口。 通过命令docker container ls -a 我们发现容器列表里多了一个刚刚创建的容器,并且被赋予了一个随机 的名字,在我的实验中它是nostalgic_raman。 我们可以通过命令docker container rename nostalgic_raman ubuntu.17.04.amd64把这个容器重命名为 ubuntu.17.04.amd64或者其他你认为合适的名字。
📄 Page
4
3 使用docker exec -it ubuntu.17.04.amd64 /bin/bash 我们可以打开目标容器的一个新的bash shell。这 使得我们在后续的调试中可以在容器中启动IDA调试服务器并用socat部署pwn题目。 此外,可以使用docker container cp命令在docker容器内外双向传输文件等等。需要注意的是,对容器的 各种操作需要在容器运行时进行,若容器尚未运行(运行docker container ls未显示对应容器),需使用命 令docker start运行对应容器。此外,若同时运行多个容器,为了避免端口冲突,在启动容器时,可以将命 令docker run -it -p 23946:23946 ubuntu/17.04.amd64 /bin/bash 中的第一个端口号23946改为其他数 字。 0x02 IDA的简单使用及远程调试配置 成功搭建了docker环境之后,我们接下来熟悉一下IDA和IDA的远程调试环境搭建。首先我们在IDA所在的文 件夹的dbgsrv文件夹下找到需要的调试服务器linux_server(32位)和linux_serverx64(64位)并复制到kali 中。 然后使用命令docker container cp linux_server ubuntu.17.04.i386:/root/linux_server 将linux_ server复制到32位容器中的/root目录下。此时我们登录容器可以看到linux_server,运行该server会提示 正在监听23946端口。 接着我们打开32位的ida,载入一个后面会用于演示堆漏洞的程序heapTest_x86,在左侧的Functions window中找到main函数,随便挑一行代码按F2下一个断点。然后通过Debugger->Process options...打开选 项窗口设置远程调试选项。
📄 Page
5
4 在弹出的选项窗口中配置Hostname为kali的ip地址,Port为容器映射到kali中的端口。 填好后点击OK,按快捷键F9运行程序。若连接正常可能提示Input file is missing:xxxxx,一路OK就 行,IDA会将被调试的文件复制到服务器所在目录下,然后汇编代码所在窗口背景会变成浅蓝色并且窗口布 局发生变化。若IDA僵死一段时间后跳出Warning窗口,则需要检查IDA所在机器与kali是否能ping通,容器 对应端口是否映射,参数是否填错等问题。
📄 Page
6
5 调试器连接成功后我们就可以使用各种快捷键对目标程序进行调试,常用的快捷键有 下断点/取消断点 F2 ,运行程序F9,单步跨过函数F8,单步进入函数F7,运行到选中位置F4等等。在调试模式下主要使用到的窗 口有汇编窗口 IDA View-EIP,寄存器窗口General registers,栈窗口Stack view,内存窗口Hex View,系 统日志窗口Output window等。 切回到kali,我们会看到随着程序运行,运行调试服务器的shell窗口会显示出新的内容 当IDA中的程序执行完call ___isoc99_scanf或者类似的等待输入的指令后会陷入阻塞状态,F4,F7,F8 ,F9等和运行相关的快捷键都不生效。此时我们可以在shell中输入内容,IDA中的程序即可恢复执行。 0x03 使用pwntools和IDA调试程序 在上一节中我们尝试了使用IDA配置远程调试,但是在调试中我们可能会有一些特殊的需求,比如自动化完 成一些操作或者向程序传递一些包含不可见字符的地址,如\x50\x83\x04\x08(0x08048350)。这个时候我们 就需要使用脚本来完成此类操作。我们选用的是著名的python库pwntools。 pwntools库可以使用pip进行安 装,其官方文档地址为http://docs.pwntools.com/en/stable/ 。在本节中我们将使用pwntools和IDA配合 调试程序。 首先我们在kali中安装pwntools,安装完成后输入python进入python环境,使用from pwn import * 导入 pwntools库。
📄 Page
7
6 使用docker exec在32位的容器中新开一个bash shell,跳转到heapTest_x86所在目录/root,查看容器的IP 地址,然后执行命令socat tcp-listen:10001,reuseaddr,fork EXEC:./heapTest_x86,pty,raw,echo=0将 heapTest_x86的IO转发到10001端口上。 我们可以看到我的容器中的IP地址是172.17.0.2。回到python中,使用io = remote("172.17.0.2", 10001) 打开与heapTest_x86的连接。 这个时候我们返回到IDA中设置断点。需要注意的是此时heapTest_x86已经开始运行,我们的目标是附加到 其运行的进程上,所以我们需要把断点设置在call ___isoc99_scanf等等待输入的指令运行顺序之后, 否则由于计算机的运行速度,我们的断点将会因为已经目标指令已经执行完而失效,达不到断下来的效果。 选择Debugger->Attach to process...,附加到./heapTest_x86的进程上。 此时EIP将指向vdso中的pop ebp指令上。
📄 Page
8
7 这几行指令实际上是执行完sys_read后的指令,此处我们不需要关心它,直接按F9,选中标志会消失。 回到python窗口,我们使用pwntools的recv/send函数族来与运行中的heapTest_x86进行交互。首先输入 io.recv(),我们发现原先会在shell窗口出现的菜单被读出到python窗口里了。 同样的,我们通过io.send()也可以向这个进程传递输入。我们使用io.send('1')告诉这个进程我们要选择 选项1。这个时候我们切换到IDA窗口,发现IDA还是处于挂起状态,这是为什么呢? 回想一下我们通过shell与这个进程交互的时候,输入选项后需要按回车键以“告诉”这个进程我们的输入 结束了。那么在这里我们同样需要再发送一个回车,所以我们再执行io.send('\n'),切换到IDA窗口就会 发现EIP停在了熟悉的程序领空。这时候我们再使用IDA的快捷键就可以进行调试,随心所欲地观察进程的内 存,栈,寄存器等的状态了。当然,我们也可以直接使用io.sendline(),就可以直接在输入的结尾自动加 上'\n'了。
📄 Page
9
8 在上图的状态中,我们在python中再次输入io.recv(),发现并没有读取到输出,并且python处于阻塞状 态。这是因为程序此时没有输出可读取。我们在IDA中按F8到call mallocChunk一行,此时按F7进入函数, 在函数中运行到call _fflush的下一行,就会发现python的阻塞状态解除了。 当我们希望结束调试时,应该使用io.close()关闭掉这个io。否则下一次试图attach时会发现有两个./ heapTest_x86进程。在IDA中按Ctrl+F2即可退出调试模式。 配置实验环境请点击跳转到原文下载
📄 Page
10
9 Linux pwn入门教程(1)——栈溢出基础 作者:Tangerine@SAINTSEC 0x00 函数的进入与返回 要想理解栈溢出,首先必须理解在汇编层面上的函数进入与返回。首先我们用一个简单执行一次回显输入的 程序hello开始。用IDA加载hello,定位到main函数后我们发现这个程序的逻辑十分简单,调用函数hello 获取输入,然后输出“hello,”加上输入的名字后退出。使用F5看反汇编后的C代码可以非常方便的看懂逻 辑。 我们选中IDA-View窗口或者按Tab键切回到汇编窗口,在main函数的call hello一行下断点,开启32位的 docker环境,启动调试服务器后直接按F9进行调试。 如图,这是当前IDA的界面。在这张图中我们需要重点注意到的东西有栈窗口,EIP寄存器,EBP寄存器和ESP 寄存器。 首先我们可以看到EIP寄存器始终指向下一条将要执行的指令,也就是说如果我们可以通过某种方式修改EIP 寄存器的值,我们就可以控制整个程序的执行,从而”pwn”掉程序(要验证这一点,我们可以在EIP后面的 数字上点击右键选择Modify value.......把数值改成080484DE然后F9继续执行,从而跳过call hello一行) 。 剩下的东西都和栈相关。顾名思义,栈就是一个数据结构中的栈结构,遵循先入后出的规则。这个栈的最小
📄 Page
11
10 单位是函数栈帧。一个函数栈帧的结构如图所示: 栈的生长方式是向低地址生长,也就是说这张图的方向和IDA中栈窗口的方向是一样的,越往上地址值越 小。同样的,新入栈的栈帧在IDA的窗口中会把原来的栈帧“压”在下面。ESP和EBP两个寄存器负责标定当 前栈帧的范围。图中标黑的部分即为实际上ESP和EBP中间的最大区域(为了方便讲解,我们把EIP和参数也 列入一个函数的函数栈帧)。图中的局部变量和参数很好理解,但EBP和EIP又是什么意思呢?我们回到IDA 调试窗口。按照程序的逻辑,接下来应该是执行call hello这行指令调用hello这个函数,函数执行完后回 到下一行的mov eax, 0,其地址为080484DE.然后我们再把当前ESP和EBP的值记下来(受地址空间随机化ASLR 的影响,每台电脑每次运行到此处的ESP和EBP值不一定相同),然后按F7进入hello函数。
📄 Page
12
11 如图,执行完call hello这一行指令后发生了如下改变。由此我们可以得知call指令是可以改变EIP“始终 指向下一条指令地址”的行为的,且call指令会把call下一条指令地址压栈。我们可以理解为call hello等 价于push eip; mov eip, [hello]。所以我们的第一个问题“栈帧中的EIP是什么意思”的回答就是:栈帧 中的EIP是call指令的下一条指令的地址。我们继续F8单步执行。
📄 Page
13
12 如图,通过依次执行三条指令,程序为hello函数开辟了新的栈帧,同时把原来的栈帧,即执行了call hello函数的main函数的栈帧的栈底EBP保存到栈中。继续往下执行到read函数,然后随便输入一些比较有标 志性的内容,比如12345678,我们就会发现存储输入的局部变量buf就在这片新开辟的栈帧中。
📄 Page
14
13 我们已经接触到了栈帧的开辟与被使用情况,接下来我们再通过调试继续学习栈帧的销毁。继续F8到leave 一行,此时我们会发现栈帧再次回到了刚执行完sub esp, 18h的状态。 执行完leave一行指令后栈帧被销毁,整体状态回到了call hello执行前的状态。即leave指令相当于add esp, xxh; mov esp, ebp; pop ebp
📄 Page
15
14 再次F8,发现EIP指向了call hello的下一行指令,同时栈中保存的EIP值被弹出,栈顶地址+4. 即retn等同 于pop eip 此时hello函数代码执行完毕,控制流程返回到了调用hello函数的main函数中。 0x01 栈溢出实战 通过上一节的调试,我们大概理解了函数栈的初始化和销毁过程。我们发现随着我们的输入变多,输入的内 容离栈上保存的EIP地址越来越近,那么我们可不可以通过输入修改掉栈上的EIP地址,从而在retn指令执行 完后“pwn”掉程序呢?我们按Ctrl+F2结束掉当前的调试,再试一次。为了节约时间,这回我们直接把断点 下在hello函数里的call _read一行。 启动调试,程序中断后界面如下
📄 Page
16
15 通过观察read函数的参数和栈中的保存的EIP地址,我们计算出两者的偏移是0x16个字节,也就是说输入 0x16=22个字节的数据,我们的输入就会和栈中的EIP“接上”,输入22+4=26个字节,我们的输入就会覆盖 掉EIP。那么我们构造payload为‘A’*22+‘B’*4,即AAAAAAAAAAAAAAAAAAAAAABBBB,根据我们的推测,在 EIP寄存器指向retn指令所在地址时,栈顶应该是‘BBBB’。即retn执行完之后,EIP里的值将不再是图中框 起来的080484DE,而是42424242(BBBB的ASCII值),按F8使IDA挂起,在docker环境中输入payload 栈中的EIP果然按照我们的推测被修改成42424242了。显然,这是一个非法的内存地址,它所在的内存页此 时对我们来说并没有访问权限,所以我们运行完retn后程序将会报错。 选择OK,继续F8并且选择将错误传递给系统,这个进程接收到信号后将会结束,调试结束。我们通过一个程 序本身的bug构造了一个特殊输入结束掉了它。 0x02 结合pwntools打造一个远程代码执行漏洞exp
📄 Page
17
16 通过上一节的内容,我们已经可以做到远程使一个程序崩溃。不要小看这个成果。如果我们能挖掘到安全软 件或者系统的漏洞从而使其崩溃,我们就可以让某些保护失效,从而使后面的入侵更加轻松。当然,我们也 不应该满足于这个成果,如果可以继续扩大这个漏洞的利用面,制造一个著名的RCE(远程代码执行),为 所欲为,岂不是更好?当然,CTF中的绝大部分pwn题也同样需要通过暴露给玩家的一个IP地址和端口号的组 合,通过对端口上运行的程序进行挖掘,使用挖掘到的漏洞使程序执行不该执行的代码,从而获取到flag, 这也是我们学习的目标。 为了降低难度,我在编写hello这个小程序的时候已经预先埋了一个后门——位于0804846B的名为getShell 的函数。 如图,这个函数唯一的作用就是调用system("/bin/sh")打开一个bash shell,从而可以执行shell命令与系 统本身进行交互 正常的程序流程并不会调用这个函数,所以我们将会利用上一节中发现的漏洞劫持程序执行流程,从而执行 getShell函数。 首先我们把hello的IO转发到10001端口上 然后我们从docker环境中获取其ip地址(我的是172.17.0.2,不同环境下可能不同) 然后在kali中启动python,导入pwntools库并且打开一个与docker环境10001端口(即hello程序)的连接 此时我们可以像上一篇文章一样打开IDA进行附加调试,在这里我就不再次演示了。从上一节的分析我
📄 Page
18
17 们知道payload的组成应该是22个任意字符+地址。但是我们要怎么把16进制数表示的地址转换成4个字 节的字符串呢?我们可以选用structs库,当然pwntools提供了一个更方便的函数p32()(即pack32位地 址,同样的还有unpack32位地址的u32()以及不同位数的p16(),p64()等等),所以我们的payload就是 22*'A'+p32(0x0804846B)。 由于读取输入的函数是read,我们在输入时不需要以回车作为结束符(printf,getc,gets等则需要),我们 使用代码io.send(payload)向程序发送payload 由于我在这里没有设置IDA附加调试,显然程序也不会被断点中断,那么这个时候hello回显我们的输入 之后应该成功地被payload劫持,跳转到getShell函数上了。为了与被pwn掉的hello进行交互,我们使用 io.interactive()
📄 Page
19
18 可以看到我们已经成功地pwn掉了这个程序,取得了其所在环境的控制权。为了增加一点气氛,我们在/home 下面放了一个flag文件。让我们来看一下flag是啥 如图,我们成功地做出了第一个pwn题。为了加深对栈溢出的理解,我选了几个真实的CTF赛题作为作业,注 意不要将思维固定在获取shell上哦。 附件(课后例题和练习题请点击跳转到原文下载)
📄 Page
20
19 Linux pwn入门教程(2)——shellcode的使用,原理与变形 作者:Tangerine@SAINTSEC 0x00 shellcode的使用 在上一篇文章中我们学习了怎么使用栈溢出劫持程序的执行流程。为了减少难度,演示和作业题程序里都带 有很明显的后门。然而在现实世界里并不是每个程序都有后门,即使是有,也没有那么好找。因此,我们就 需要使用定制的shellcode来执行自己需要的操作。 首先我们把演示程序~/Openctf 2016-tyro_shellcode1/tyro_shellcode1复制到32位的docker环境中并开启 调试器进行调试分析。需要注意的是,由于程序带了一个很简单的反调试,在调试过程中可能会弹出如下窗 口: 此时点OK,在弹出的Exception handling窗口中选择No(discard)丢弃掉SIGALRM信号即可。 与上一篇教程不同的是,这次的程序并不存在栈溢出。从F5的结果上看程序使用read函数读取的输入甚至都 不在栈上,而是在一片使用mmap分配出来的内存空间上。
The above is a preview of the first 20 pages. Register to read the complete e-book.
Recommended for You
Loading recommended books...
Failed to load, please try again later