Skip to content

Pwnable tw 3x17

复制本地路径 | 在线编辑

需要掌握的知识点

  • 静态编译和动态编译,以及Stripped
  • Linux 程序执行流程(main 函数是如何执行起来的)
  • 64 Bit SystemCall in Assembly

漏洞分析

Stage 1

首先很不幸,IDA Pro分析程序,发现全是SUB_XXX的样式。所以应该先恢复符号表,这个请看另外一篇文章。

Stage 2

恢复完成,代码如下。

int __cdecl main(int argc, const char **argv, const char **envp)
{
    int result; // eax
    int v4; // eax
    char *v5; // ST08_8
    char buf; // [rsp+10h] [rbp-20h]
    unsigned __int64 cookie; // [rsp+28h] [rbp-8h]

    cookie = __readfsqword(0x28u);
    result = (unsigned __int8)++byte_4B9330;
    if ( byte_4B9330 == 1 )
    {
        write(1u, "addr:", 5uLL);
        read(0, &buf, 0x18uLL);
        sub_40EE70((__int64)&buf);
        v5 = (char *)v4;
        write(1u, "data:", 5uLL);
        read(0, v5, 0x18uLL);
        result = 0;
    }
    return result;
}

分析代码,现在唯一的问题就是这个 SUB_40EE70,可以看到在他函数之后,他将 eax 赋给了另外一个变量。而我们知道在64Bit中,eax 存放的是返回值,所以进入 GDB 调试。

查看的情况就不贴图了,输入 100,发现经过这个函数之后 eax 为0x64,所以说这个函数功能就是把字符串参数转换为数值。这个函数我原来以为是自创的。后来查阅得知这个函数就是 strtol (想一下atoi,是不是很像,就是属于XtoY的一类)。

Stage 3

那么程序的逻辑清楚了: 输入一个字符串, 程序将其转为数值, 之后可以对这个数值指向的内存进行写入。但是关键在于只能执行一次任意写,只有一次修改这种威力完全不够。所以怎么可以多次进行任意写呢?

答案: fin_array, 查阅代码,观察_libc_csu_fini。

__int64 _libc_csu_fini()
{
    signed __int64 index; // rbx

    // 右移3位,即除以8,因为前面的那个是1-Byte排序,而index要的是4-Byte排序
    // (4B4100 - 4B40F0) / 8 = 2,所以该函数一共执行2次子函数,即fin_arr[1], fin_arr[0]
    if ( (&unk_4B4100 - (_UNKNOWN *)off_4B40F0) >> 3 )
    {
        index = ((&unk_4B4100 - (_UNKNOWN *)off_4B40F0) >> 3) - 1;
        do
            off_4B40F0[index--]();
        while ( index != -1 );
    }
    return sub_48E32C();
}

注意fin_array是逆序执行,fin_array[1] 更改为main函数,然后就可以继续执行main函数,然后进行多次任意写了。

你要问我为什么不把fin_array[0]改为main函数,事实上它要改成_libc_csu_fini(),即利用它重新执行该fin_arr[1]。

如果把fin_arr[0]改成main函数,那么执行完两次main函数,_libc_csu_fini()就结束了,可以看到 main 方法中只有flag == 1 才会执行,所以执行三次 main 函数丝毫不会有影响。只有不断执行 main 函数,让flag 溢出回到1,这样才OK。

Stage 4

所以就先把 fin_array 改掉,然后可以任意写之后,构造 ret2syscall 咯。现在问题是如何确保最后执行了 ret2syscall,最后一次返回地址改掉就好了哇。

坑点

第一次应该怎么写,可以确保有第二次写的出现?

我本来是打算,写成fin_array[1] = main,然后第二次再进行fin_array[0] = libc_csu_fin,但是结果失败。

原因在于,虽然确实执行了main方法,但是里面(flag == 2),因此不会执行写操作。

正确做法是,一口气把fin_array全部写完,这样才能保证会不断循环,然后有了第二次写的出现

总结反思

  • 经验
  • 只有一次写的机会,可以尝试修改fin_array

Comments