目录

1、pwn18

2、pwn19

3、总结


1、pwn18

仔细看看源码,或许有惊喜

假作真时真亦假,真作假时假亦真

下载附件,常规检查

checksec pwn

64 位程序,保护全开 

拖进 ida 分析,反编译查看伪代码,定位关键代码:

代码分析: 

首先接收用户输入,%d 是 C 语言中用来接收整数输入的格式化字符串,在 scanf 函数中,%d 会匹配并读取一个十进制整数,这里将其存储到变量 v4 中;

进行简单的 if 判断,如过输入的数等于 9 ,则执行 fake() 函数,否则执行 real() 函数;

最后无论怎样,都会调用系统函数 cat 根目录下的 ctfshow_flag。

我们分别双击跟进 fake 和 real 函数:

fake() 和 real() 函数都调用了 system() 函数来执行系统命令,将字符串 "flag is here" 写入到 /ctfshow_flag 文件中。

但它们使用了不同的命令来实现这一目的:

fake() 函数使用了 echo 'flag is here'>>/ctfshow_flag 命令,这个命令会将字符串附加到文件的末尾。
real() 函数使用了 echo 'flag is here'>/ctfshow_flag 命令,这个命令会将字符串写入到文件中,如果文件存在,则会覆盖文件内容。

如果让 real 函数被调用,则会覆盖掉原有的 flag 的内容,因此我们只能让 fake 函数被调用,让字符串 "flag is here" 被追加到 flag 内容的后面,这样我们也能看到原本的 flag 内容。

因此我们 nc 连上后,键入 9 ,v4 = 9 ,if 判断成立就会调用 fake 函数: 

得到 flag:ctfshow{54340fbc-d377-4f50-bdd0-25d2610395ae} 

2、pwn19

关闭了输出流,一定是最安全的吗?

老规矩下载附件 checkcheck

还是 64 位程序,保护全开 

ida 反编译后的伪代码: 

代码分析:

fork() 函数会创建一个新的进程,该进程是调用进程的副本,称为子进程,fork() 函数在父进程中返回子进程的进程 ID(非0为真),而在子进程中返回 0(为假),这样做是为了让父进程和子进程可以根据返回值的不同来执行不同的代码逻辑。

因此:

在父进程中,if (fork()) 语句会评估为 true,因为父进程中 fork() 返回的是一个非零值。在这种情况下,父进程会调用 wait() 函数等待子进程的结束,然后使用 sleep() 函数休眠 3 秒钟,最后打印一条消息:"flag is not here!"。

在子进程中,if (fork()) 语句会评估为 false,因为子进程中 fork() 返回的是 0。在这种情况下,子进程会输出一条消息:"give you a shell! now you need to get flag!",然后关闭一个文件流 _bss_start,接着读取用户输入到缓冲区 buf 中,最后调用 system() 函数执行用户输入的命令。

关于 read() 函数的用法:

用于从文件描述符中读取数据

ssize_t read(int fd, void *buf, size_t count);

参数解释:

  • fd:文件描述符,指定要读取的文件或者其他 I/O 设备。通常,0 表示标准输入(stdin)、1 表示标准输出(stdout)、2 表示标准错误输出(stderr)。

  • buf:用来存储读取数据的缓冲区的地址,read() 函数将从文件描述符中读取的数据存储到这个缓冲区中。

  • count:要读取的最大字节数。read() 函数最多会读取 count 个字节的数据。

因此这里表示从标准输入中读取最多 0x20uLL(32个)字节的数据,并将其存储到 buf 缓冲区中,再将读取内容作为 system() 函数的参数进行调用。

值得注意的是:

fclose(_bss_start); 

这个操作关闭了名为 _bss_start 的文件流

双击跟进看看:

根据这个定义,_bss_start 是一个文件指针(FILE*),它与输出流相关联,在C语言中,通常 _bss_start 会被初始化为标准输出流 stdout 或者其他类似的输出流。

通过 fclose(_bss_start); 关闭这个文件指针,会关闭与之关联的文件流,后续对该文件流的输出操作会失败,因为它已经被关闭了,这可能会影响程序的输出行为,导致一些输出不再被正确处理。

那么我们如何让执行结果被输出?

这里是在 else 语句中关闭的输出流,也就是 if 语句为假时,也就是说关闭的是子进程的输出流,但是父进程的输出流仍然是打开的。 

构造 payload :

exec cat /ctf* 1>&0

exec 是一个系统调用,用于替换当前进程的执行代码,通常用于在当前进程中执行外部命令,这里会执行 cat /ctf* 命令;

1>&0 是输入/输出重定向的语法,1 表示标准输出文件描述符,0 表示标准输入文件描述符,>& 意味着将输出重定向到指定的文件描述符,所以 1>&0 表示将标准输出重定向到标准输入;

1>&0 命令是在子进程中执行的,因为它在 else 分支中,1>&0 重定向子进程的标准输出到标准输入,因此子进程的输出会被发送到父进程的标准输入

最终将会在父进程中执行 cat /ctf* 命令,并且输出执行结果:

拿到 flag:ctfshow{55478b27-8da8-422a-af13-fb7bd9fcf89d} 

尝试以这种方法执行其他命令:

exec ls 1>&0

也是可行的 

使用完整的命令获取 flag (这里接收的最大长度是 32 字节,因此可以使用完整的文件名)

exec cat /ctfshow_flag 1>&0

 

3、总结

关于这两道题的总结:

1、>: 将命令的输出重定向到指定文件,如果文件不存在则创建,如果存在则覆盖文件内容;
>>: 将命令的输出追加到指定文件的末尾,如果文件不存在则创建。

2、fork() 函数的作用是创建一个新的进程,这个新进程是调用进程的副本。具体来说,fork() 函数在父进程中返回子进程的进程 ID,而在子进程中返回 0。通过这种方式,父子进程可以根据返回值的不同来执行不同的代码逻辑,实现并发执行或者多进程模型。

3、1>&0 这种输入/输出重定向操作符的作用是将标准输出重定向到标准输入,这种操作可以将一个进程的输出作为另一个进程的输入。在Unix/Linux系统中,每个进程都有三个默认打开的文件描述符:标准输入(文件描述符0)、标准输出(文件描述符1)、标准错误(文件描述符2)。通过将标准输出重定向到标准输入,可以实现将一个进程的输出传递给另一个进程。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐