ctfshow-pwn-前置基础(13-19)
pwn13
按照题目提示的信息,用gcc命令生成可执行文件,再运行即可得到flag
pwn14
题目提示:阅读以下源码,给定key为”CTFshow”,编译运行即可获得flag,那么我们直接看源代码
开始有一个文件是否存在的检查,如果当前目录下不存在名为"key"的文件就会报错
接下去就是通过循环将fp的值(也就是key的内容)逐个转为8位二进制数,通过_连接,用ctfshow{}包裹后存入output
用gcc命令生成可执行文件后运行,得到flag
pwn15
本题目是让我们将flag.asm汇编代码编译成可执行文件再运行即可拿到flag。那我们就下载flag.asm文件编译运行。
- nasm -f elf64 flag.asm # 将flag.asm编译成64为.o文件
- ld -s -o flag flag.o # 将flag.o链接成flag可执行文件
- ./flag # 运行flag可执行文件拿到flag
pwn16
本题是让我们使用gcc将flag.s文件编译成可执行文件。那我们就下载flag.s文件将其编译成可执行文件。
- gcc -o flag flag.s # 将flag.s编译成flag可执行文件
- ./flag # 运行flag可执行文件拿到flag
pwn17
题目给了个环境,直接先连上去看看
给了几个选项,我们看到了3选项有我们需要的信息
但是事与愿违,他并没有这么简单,所以我们把题目给的附件放到IDA里面看看,可以看到有个switch函数
- 我们先来分析一下代码的逻辑,首先是进入一个while循环打印出menu也就是菜单。然后是一个switch-case语句,根据我们输入的选项来执行分支语句。我们分析源码看到case 3中有一个system(“cat /ctfshow_flag”)的语句,也就是可以读取flag的语句。但是我们来看,这条sleep(0x1BF52u)语句是我们读取flag的障碍!因为这条语句它要睡眠0x1BF52秒啊,换算成10进制就是114514秒,那就是31个小时!这怎么等的了!所以另求他法。
- 我们再看case 2:如果我们选择的case2,首先它是输出一行字符串,然后让我们输入一行长度为0xA的字符串buf,也就是长度为9的字符串。接着把我们输入的字符串buf赋给dest,然后使用system函数将dest作为参数传入进行命令执行。选项2的功能总的来说就是,将我们输入的字符串当作参数传入system()函数!那我们就可以直接传入cat /ctfshow_flag吗?那当然不行的,因为代码已经限制了我们输入字符串的长度为9,所以我们得换个命令想办法读取flag。我们知道system(“/bin/sh”);是可以获得Linux的交互式shell的,正好/bin/sh的长度也没有超过9,所以我们就可以传入/bin/sh来获取交互式shell,进而手动执行cat /ctfshow_flag来get flag了!
输入/bin/sh,再用ls查目录,最后直接cat即可
pwn18
和上一题的做法一样,先连接看看,发现没有反应
那就拿IDA打开看看
我们先来分析一下代码逻辑,大概流程就是需要我们输入一个值v4,看这个值v4是否等于9,如果等于9就执行fake()函数,然后再执行system函数打印出flag;如果不等于9就先执行real()函数,然后再执行system函数打印出flag。
查看源码我们可以发现,real() 函数使用了 echo 'flag is here'>/ctfshow_flag 命令,这个命令会将字符串写入到文件中,如果文件存在,则会覆盖文件内容,而fake() 函数使用了 echo 'flag is here'>>/ctfshow_flag 命令,这个命令会将字符串附加到文件的末尾。如果让 real 函数被调用,则会覆盖掉原有的 flag 的内容,因此我们只能让 fake 函数被调用,让字符串 "flag is here" 被追加到 flag 内容的后面,这样我们也能看到原本的 flag 内容。
注意:靶机要重开,因为刚刚试的时候给flag覆盖了
pwn19(这个题参考别的师傅的)
nc之后发现,也是没东西
那就继续看代码吧
代码分析:
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* 命令,并且输出执行结果:
尝试以这种方法执行其他命令:
exec ls 1>&0
也是可行的
使用完整的命令获取 flag (这里接收的最大长度是 32 字节,因此可以使用完整的文件名)
exec cat /ctfshow_flag 1>&0