1、pwn0
连上,等它程序执行完你可以直接来到 shell 界面
执行命令,获取 flag
ctfshow{294ffc57-ee28-40ea-8c74-4dfeaf89d1e7}
2、pwn1
提供一个后门函数,连上即可得到flag
下载附件,拉进 ubantu ,使用命令 checksec 检查
该命令用于查看程序基本信息以及开启了哪些保护机制
checksec pwn1
可以看到是 64 位程序,并且未启用堆栈保护
关于上述参数的详细解释:
Arch (体系结构):
amd64-64-little
表示该程序是针对 x86-64 架构编译的,这是一种64位的CPU架构。little表示它是小端字节序的,这是x86架构的一种特征,其中最低有效字节存储在内存中的最低地址。RELRO (重定位只读):
Full RELRO
表示重定位表(GOT)已被标记为只读,这意味着在程序启动时,PLT和GOT表都会在初始化后被锁定,无法进行修改,以防止一些攻击(比如GOT覆盖)。Stack (栈保护):
No canary found
表示栈未启用堆栈保护(即没有发现堆栈 Canary)。栈 Canary 是一种安全机制,它在栈的关键部分插入随机值,以检测缓冲区溢出攻击。NX (不可执行栈):
NX enabled
表示内存页面的执行权限已启用。这意味着程序的内存区域(如堆栈)是不可执行的,防止了攻击者通过将恶意代码注入到内存中并执行它来利用程序。PIE (位置独立执行):
PIE enabled
表示程序启用了位置独立执行,这意味着它可以在内存中的任何位置加载并执行,而不受固定地址的限制,增加了对攻击的防御性。
尝试执行该文件发现权限不够,添加可执行权限
chmod +x pwn1
提示我们只需要 nc 远程的地址即可获得 flag
可以看到连接上它会自己执行 cat /ctfshow_flag 的命令
直接 nc 上,就回显 flag 了
ctfshow{a9c43ca7-cd95-43fe-ad01-007a46fe29fe}
接下来我们使用 ida64 分析下程序
找到 main 函数
使用 F5 进行反编译,得到伪代码
可以看到这里它直接执行了 system 函数,与我们前面的分析一致
3、pwn2
给你一个shell,这次需要你自己去获得flag
检查一下附件,还是 64 位程序,未开启堆栈保护
使用 ida64 分析
找到 main 函数,F5 反编译
可以看到这里 system 函数执行了 /bin/sh
什么是 /bin/sh ?
它就是一个指向 dash 的软链接(软链接可以理解为 Windows 下的快捷方式)
而 dash 是什么?
它是 Debian Almquist shell 的缩写,就是一种 shell,常用的 shell 还有 bash
简单点来说 system("/bin/sh") 执行后会返还一个 shell 给函数调用者
我们 nc 地址,连接上后,便可获得一个 shell,直接执行我们想要执行的命令
得到 flag
ctfshow{ab6cb831-7c6a-40f2-9c7e-a27ede4cc4c9}
4、pwn3
哪一个函数才能读取flag?
老规矩检查一下,是 64 位程序,但是开了堆栈保护
拖进 ida64 ,找到 main 函数反编译
很容易可以看到情况 6 调用了系统函数,其他很多都是一些输入或者输出
双击跟进,可以看到是获取 flag 的操作
使用 nc 连接上地址
选项 6 ,即可获取到 flag
ctfshow{fa7def0a-16b9-49e7-8826-3928b2fd9a45}
5、pwn4
或许需要先得到某个神秘字符
检查一下,是 64 位程序,保护全开
使用 ida64 分析,对 main 函数反编译
我们对伪代码进行逐行分析:
int __cdecl main(int argc, const char **argv, const char **envp)
: 这是main
函数的定义,它接受三个参数,分别是程序的参数数量argc
、参数列表argv
和环境变量envp
。
char s1[11];
和char s2[12];
: 这两行声明了两个字符数组,分别是s1
和s2
,分别有 11 和 12 个元素。
unsigned __int64 v6;
: 这是一个无符号 64 位整数v6
。
v6 = __readfsqword(0x28u);
: 这一行读取了 FS 寄存器偏移 0x28 处的值并存储到v6
中。
setvbuf(_bss_start, 0LL, 2, 0LL);
和setvbuf(stdin, 0LL, 2, 0LL);
: 这两行调用了setvbuf
函数,用于设置缓冲区类型。第一个调用将_bss_start
的缓冲区类型设置为无缓冲,第二个调用将标准输入的缓冲区类型设置为无缓冲。
strcpy(s1, "CTFshowPWN");
: 这行将字符串"CTFshowPWN"
复制到s1
数组中。
logo();
: 这行调用了一个叫做logo
的函数,用于打印某个东西。
puts("find the secret !");
: 这行打印了字符串"find the secret !"
。
__isoc99_scanf("%s", s2);
: 这行使用scanf
函数从标准输入读取字符串,并将其存储到s2
数组中。
if ( !strcmp(s1, s2) ) execve_func();
: 这行使用strcmp
函数比较s1
和s2
是否相等,如果相等,则调用execve_func
函数。
return 0;
: 最后返回 0,表示程序正常退出。根据代码逻辑,程序的主要目的似乎是要求用户输入一个字符串,如果输入的字符串与预设的字符串相同,则调用
execve_func
函数。
关于其中某些函数的详细解释:
因为我 C 几乎忘完了,因此这里整理下
setvbuf
: 这个函数用于设置文件流的缓冲区类型。它允许程序员控制标准 I/O 库中文件流的缓冲方式。通常,我们可以将文件流设置为无缓冲、行缓冲或全缓冲。在这个代码中,setvbuf
函数被用于设置_bss_start
和标准输入流stdin
的缓冲类型为无缓冲,这意味着每次输出都会立即被写入,而不会先缓存在内存中。
strcpy
: 这个函数用于将一个字符串复制到另一个字符串数组中。它接受两个参数,第一个参数是目标字符串数组,第二个参数是源字符串。strcpy
会将源字符串的内容逐个字符地复制到目标字符串数组中,直到遇到源字符串的结束符\0
。
puts
:puts
函数用于向标准输出打印字符串,并在最后自动添加一个换行符\n
。它接受一个字符串作为参数,并将其打印到标准输出流中。
scanf
: 这个函数用于从标准输入流中读取输入。它接受格式化字符串作为第一个参数,用于指定输入的格式,以及一系列指向变量的指针,用于存储读取到的值。在这个代码中,scanf
被用于从标准输入中读取一个字符串,并将其存储到s2
字符数组中。
strcmp
: 这个函数用于比较两个字符串是否相等。它接受两个字符串作为参数,并返回一个整数值,如果两个字符串相等则返回0,否则返回它们第一个不相等字符的 ASCII 差值。在这个代码中,strcmp
用于比较s1
和s2
是否相等,如果相等则返回0,程序将执行execve_func
函数。
双击跟进 execve_func 函数
unsigned __int64 execve_func()
: 这个函数返回一个无符号 64 位整数。
char *argv[3];
: 这里声明了一个字符串指针数组argv
,长度为 3。
v2 = __readfsqword(0x28u);
: 这一行读取了 FS 寄存器偏移 0x28 处的值并存储到v2
中。
argv[0] = "/bin/sh";
: 将字符串"/bin/sh"
赋值给argv
数组的第一个元素,表示要执行的命令。
argv[1] = 0LL;
和argv[2] = 0LL;
: 将argv
数组的第二个和第三个元素设置为 NULL,表示参数列表结束。
execve("/bin/sh", argv, 0LL);
: 这一行调用了execve
函数,用于执行一个程序。它接受三个参数,分别是要执行的程序路径、参数列表和环境变量列表。在这里,它执行了/bin/sh
,并传递了一个空的参数列表和环境变量列表,表示没有额外的参数和环境变量。
return __readfsqword(0x28u) ^ v2;
: 这里返回了一个异或操作的结果。综合起来,这个函数的作用是执行
/bin/sh
,即启动一个交互式 shell。
既然能获取 shell ,那么我们就可以执行命令,进而获取 flag
因此我们让前面的 if 判断成立,即让 s1 = s2 = CTFshowPWN
使用 nc 连接上后,输入 CTFshowPWN 将其传给 s2
s1 = s2 ,strcmp(s1, s2) 返回 0,!0 即为 1,if(1),则会调用后面的 execve_func() 函数
我们尝试执行命令,成功
直接 cat ctfshow_flag
拿到 flag
ctfshow{203c8829-a5f7-4962-89a0-8a5907838d16}