我有一个正在使用的ELF可执行文件(已从以前的CTF竞赛中获得)。该可执行文件仅要求输入密码,然后输出“恭喜”。




代码片段和注释很长,因此我将它们包含在单独的HTML链接中。

反编译的C -Code

ELF可执行文件

//Gives you an overview of what this program does in C.
int main(int argc, char ** argv)
{
    int32_t result; // success value


    if (argc > 1)
    {
        setup(); //Allocates an integer array (g1)

        int32_t v1 = 0; //counter

        while (true)
        {
            char v2 = *(char *) (*(int32_t *) ((int32_t)argv + 4) + v1); 
            int32_t v3 = *(int32_t *) (4 * v1 + (int32_t) & g1);

            int32_t v4 = v1 + 1; //index in g1 array

            if (check( (int32_t *) v3, (int32_t) v2) != 0)
            {
                puts("Nope");
                return -1;
            }
            if (v4 >= 31) //when we've reached our last index in array
            {
                break;
            }
            v1 = v4;
        }
        puts("congrats!");
        result = 0;
    }
    else
    {
        puts("usage ./smoothie ");
        result = -1;
    }
    return result;
}



我尝试使用GDB调试程序和IDA以了解控件-流。我的推论最后涉及三件事:


setup()生成某种整数数组
check(int32_t * array,int * array)被调用31次以确认我们如可能的while循环所示,“ password”在check(...)内,它通过每个malloc数组[5]并确认密码的每个“字符”。
这是IDA的check()控制流程图(简单-3个部分):




这是check()的控制流程图点击放大:




大多数check()比较*(数组)为上半部分和*(数组+ 8)表示下半部分,再加上passwordIndex比较。
在该程序的main(int argc,char ** argv)中,有一个while循环需要执行31次(密码长度可能为31个字符)。如果check()返回1,则while循环将退出。


我认为中间部分会进行一些有用的计算(add,sub,cdq和idiv,imul和xor)。

请引导我正确的方向(如果可以,请提供提示)。自己调试程序可能会让我的问题更清楚。 “我的输入ascii应该匹配的正确数字(例如,计算得出的90,因此index [i]应该等于'Z'〜90)。
在检查的后半部分,它使用*(数组+ 8),这意味着它在101、142,...,数字比较中使用数组的第三个数字。我们可以将*(数组+ 8)的解引用值更改为计算得出的数字(90),以进行最终比较。的支票数,因此90 ==90。由于它们相等,因此函数成功返回0。由于数组中的预设值而相同。我该怎么做才能纠正这个问题?

评论

所以你在这里问什么?

我添加了check()的整个控制流图。通过检查0x65至0xfa,它们在某种程度上与分配的数组中的值匹配。检查的前半部分对应于*(数组),后半部分对应于*(数组+ 8)。我可以在GNU调试器中做一些事情来通过数组传递这些检查吗?

那是什么周大福?

CTF-Sparsa。调试-此处未上传200(或冰沙):github.com/pchaigno/ctf-sparsa。这就是比赛的基本样子(危险的风格)。

好的,我已经分析了二进制文件并知道标记具有99%的概率,但是当前的二进制文件存在缺陷。对于今天,我唯一能提供的建议就是尝试分析设置并检查(尤其是它与设置之间的关系),您将了解如何获取标志。但是,再次-二进制是错误的-我明天晚些时候发布(现在是午夜)

#1 楼

解决此CTF的方法是同时分析setup()check()方法(duh!)。第一个很长,但是当您检查正在发生的事情时,这很简单。它正在分配20B的内存来存储5 x 4 B的数据。它执行了31次。在那里存储的值看起来像是随机的,但不是。第一个和第三个位置只有5个不同的值。


第一个和第三个插槽中的值是操作类型值;第三,第四-操作数; 5-操作数和结果


如果我们检查check()函数。我们看到正在传递的参数是:在标志的setup()char中创建并填充的缓冲区之一。根据缓冲区中找到的值,可以对缓冲区中的值进行数学运算。


有关该运算的更多信息。基于第一和第三值的数量很少。如果我们以第一个缓冲区的值为例:0x01 0x81 0x65 0x0C 0x5A 0x01-修改操作,0x65-添加;因此字符为(0x5A%0x81)+ 0x0C。我们在每个缓冲区上进行模拟(但操作不同)。


此二进制文件有什么问题(或者可能是故意的)?好吧,几乎在所有情况下,操作数上计算出的值都不会与标志的char进行比较,并且此eax的设置不正确,我们得到“无”。需要解决的是更改跳转,以便所有情况都通过检查标志是否为char。除了修改二进制文件之外,您可以做的就是欺骗gdb以使其正常运行。


leave上设置断点


type命令
/> type p / c $ edx
type set $ eax = 0
type end



这样,只要触发断点,就将打印计算出的字符,并且它将模拟比较正常,并允许应用程序继续。有了它,您可以运行该应用程序,点击几次“ c”并观察标志

最后是标志


标志{HereBeDaFlagForYoDebu?g? h}


还有一些解释...


这两个'?'控制字节错误或操作数错误,因此它们的计算错误。也许我会花一些时间找出并尝试正确地计算它们,但是使用当前值,我们得到的结果超出了ASCII可打印的范围。

#2 楼

编辑:正如PawełŁukasik和DittoPDX所说,二进制文件确实存在缺陷,因此不允许使用任何正确的标志。但是,下面的方法仍然是有效的,并且可以在类似的任务中提供帮助。只需很少的逆向工程即可完成的任务。

整个解决方案取决于构建主循环的方式:

for ( i= 0; i <= 30; i++)
{
    if ( check(ec[i], argv[1][i]) )
    {
        puts("Nope");
        return -1;
    }
}
puts("congrats!");
result = 0;


唯一的方法达到“恭喜!”
有趣的是,对输入的每个字符调用check函数,并且循环在无效时就存在。
br />
这种构造对您可以在程序执行期间评估的事物有一些暗示。可以将其称为“旁道攻击”。


扰流板,在此标记下方是解决此任务的更多详细信息。



我的意思是:每获得一个正确的char,二进制执行的指令数量就会增加。

解决这一难题的一种方法是计算已执行指令的数量(提示:PinTools;源代码/工具/ ManualExamples /实例),并通过char暴力破解输入的char。每当您猜到正确的char时,执行指令的数量就会增加。

编写所有脚本的简单方法是使用pwntools python模块:)


希望这对CTF有所帮助!

评论


提到上面Lukasik的“有缺陷的二进制”注释方式,我认为即使我确实找到了密码,程序也不会接受它,因为它需要我们手动调试程序并找出每个单独的ascii字符。如果我们在扩展的ascii表中输入(0-256)的任何单个输入,则无论该数组如何,都将返回false。为了通过第二组检查,我们必须手动更改数组值以匹配其计算值。

–user21677
17/09/22在2:00