pwntools是什么
Python 编写的一个用于二进制利用的工具包
重点对象:PWN/CTF/渗透测试
提供便捷的 API,自动化部署,连接,构造 payload,ROP 分析,libc 查询等
安装命令:
pip install pwntools
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pwntools主要功能
测试程序
#include <stdio.h>
int main(){
char name[0x20]; printf("Enter Your Name:");
fflush(stdout);
scanf("%s",name);
printf("Hello %s!\n", name);
puts("this is puts!");
return 0;
}
编译命令:
gcc -fno-stack-protector -no-pie -z execstack -m32 test.c -o test本地连接
from pwn import *
r = process("./test") # 本地运行
r.interactive() # 交互式
与本地运行效果基本一致

远程连接
前提条件
该主机应该先将程序映射到对应的端口上
nc.traditional -lvp 1234 -e ./test
nc -lvp 1234 -e ./test远程连接端口
from pwn import *
# r = process("./test") # 本地运行
r = remote("192.168.48.139", 1234) # 远程 nc-like 连接
r.interactive() # 交互式

发送和接收数据
recvuntil:接受数据sendline:发送数据interactive:交互式shell
注意这边的发送数据要采用byte流发送,否则会失败
from pwn import *
# r = process("./test") # 本地运行
r = remote("192.168.48.139", 1234) # 远程 nc-like 连接
prompt = r.recvuntil(":",drop=True)
print("prompt==>:",prompt)
r.sendline(b"admin")
data = r.recv(1024).decode()
print("recv data:",data)
r.interactive() # 交互式
构造payload
p1 = b'A'*10+p32(0x080491d6)
p2 = b'B'*10+p64(0x401238)
print(p1,p2)总结系统功能
ELF
from pwn import *
# r = process("./test") # 本地运行
r = remote("192.168.48.139", 1234) # 远程 nc-like 连接
#prompt = r.recvuntil(":",drop=True)
#print("prompt==>:",prompt)
#r.sendline(b"admin")
#data = r.recv(1024).decode()
#print("recv data:",data)
#r.interactive() # 交互式
elf = ELF("./test")
print(elf.symbols['main'])
print(elf.got['puts'])
跟我们执行checksec差不多
ROP 链分析
from pwn import *
# r = process("./test") # 本地运行
r = remote("192.168.48.139", 1234) # 远程 nc-like 连接
#prompt = r.recvuntil(":",drop=True)
#print("prompt==>:",prompt)
#r.sendline(b"admin")
#data = r.recv(1024).decode()
#print("recv data:",data)
#r.interactive() # 交互式
elf = ELF("./test")
print(elf.symbols['main'])
print(elf.got['puts'])
rop = ROP(elf)
print(rop.call("puts", [elf.got['puts']]))
动态 shellcode 生成
可以生成对应需求的shellcode
shellcode = asm(shellcraft.sh())可以看到会生成一个获取sh的shellcode

pwntools实操
Lab 0
源码:
#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;
}MakeFile:
pwntools: pwntools.c
gcc pwntools.c -o pwntools源码分析
我们既然有源码,那么就先看一下源码
printf("Give me the magic number :)\n");
read(0, &magic, 4);
if (magic != 3735928559) {
printf("Bye~\n");
exit(0);
}这是第一个判断,我们要先通过magic的校验,不然就会直接弹出Bye,就像这样:

而我们输入的内容会被read函数读取,他只读取四个字节,也就是说我们如果直接传入3735928559也是不行的

应该传入十六进制的形式才行

但是这个又涉及到大端小端的排序问题,所以我们就采用pwntools来帮助我们实现
from pwn import *
r = process("./pwntools")
res = r.recvuntil(b":)")
print(f"recv data 1: {res}")
r.sendline(p32(3735928559))
print(f"send magic number: {p32(3735928559)}")
r.interactive()此处使用p32就可以帮我们自动实现转换

既然第一个if判断已经绕过了,那么让我们看看第二个限制
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");可以看到只有在90秒内计算出一千道题目的答案才能成功拿到shell,我们手算显然是不可能的,直接上脚本吧
from pwn import *
def calc(string):
left = string.split("=")[0].strip()
num1, opt, num2 = left.split()
ret = eval(num1 + opt + num2)
print(f"{num1} {opt} {num2} = {ret}")
return ret
r = process("./pwntools")
res = r.recvuntil(b":)").decode()
# receive the hint message
print(f"recv data hint1: {res}")
# send the magic number
r.sendline(p32(3735928559))
print(f"send magic number: {p32(3735928559)}")
# receive the hint message
res = r.recvuntil(b"!!!").decode()
r.recvline()
print(f"recv data hint2: {res}")
# loop
for i in range(1000):
res = r.recvuntil(b"?").decode()
ret = calc(res)
r.sendline(bytes(str(ret).encode()))
r.interactive()
成功拿下,此处其实就是在于提取计算表达式的问题,其他的问题倒是没有
不过我们最好不要使用eval函数进行一个计算处理,如果人家在1000道题目中参杂了一个反弹shell的语句,自己的服务器就被别人拿到权限了,所以对于calc函数还可以进行进一步的优化
from pwn import *
import ast
import operator
# 定义支持的操作符
ops = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.floordiv, # 或使用 truediv
}
def calc(string):
left = string.split('=')[0].strip()
num1_str, opt, num2_str = left.split()
num1 = ast.literal_eval(num1_str)
num2 = ast.literal_eval(num2_str)
if opt not in ops:
raise ValueError(f"Unsupported operator: {opt}")
if opt == '/' and num2 == 0:
raise ValueError("Division by zero")
ret = ops[opt](num1, num2)
print(f"{num1} {opt} {num2} = {ret}")
return ret
r = process("./pwntools")
res = r.recvuntil(b":)").decode()
# receive the hint message
print(f"recv data hint1: {res}")
# send the magic number
r.sendline(p32(3735928559))
print(f"send magic number: {p32(3735928559)}")
# receive the hint message
res = r.recvuntil(b"!!!").decode()
r.recvline()
print(f"recv data hint2: {res}")
# loop
for i in range(1000):
res = r.recvuntil(b"?").decode()
ret = calc(res)
r.sendline(bytes(str(ret).encode()))
r.interactive()
评论 (0)