loading

【笔记】pwn.college之Pwntools Tutorials(pwn.college)

Pwntools Tutorials Pwntools 教程

Level 0.0

此关卡是一个教程且相对简单。你可以直接在终端中运行 /challenge/pwntools-tutorials-level0.0,然后输入特定字符串(通过阅读 bypass_me 函数可以找到),但这不是本关卡的目标。

本关卡将指导你如何使用 pwntools 完成挑战。接下来,你需要使用 pwntools 中的 processsendrecv 等 API 来编写一个漏洞利用脚本,发送特定输入以绕过检查,并读取 /flag。请参考以下 pwntools 示例代码(提示:请务必将 FIXME 替换为上述特定字符串):

from pwn import *

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level0.0"
p = process(challenge_path)

payload = b'FIXME'
# 在找到字符串 ":)\n###\n" 后发送 payload。
p.sendafter(":)\n###\n", payload)

# 从进程接收标志
flag = p.recvline()
print(f"flag is: {flag}")
查看解析
cat /challenge/pwntools-tutorials-level0.0.c
得知`bypass_me`函数需要传参`pokemon`
from pwn import *

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level0.0"
p = process(challenge_path)

payload = b'pokemon\n'
# 在找到字符串 ":)\n###\n" 后发送 payload。
p.sendafter(":)\n###\n", payload)

# 从进程接收标志
flag = p.recvline()
print(f"flag is: {flag}")

Level 1.0 关卡 1.0

此关卡要求你阅读挑战中的 bypass_me 函数,并使用 pwntools 完成挑战。接下来,你需要使用 pwntools 中的 p64p32p16p8 等 API 来编写漏洞利用脚本,发送特定输入以绕过检查,并读取 /flag

int bypass_me(char *buf)
{
        unsigned int magic = 0xdeadbeef;

        if (!strncmp(buf, (char *)&magic, 4)) {
                return 1;
        }

        return 0;
}
查看解析
cat /challenge/pwntools-tutorials-level0.0.c
magic = 0xdeadbeef 是一个无符号整数(4字节)
strncmp(buf, (char *)&magic, 4) 比较前4个字节
需要发送 0xdeadbeef 的字节表示(小端序),可使用p32(0xdeadbeef)
在x86-64架构中,使用小端字节序:0xdeadbeef → \xef\xbe\xad\xde
from pwn import *

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level1.0"
p = process(challenge_path)

# 使用p32将整数打包为4字节小端序
payload = p32(0xdeadbeef) + b'\n'

# 在找到字符串 ":)\n###\n" 后发送 payload。
p.sendafter(":)\n###\n", payload)

# 从进程接收标志
flag = p.recvline()
print(f"flag is: {flag}")

Level 1.1 关卡 1.1

此关卡要求你阅读挑战中的 bypass_me 函数,并使用 pwntools 完成挑战。你需要使用 Python 字符串拼接以及 pwntools 中的 p64p32p16p8 API 来编写漏洞利用脚本,发送特定输入以绕过检查,并读取 /flag

int bypass_me(char *buf)
{
        int flag = 1;
        int num;

        if (buf[0] != 'p' || buf[1] != 0x15) {
                flag = 0;
                goto out; 
        }

        memcpy(&num, buf + 2, 4);
        if (num != 123456789) {
                flag = 0;
                goto out;
        }

        if (strncmp(buf + 6, "Bypass Me:)", 11)) {
                flag = 0;
                goto out;
        }

out:
        return flag;
}
查看解析
cat /challenge/pwntools-tutorials-level0.0.c
buf[0] 必须是字符 'p'(ASCII码 0x70)
buf[1] 必须是十六进制值 0x15
buf[2] 到 buf[5] 的4个字节必须是小端序表示的整数 123456789
buf[6] 到 buf[16] 的11个字节必须是字符串 "Bypass Me:)"
from pwn import *

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level1.1"
p = process(challenge_path)

payload = b'p'                    # buf[0] = 'p'
payload += b'\x15'                # buf[1] = 0x15
payload += p32(123456789)         # buf[2]到buf[5] = 123456789的小端序
payload += b'Bypass Me:)'         # buf[6]到buf[16] = "Bypass Me:)"
payload += b'\n'                  # 换行符结束输入

# 在找到字符串 ":)\n###\n" 后发送 payload。
p.sendafter(":)\n###\n", payload)

# 从进程接收标志
flag = p.recvline()
print(f"flag is: {flag}")

Level 2.0 关卡 2.0

在本关卡中,你需要编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

rax = 0x12345678

请参考以下 pwntools 示例代码来完成脚本(提示:请确保将 NOP 指令替换为特定的汇编指令):

from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.0"

p = process(challenge_path)

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm("NOP"))

print_lines(p)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码,目标是将rax寄存器的值设置为0x12345678
asm("mov rax, 0x12345678")
用于将汇编指令编译为机器码
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.0"

p = process(challenge_path)

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm("mov rax, 0x12345678"))

print_lines(p)

Level 2.1 关卡 2.1

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

交换特定寄存器的值(参考 `ASMChallenge` 类的 `trace` 方法)。
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.1
得知目标是将rax寄存器的值和rbx寄存器的值交换
asm("xchg rax, rbx")
# xchg 用于交换两个操作数的值
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.1"

p = process(challenge_path)

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm("xchg rax, rbx"))

print_lines(p)

Level 2.2 关卡 2.2

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

计算特定公式(参考 `ASMChallenge` 类的 `trace` 方法)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.2
得知目标是
rax % rbx(求余数)
加上 rcx
减去 rsi
结果存入 rax
asm_code = """
    ; 计算 rax % rbx
    xor rdx, rdx    ; 清零rdx
    div rbx         ; rdx = rax % rbx 
    ; 将余数移到rax,然后加上rcx,减去rsi
    mov rax, rdx
    add rax, rcx     ; 加上rcx
    sub rax, rsi     ; 减去rsi
"""
asm(asm_code)
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.2"

p = process(challenge_path)

asm_code = """
    xor rdx, rdx
    div rbx
    mov rax, rdx
    add rax, rcx
    sub rax, rsi
"""

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm(asm_code))

print_lines(p)

Level 2.3 关卡 2.3

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

修改特定全局数据区的值。(参考 `ASMChallenge` 类的 `trace` 方法)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.3
得知目标是需要将内存地址 `0x404000` 处的8字节数据复制到内存地址 `0x405000` 处
asm_code = """
    ; 从0x404000读取8字节到rax
    mov rax, [0x404000]
    ; 将rax中的8字节写入0x405000
    mov [0x405000], rax
"""
asm(asm_code)
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.3"

p = process(challenge_path)

asm_code = """
    mov rax, [0x404000]
    mov [0x405000], rax
"""

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm(asm_code))

print_lines(p)

Level 2.4 关卡 2.4

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

设置特定的栈内存区域。(参考 `ASMChallenge` 类的 `trace` 方法)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.4
得知目标是需要将栈顶的值减去 rbx 寄存器的值,并将结果放回栈顶
asm_code = """
    pop rax      ; 弹出栈顶值到rax
    sub rax, rbx ; rax = rax - rbx
    push rax     ; 将结果压回栈顶
"""
asm(asm_code)
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.4"

p = process(challenge_path)

asm_code = """
    pop rax
    sub rax, rbx
    push rax
"""

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm(asm_code))

print_lines(p)

Level 2.5 关卡 2.5

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

编写一个 `if` 语句来设置特定的栈内存区域。(参考 `ASMChallenge` 类的 `trace` 方法)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.5
得知目标是需要计算栈顶值的绝对值,并将结果放回栈顶
asm_code = """
    pop rax        ; 弹出栈顶值到rax
    test rax, rax  ; 检查符号位
    jns positive   ; 如果非负(SF=0),跳转到positive
    neg rax        ; 如果是负数,取负
positive:
    push rax       ; 将绝对值压回栈顶
"""
asm(asm_code)
# test 对两个操作数执行按位与(AND)操作,更新标志寄存器,但不修改操作数本身
ZF(零标志):结果为0时置1
SF(符号标志):结果为负时置1(最高位为1)
PF(奇偶标志):结果中1的个数为偶数时置1
CF(进位标志):总是清零
OF(溢出标志):总是清零
# jns Jump if Not Signed(非负则跳转),当符号标志SF=0时跳转到指定标签
# neg 取负,计算 0 - 操作数
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.5"

p = process(challenge_path)

asm_code = """
    pop rax
    test rax, rax
    jns positive
    neg rax
positive:
    push rax
"""

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm(asm_code))

print_lines(p)

Level 2.6 关卡 2.6

此关卡要求你编写一段汇编代码片段以满足以下条件以绕过检查,并使用 pwntools 的 asm API 编译汇编代码并完成挑战。本挑战的条件是:

编写一个 `for` 循环来计算特定公式。(参考 `ASMChallenge` 类的 `trace` 方法)
查看解析
根据题意,我们需要使用pwntools的asm编写汇编代码
/challenge/pwntools-tutorials-level2.6
得知目标是需要编写一个循环(for语句)来计算从1到rcx的整数之和,结果存入rax
asm_code = """
    xor rax, rax      ; 清零rax,用于累加
    test rcx, rcx     ; 检查rcx是否为0
    jz end            ; 如果rcx=0,直接结束
loop_start:
    add rax, rcx      ; 将rcx加到rax
    loop loop_start   ; rcx减1,如果rcx不为0则跳转
end:
"""
asm(asm_code)
# jz Jump if Zero(为零则跳转),当零标志ZF=1时跳转到指定标签
# loop 循环指令,使用 rcx(64位)或 ecx(32位)作为计数器,自动将计数器减1,并检查是否为0
from pwn import *

def print_lines(io):
    info("Printing io received lines")
    while True:
        try:
            line = io.recvline()
            success(line.decode())
        except EOFError:
            break

# 设置架构、操作系统和日志级别
context(arch="amd64", os="linux", log_level="info")

# 加载 ELF 文件并作为新进程执行。
challenge_path = "/challenge/pwntools-tutorials-level2.6"

p = process(challenge_path)

asm_code = """
    xor rax, rax
    test rcx, rcx
    jz end
loop_start:
    add rax, rcx
    loop loop_start
end:
"""

# 在找到字符串 "(up to 0x1000 bytes): \n" 后发送 payload。
p.sendafter("Please give me your assembly in bytes", asm(asm_code))

print_lines(p)

Level 3.0 关卡 3.0

当前关卡需要你阅读挑战关卡的 bypass_me 函数,使用 pwntools 完成本关卡挑战。接下来,你需要使用 pwntools 中的交互 API 编写利用脚本,发送特定输入绕过检查,并读取 /flag

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include"util.h"

#define BUF_SIZE 0x100

enum book_status{
        CREATED = 0X12345,
        ABANDONED = 0X67890,
}
;
typedef struct {
        char content[BUF_SIZE];
        int status;
} notebook;

notebook * notebooks[10] = {NULL};

void init()
{
        setvbuf(stdin, 0, 2, 0);
        setvbuf(stdout, 0, 2, 0);
        setvbuf(stderr, 0, 2, 0);
}

int read_int()
{
        char buf[0x10];
        read(0, buf, 0x10);
        return atoi(buf);
}

void read_flag()
{
        char flag[0x100];
        int fd, size = 0;

        fd = open("/flag", 0);
        if (fd < 0) {
                puts("Open flag failed");
                exit(-1);
        }
        size = read(fd, flag, 0x100);
        write(1, flag, size);
        close(fd);
        exit(0);
}

void create_notebook()
{
        notebook *book = NULL;
        int idx;

        puts("Input your notebook index:");
        idx = read_int();
        if (idx < 0 || idx >= 0x20) {
                puts("Invalid index for notebooks, Hacker!");
                return;
        }

        book = malloc(sizeof(notebook));
        if (book == NULL) {
                puts("malloc error");
                exit(-1);
        }

        puts("Input your notebook content:");
        read(0, book->content, BUF_SIZE);
        book->status = CREATED;

        notebooks[idx] = book;
        puts("Done!");
}

void edit_notebook()
{
        int idx;

        puts("Input your notebook index:");
        idx = read_int();
        if (idx < 0 || idx >= 0x10) {
                puts("Invalid index, Hacker!");
                return;
        }

        if (notebooks[idx] == NULL) {
                puts("You don't have this notebook, create it first");
                return;
        }

        notebooks[idx]->status = ABANDONED;
        puts("Done!");
}

void delete_notebook()
{
        int idx;

        puts("Input your notebook index:");
        idx = read_int();
        if (idx < 0 || idx >= 0x10) {
                puts("Invalid index, Hacker!");
                return;
        }

        if (notebooks[idx] == NULL) {
                puts("You don't have this notebook, create it first");
                return;
        }

        free(notebooks[idx]);
        puts("Done!");
}

void show_notebook()
{
        int idx;

        puts("Input your notebook index:");
        idx = read_int();
        if (idx < 0 || idx >= 0x10) {
                puts("Invalid index, Hacker!");
                return;
        }

        if (notebooks[idx] == NULL) {
                puts("You don't have this notebook, create it first");
                return;
        }

        printf("content = %s, status = 0x%x\n", notebooks[idx]->content, notebooks[idx]->status);
}

int bypass_me()
{
        int flag = 0;

        // notebooks[0]
        if ((notebooks[0] == NULL) ||
            (strcmp(notebooks[0]->content, "hello ") != 0) ||
            (notebooks[0]->status != CREATED))
                return flag;

        // notebooks[1]
        if ((notebooks[1] == NULL) ||
            (strcmp(notebooks[1]->content, "world,") != 0) ||
            (notebooks[1]->status != ABANDONED))
                return flag;        

        // notebooks[3]
        if ((notebooks[3] == NULL) ||
            (strcmp(notebooks[3]->content, "magic ") != 0) ||
            (notebooks[3]->status != CREATED))
                return flag;

        // notebooks[5]
        if ((notebooks[5] == NULL) ||
            (strcmp(notebooks[5]->content, "notebook") != 0) ||
            (notebooks[5]->status != ABANDONED))
                return flag;

        flag = 1;

        return flag;
}

void menu()
{
        puts("1. Create Notebook");
        puts("2. Edit   Notebook");
        puts("3. Delete Notebook");
        puts("4. Show   Notebook");
        puts("5. Gift for You");
        puts("Choice >> ");
}

int main()
{
        int choice, flag = 1;

        init();
        puts("We have a magic notebook for you:");
        
        while (flag) {
                menu();
                scanf("%d", &choice);
                switch (choice)
                {
                case 1:
                        create_notebook();
                        break;
                case 2:
                        edit_notebook();
                        break;
                case 3:
                        delete_notebook();
                        break;
                case 4:
                        show_notebook();
                        break;
                case 5:
                        if (bypass_me()) 
                                read_flag();
                        flag = 0;
                        break;
                default:
                        puts("Invalid choice");
                        break;
                }
        }
        
        puts("Bye bye~");

        return 0;
}
查看解析
cat /challenge/pwntools-tutorials-level3.0
经分析可知主要函数如下:
create_notebook(): 创建笔记本,分配内存,设置状态为CREATED (0x12345)
edit_notebook(): 将笔记本状态改为ABANDONED (0x67890)
delete_notebook(): 释放笔记本内存
show_notebook(): 显示笔记本内容
read_flag(): 读取并输出flag
想要绕过,read_flag()中关键函数bypass_me()会检测:
notebook[0]: content="hello ", status=CREATED (0x12345)
notebook[1]: content="world,", status=ABANDONED (0x67890)
notebook[3]: content="magic ", status=CREATED (0x12345)
notebook[5]: content="notebook", status=ABANDONED (0x67890)
即我们要做:
创建笔记本0,内容为"hello ",状态自动为CREATED
创建笔记本1,内容为"world,",然后编辑它使状态变为ABANDONED
创建笔记本3,内容为"magic ",状态为CREATED
创建笔记本5,内容为"notebook",然后编辑它使状态变为ABANDONED
#!/usr/bin/env python3
from pwn import *

# 创建进程
p = process('/challenge/pwntools-tutorials-level3.0')

# 函数:发送菜单选项
def send_choice(choice):
    p.sendlineafter(b'Choice >> ', str(choice).encode())

# 函数:创建笔记本
def create_notebook(idx, content):
    send_choice(1)
    p.sendlineafter(b'Input your notebook index:', str(idx).encode())
    p.sendafter(b'Input your notebook content:', content.encode())

# 函数:编辑笔记本
def edit_notebook(idx):
    send_choice(2)
    p.sendlineafter(b'Input your notebook index:', str(idx).encode())

# 函数:删除笔记本
def delete_notebook(idx):
    send_choice(3)
    p.sendlineafter(b'Input your notebook index:', str(idx).encode())

# 函数:显示笔记本
def show_notebook(idx):
    send_choice(4)
    p.sendlineafter(b'Input your notebook index:', str(idx).encode())
    return p.recvline()

# 按照bypass_me()函数的要求设置各个笔记本

# notebooks[0]:content="hello ", status=CREATED
create_notebook(0, "hello ")

# notebooks[1]:content="world,", status=ABANDONED
create_notebook(1, "world,")
edit_notebook(1)  # 编辑后status变为ABANDONED

# notebooks[3]:content="magic ", status=CREATED
create_notebook(3, "magic ")

# notebooks[5]:content="notebook", status=ABANDONED
create_notebook(5, "notebook")
edit_notebook(5)  # 编辑后status变为ABANDONED

# 调用选项5获取flag
send_choice(5)

# 接收并打印flag
print(p.recvall().decode())

Level 4.0 关卡 4.0

在本关卡中,在没有源代码帮助的情况下,使用循环模式(cyclic patterns)和核心转储分析来自动找到返回地址,并利用栈溢出来读取 /flag 文件。

待补充……

posted @ 2025-12-23 17:32  Super_Snow_Sword  阅读(0)  评论(0)    收藏  举报