pwnable.kr靶场通关(Level5~10)

1. pwnable-random

pwnable.kr 的第六题是 random。这道题主要考查密码学和安全开发中的经典漏洞:伪随机数生成器(PRNG)的可预测性

1.1 源码分析

使用 SSH 登录靶机并查看官方提供的 random.c 源码:

#include <stdio.h>

int main(){
        unsigned int random;
        random = rand();        // random value!

        unsigned int key=0;
        scanf("%d", &key);

        if( (key ^ random) == 0xcafebabe ){
                printf("Good!\n");
                setregid(getegid(), getegid());
                system("/bin/cat flag");
                return 0;
        }

        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
}

漏洞核心要点:没有初始化的随机数种子

在 C 语言中,rand() 函数用来生成伪随机数。计算机并不能产生真正意义上的随机数,它是通过一个数学公式,基于一个叫“种子(Seed)”的初始值计算出一段数字序列:

  • 如果在调用 rand() 之前,程序没有使用 srand(seed) 函数来初始化种子,那么系统就会默认将种子设置为1
  • 既然每一次运行程序时种子都固定是1,那么 rand() 第一次被调用时返回的数值也就永远是一个固定死的值。这就是经典的伪随机数可预测漏洞

1.2 数学推导与计算

我们的目标是满足条件:key ^ random == 0xcafebabe

根据异或(XOR)运算的对称性质:若 A⊕B=C,则必然有 A⊕C=B。我们可以将公式变形为:

key = random ⊕ 0xcafebabe

因此,我们只需要知道 random 这个固定的伪随机数值是多少,就能直接算出应该输入的 key

1.2.1 获取固定的random值

在 Linux 环境(如靶机终端)下,直接写一个简单的测试程序,或者通过 GDB 调试打印出无种子时 rand() 的第一个返回值:

  • 十六进制形式0x6b8b4567
  • 十进制形式1804289383
#include <stdio.h>
#include <stdlib.h>

int main(){
    unsigned int random1,random2,random3;

    random1 = rand();
    random2 = rand();
    random3 = rand();
    printf("不设置种子\n");
    printf("%d\n%d\n%d\n\n", random1, random2, random3);

    unsigned int a,b,c;
    srand(1);
    a = rand();
    b = rand();
    c = rand();
    printf("设置种子为1\n");
    printf("%d\n%d\n%d\n\n", a, b, c);

    unsigned int x,y,z;
    srand(2);
    x = rand();
    y = rand();
    z = rand();
    printf("设置种子为2\n");
    printf("%d\n%d\n%d\n", x, y, z);
}
不设置种子
1804289383
846930886
1681692777

设置种子为1
1804289383
846930886
1681692777

设置种子为2
1505335290
1738766719
190686788

1.2.2 计算目标key

将获取到的随机数与 0xcafebabe进行异或计算:

key = 0x6b8b4567 ⊕ 0xcafebabe = 0xa175ffd9

由于程序中的 scanf("%d", &key) 使用了 %d(有符号十进制整数格式)或直接作为 unsigned int 匹配,我们可以将其转换为十进制输入:

  • 十六进制 0xa175ffd9转换成十进制为:2708864985
  • (如果被识别为有符号数,对应的负数输入为 -1255736440,两者在内存中的二进制表示完全相同,均可通关);

1.3 通关验证

直接运行靶机上的可执行文件 random,并在提示输入时键入计算好的十进制数字:

random@pwnable:~$ ./random
2708864985

程序成功通过验证并打印出 flag 内容:

2. pwnable-input2

pwnable.kr 的第七题是 input。这道题堪称 Linux 编程与基础交互的经典之作。它几乎没有复杂的溢出利用,而是纯粹考查 Linux 环境下各种底层输入/输出(I/O)以及进程控制的基本功

程序包含 5 个阶段(Stage),我们必须同时满足这 5 个阶段的输入验证才能拿到 Flag。

2.1 5个 Stage 的源码要求解析

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
        printf("Welcome to pwnable.kr\n");
        printf("Let's see if you know how to give input to program\n");
        printf("Just give me correct inputs then you will get the flag :)\n");

        // argv
        if(argc != 100) return 0;
        if(strcmp(argv['A'],"\x00")) return 0;
        if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
        printf("Stage 1 clear!\n");

        // stdio
        char buf[4];
        read(0, buf, 4);
        if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
        read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
        printf("Stage 2 clear!\n");

        // env
        if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
        printf("Stage 3 clear!\n");

        // file
        FILE* fp = fopen("\x0a", "r");
        if(!fp) return 0;
        if( fread(buf, 4, 1, fp)!=1 ) return 0;
        if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
        fclose(fp);
        printf("Stage 4 clear!\n");

        // network
        int sd, cd;
        struct sockaddr_in saddr, caddr;
        sd = socket(AF_INET, SOCK_STREAM, 0);
        if(sd == -1){
                printf("socket error, tell admin\n");
                return 0;
        }
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons( atoi(argv['C']) );
        if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
                printf("bind error, use another port\n");
                return 1;
        }
        listen(sd, 1);
        int c = sizeof(struct sockaddr_in);
        cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
        if(cd < 0){
                printf("accept error, tell admin\n");
                return 0;
        }
        if( recv(cd, buf, 4, 0) != 4 ) return 0;
        if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
        printf("Stage 5 clear!\n");

        // here's your flag
        setregid(getegid(), getegid());
        system("/bin/cat flag");
        return 0;
}
  • Stage 1:命令参数(argv):
    • 代码逻辑:检查 argc 是否等于 100,且检查 argv['A'](即 argv[65])是否为 \x00argv['B'](即 argv[66])是否为 \x20\x0a\x0d
    • 核心难点:普通终端无法直接通过命令行传入 \x00(截断符),需要通过编写程序(如 Python 或 C)利用 execve 或进程创建来生成精密的 argv 数组;
  • Stage 2:标准I/O(stdio):
    • 代码逻辑:程序通过 read(0, buf, 4) 从标准输入(stdin)读取 4 字节,比对是否为 \x00\x0a\x00\xff;接着通过 read(2, buf, 4) 从标准错误(stderr)读取 4 字节,比对是否为 \x00\x0a\x02\xff
    • 核心难点:如何同时向 stdin 和 stderr 写入指定字节?需要通过创建匿名管道(Pipe)并使用 dup2 重定向子进程的文件描述符;
  • Stage 3:环境变量(env):
    • 代码逻辑:检查系统环境变量中是否存在键名为 \xde\xad\xbe\xef 的变量,且其值必须为 \xca\\xfe\xba\xbe
    • 核心难点:同样必须通过编写父进程脚本,在启动子进程时将自定义的环境变量字典(envp)传递过去;
  • Stage 4:文件系统(file):
    • 代码逻辑:程序尝试打开一个文件名为 \x0a(换行符)的文件,并从中读取 4 字节,比对是否为 \x00\x00\x00\x00
    • 核心难点:在父进程运行前,先在当前目录下通过代码创建一个文件名为 \x0a 的文件,并写入 4 个空字节;
  • Stage 5:网络通信(network):
    • 代码逻辑:程序会在 argv[67] 指定的端口上监听一个 TCP 对应 Socket,并等待连接。收到连接后会读取 4 字节,比对是否为 \xde\xad\xbe\xef
    • 核心难点:启动子进程后,父进程需要短暂休眠(如 time.sleep(1))等待子进程建立监听,随后创建客户端 Socket 去连接本地对应的端口,并发送 \xde\xad\xbe\xef

2.2 编写 Exploit 脚本

因为题目环境有读写权限限制,我们通常需要在靶机自带的 /tmp 目录下创建一个个人专属目录(例如 /tmp/my_pwn_dir),并在该目录下编写并运行 Python 脚本。

此外,由于程序运行成功后会读取同目录下的 flag 文件,我们需要事先在 /tmp/my_pwn_dir 中通过 ln -s /home/input2/flag ./flag 创建软链接指向真实的 flag。

以下是基于 Python3 与 pwntools 库实现的完整自动化通关脚本(exp.py):

import os
import time
from pwn import *

# 0. 准备工作:在本地创建名为 \x0a 的文件 (Stage 4)
with open("\x0a", "wb") as f:
    f.write(b"\x00\x00\x00\x00")

# 1. 构造 argv 参数 (Stage 1)
# 需要填满 100 个参数
argvs = ["A" for _ in range(100)]
argvs[0] = "/home/input2/input"  # 目标程序路径
argvs[65] = "\x00"                # argv['A']
argvs[66] = "\x20\x0a\x0d"        # argv['B']
argvs[67] = "9999"                # argv['C'] 为 Stage 5 的端口号

# 2. 构造环境变量 (Stage 3)
envs = {"\xde\xad\xbe\xef": "\xca\xfe\xba\xbe"}

# 3. 构造 Stdio 的管道流 (Stage 2)
# 分别创建给 stdin 和 stderr 喂数据的管道
r_stdin, w_stdin = os.pipe()
r_stderr, w_stderr = os.pipe()

# 4. 启动目标程序
# 使用 pwntools 的 process 轻松控制 argv, env 以及文件描述符重定向
p = process(
    argv=argvs,
    env=envs,
    stdin=r_stdin,
    stderr=r_stderr
)

# 向 stdin 写入 4 字节
os.write(w_stdin, b"\x00\x0a\x00\xff")
# 向 stderr 写入 4 字节
os.write(w_stderr, b"\x00\x0a\x02\xff")

# 5. 网络交互 (Stage 5)
# 稍微等待 1 秒,确保目标程序已经起来并绑定了端口
time.sleep(1)

# 连接本地刚才指定的 9999 端口
r = remote("127.0.0.1", 9999)
r.send(b"\xde\xad\xbe\xef")

# 6. 获取交互与 flag
p.interactive()

2.3 通关步骤

首先,利用SSH 登录靶机,进入 /tmp 并建立自己的工作区:

cd /tmp
mkdir my_working_dir && cd my_working_dir

其次,创建 flag 的软链接:

ln -s /home/input2/flag ./flag

再次,将上面的代码保存为 exp.py,并赋予执行权限:

chmod +x exp.py

最后,运行脚本:

python3 exp.py

运行后,程序会依次显示:Stage 1 clear! 直至 Stage 5 clear!,随后打印出最终的 flag 字符串。

由于使用的python虚拟环境,所以这里就不测试了。

3. pwnable-leg

pwnable.kr 的第八题是 leg。这道题与其他题目最大的不同点在于,它是一道 ARM 架构(32位)的汇编基础题

题目没有传统的缓冲区溢出或内存破坏漏洞,而是给出了一个在 ARM 机器下编译的程序和源码,要求你通过阅读 ARM 汇编,准确计算出三个函数的返回值之和。

3.1 源码分析

通过 SSH 登录靶机,查看 leg.c 的核心代码:

#include <stdio.h>
#include <fcntl.h>
int key1(){
	asm("mov r3, pc\n");
}
int key2(){
	asm(
	"push	{r6}\n"
	"add	r6, pc, $1\n"
	"bx	r6\n"
	".code   16\n"
	"mov	r3, pc\n"
	"add	r3, $0x4\n"
	"push	{r3}\n"
	"pop	{pc}\n"
	".code	32\n"
	"pop	{r6}\n"
	);
}
int key3(){
	asm("mov r3, lr\n");
}
int main(){
	int key=0;
	printf("Daddy has very strong arm! : ");
	scanf("%d", &key);
	if( (key1()+key2()+key3()) == key ){
		printf("Congratz!\n");
		int fd = open("flag", O_RDONLY);
		char buf[100];
		int r = read(fd, buf, 100);
		write(0, buf, r);
	}
	else{
		printf("I have strong leg :P\n");
	}
	return 0;
}

简单来说,就是需要你输入一个十进制整数 key,使得 key == key1() + key2() + key3()。因此,关键在于推导这三个函数在 ARM 架构下运行时的精确返回值。

3.2 ARM汇编知识回顾

在分析汇编前,必须了解 ARM 模式(此处为 ARM 32-bit 状态,非 Thumb 模式)下的两个关键特性:

  • 1. 流水线机制与PC寄存器:
    • 在 ARM 状态下,PC(Program Counter,程序计数器)总是指向当前正在执行的指令之后的第二条指令(即 当前指令地址 + 8 字节);
    • 每一条 ARM 32-bit 指令固定占用 4 个字节
  • 2. 寄存器分工:
    • R0:在 C 语言调用约定中,函数返回值通常存放在 R0 寄存器中;
    • LR(Link Register,连接寄存器,即 R14):存有当前函数执行完毕后的返回地址(即调用该函数处的下一条指令地址);

3.3 汇编分析

使用官方提供的 leg.asm(或者在靶机上使用 objdump -d leg 查看反汇编):

3.3.1 计算key1()

0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
  • 分析:当程序执行到 8cdc: mov r3, pc 时,根据 PC = 当前地址 + 8 的原理,此时 PC 的值等于 0x8cdc + 8 = 0x8ce4
  • key1() 返回 0x8ce4

3.3.2 计算key2()

   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr

这里需要知道执行到 8cf8 R3 的值是多少。我们必须回到 main 函数中看它是怎么调用 key2 的:

0x00008d68 <+44>:	bl	0x8cd4 <key1>
0x00008d6c <+48>:	mov	r4, r0
0x00008d70 <+52>:	bl	0x8cf0 <key2>
0x00008d74 <+56>:	mov	r3, r0
  • 分析:在调用 key2 之前,main 函数在 8d6c 处将 key1 的返回值(R0)赋给了 R4。此后 R4 的值未被修改。当进入 key2 后执行 mov r3, r0 时,R4 的值依然是 key1 的返回值;
  • 结果key2() 返回 0x8d0c

3.3.3 计算 key3()

0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
0x00008d24 <+4>:	add	r11, sp, #0
0x00008d28 <+8>:	mov	r3, lr
0x00008d2c <+12>:	mov	r0, r3
0x00008d30 <+16>:	sub	sp, r11, #0
0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
0x00008d38 <+24>:	bx	lr

这里需要知道调用 key3 LR 寄存器的值。再次查看 main 函数对 key3 的调用:

   0x00008d70 <+52>:	bl	0x8cf0 <key2>
   0x00008d74 <+56>:	mov	r3, r0
   0x00008d78 <+60>:	add	r4, r4, r3
   0x00008d7c <+64>:	bl	0x8d20 <key3>
   0x00008d80 <+68>:	mov	r3, r0
  • 分析:在 8d7c 处,程序通过 bl(Branch with Link)指令跳转到 key3bl 指令会自动将下一条指令的地址(即 8d80)存入 LR 寄存器,以便函数执行完后能返回。因此,进入 key3 后,LR 的值固定为 0x8d80
  • 结果key3() 返回 0x8d80

3.4 通关步骤

我们将三个十六进制返回值相加:Total = key1 + key2 + key3即Total = 0x8ce4 + 0x8ce4 + 0x8d80;

使用十六进制计算:0x8ce4 + 0x8d0c = 0x119F00x119F0 + 0x8d80 = 0x1A770;将十六进制的 0x1A770 转换为十进制就是108400

直接运行靶机上的 ./leg 程序,在提示时输入十进制数字 108400

4. pwnable-mistake

这道题主要考查 C 语言编程中的运算符优先级(Operator Precedence)漏洞。这种失误非常隐蔽,属于典型的逻辑与代码审计漏洞。

4.1 源码分析

通过 SSH 登录靶机并查看官方提供的 mistake.c 源码,其核心逻辑如下:

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
        int i;
        for(i=0; i<len; i++){
                s[i] ^= XORKEY;
        }
}

int main(int argc, char* argv[]){

        int fd;
        if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
                printf("can't open password %d\n", fd);
                return 0;
        }

        printf("do not bruteforce...\n");
        sleep(time(0)%20);

        char pw_buf[PW_LEN+1];
        int len;
        if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
                printf("read error\n");
                close(fd);
                return 0;
        }

        char pw_buf2[PW_LEN+1];
        printf("input password : ");
        scanf("%10s", pw_buf2);

        // xor your input
        xor(pw_buf2, 10);

        if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
                printf("Password OK\n");
                setregid(getegid(), getegid());
                system("/bin/cat flag\n");
        }
        else{
                printf("Wrong Password\n");
        }

        close(fd);
        return 0;
}

漏洞核心:运算符优先级失误

if(fd=open("/home/mistake/password", O_RDONLY) < 0)

在 C 语言中,小于号 <(关系运算符)的优先级高于等号 =(赋值运算符)。因此,编译器在解释这行代码时,实际上的执行顺序是:

  • 先执行 open("/home/mistake/password", O_RDONLY),成功打开文件并返回文件描述符(通常为 3);
  • 接着执行判断:3 < 0。这个条件显然不成立,结果为 0(False);
  • 最后执行赋值:fd = 0

结果:本来 fd 应该存放文件的句柄(如 3),但因为优先级问题,fd 被硬生生地赋值成了 0

4.2 逻辑推导与利用

在 Linux 系统中,固定的标准文件描述符如下:

  • 0:标准输入(stdin,通常指键盘输入);
  • 1:标准输出(stdout);
  • 2:标准错误(stderr);

既然 fd 变成了 0,那么紧接着的读取操作:

len = read(fd, pw_buf, 10);

实际上就变成了 read(0, pw_buf, 10)。也就是说,程序本来打算从后台文件读取正确密码,现在却变成了从屏幕等待用户手动输入前 10 个字节作为正确密码(pw_buf)

  • 第一次输入:程序执行 read(0, ...),等待我们输入一串 10 字节的字符串,它会被当作“正确密码”(pw_buf);
  • 第二次输入:程序执行 scanf("%10s", pw_buf2),等待我们输入我们猜测的密码(pw_buf2);
  • 密码处理:程序会将第二次输入的 pw_buf2 每一个字符都与 1 进行异或(^ 1);
  • 最终比对:只要通过异或处理后的 pw_buf2 等于第一次输入的 pw_buf,就能成功通关;

3.3 构造 Payload 与通关

为了让 pw_buf2 ^ 1 == pw_buf,根据异或的对称性,我们只需要让:

第二次输入 = 第一次输入 ⊕ 1

举个例子,如果我们想让第二次输入全是 0(10 个 0):
根据字符数字的 ASCII 码:

  • 字符 '0' 的 ASCII 码是 48(二进制 00110000);
  • 48 ^ 1 = 49(二进制 00110001),对应的字符是 '1'

因此结果就是:

  • 第一次输入(作为正确答案)1111111111(10个1);
  • 第二次输入(作为猜测密码)0000000000(10个0);

直接运行靶机上的 ./mistake

5. pwnable-coin1

pwnable.kr 的第十一题是 coin1。这道题与前面的二进制漏洞利用(如栈溢出、GOT 劫持)截然不同。它是一道纯粹的算法与网络编程题

题目要求你在规定的时间限制内(通常是 60 秒),通过网络交互连续玩 100 轮“找假币”的游戏。由于时间极短且交互次数多,手工不可能完成,必须编写 Python 自动化脚本(通常使用 pwntools,利用 二分查找(Binary Search) 算法来通关。

5.1 游戏规则分析

通过网络连接 nc pwnable.kr 10009 启动游戏后,服务器会给出如下提示:

  • 基本信息:每轮游戏会给出N枚硬币和C次机会,其中有一枚是假币;
  • 基本规则:
    • N 个硬币中,有一个是假币;
    • 真币的重量固定为10,假币的重量固定为9;
    • 你一共有C次称重的机会(C的数量刚好是[Log2N],也就是刚好够你进行二分查找);
  • 交互方式:
    • 输入:每次向服务器发送你想放在天平左侧的硬币编号(以空格分隔),例如:0 1 2 3 4;
    • 服务器返回:服务器会返回这些硬币的总重量。如果全为真币,重量就是 50;如果包含假币,重量就是 49;、
  • 通关条件:利用这 C 次机会找出假币的编号并提交给服务器。连续答对 100 轮 即可获得 Flag;

5.2 二分查找

因为 C = log2N,这暗示了我们必须使用二分法:

假设当前硬币的搜索区间是 [left, right]

  • 计算中间位置:mid = (left + right) // 2
  • 构造称重列表:将区间左半部分 [left, mid] 的所有硬币编号打包发送给服务器;
  • 理论重量计算:这部分硬币的数量为 count = mid - left + 1。如果它们全都是真币,理论总重量应该是 expected_weight = count * 10
  • 比对服务器返回的实际重量 real_weight
    • 如果 real_weight == expected_weight:说明假币不在左半部分,它在右半部分 [mid + 1, right]
    • 如果 real_weight < expected_weight:说明假币在左半部分 [left, mid]
  • 循环这个过程,直到 left == right,此时该位置就是假币的编号;

5.3 编写 Python 自动化脚本

由于在本地通过远程网络连接(因网络延迟)极易导致 60 秒超时,最稳妥的做法是将编写好的 Python 脚本上传到 pwnable.kr 的靶机服务器(如上一关的 /tmp 目录)中运行。在靶机内部连接本地环回地址,网络延迟几乎为零。

基于 Python3 和 pwntools 库实现的完整通关脚本:

from pwn import *
import re

io = remote('localhost', 10009)
# 接收菜单
print(io.recvuntil(b'- Ready? starting in 3 sec... -').decode())

# 循环一百次
for i in range(100):
    line = io.recvuntil(b"N=").decode() + io.recvline().decode()
    N, C = re.findall("N=(\d+) C=(\d+)", line)[0]
    N = int(N)
    C = int(C)
    print(N,C)

    left , right = 0 , N

    while (right -left>1) & (C>0):
        mid = (left + right)//2
        payload = ' '.join(str(b) for b in range(left,mid))
        io.sendline(payload.encode())
        weight = int(io.recvline().decode().strip())
        if weight % 10 != 0:
            right = mid
        else:
            left = mid
        C-=1

    #找出了答案,但是次数没满,就消耗掉
    while C>0:
        io.sendline(str(0).encode())
        io.recvline()
        C-=1
        
    io.sendline(str(left).encode())
    print(io.recvline().decode())

print(io.recv().decode())

5.4 通关步骤

SSH 登录到前面任意一关的靶机,进入 /tmp 目录并创建你的专属工作目录:

cd /tmp
mkdir my_coin_game && cd my_coin_game

接下来保存为solve.py,运行脚本:

python3 solve.py

脚本会以极快的速度疯狂刷屏完成 100 轮,并在最后打印出本关的 flag

免责声明:

1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。

2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。

3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
觉得有帮助可以赞赏本文哦~万分感谢!
文章:pwnable.kr靶场通关(Level5~10)
作者:沛旗
链接:https://www.peiqiblog.com/article/16601/
版权声明::本博客站点所有文章除特别声明外,均采用 CC BY-NC-SA 4.0协议
转载请注明文章地址及作者哦~
暂无评论

发送评论(禁止发表一切违反法律法规的敏感言论) 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇