sandboxie没有生态,除了大佬多数和我一样是新手。
开源后业余时间断续学习,啃别人代码,理解他人思路属实不易,源码笔记有点乱序,后经过整理才有这篇文章,分享学习过程中遇到问题和思路。
本篇文章并不是轻量级沙盘实现原理,关注者可以直接略过该文。
共37页,部分内容可能不严谨,但是会随着学习的深入不断完善和改正错误。
预计分为4篇:
1. 进程篇
2. 监控篇
3. toB(正在研究的阶段)
4. 原理机制
Ps:多少参杂了一些逆向,LPC通信Handler处理用windbg附加SbieSvc服务,利用Start.exe挂调试,IDA定位分析/源码辅助学习,调试过程有些问题拿捏不准,感谢前辈的帮助。
SandBoxie循序渐进耳(进程篇)
序
关于沙箱,工作之外断断续续学习走过不少坑,去年部署Cuckoo用来个人学习优化Monitor。也尝试过开发轻量级沙盘,逆向Sandboxie连皮毛都没学到,幸好今年SandBoxie开源给学习者带来了更多的思路。
作者推荐了代码审计重点,个人喜欢从界面开始熟悉,交互是响应各功能模块第一步,相对比较友好,较快的理解软件流程的来龙去脉。
推荐一个活跃的sandboxie生态论坛分支,也许你会找到想要的答案:
https://www.wilderssecurity.com/forums/sandboxing-virtualization.98/
SandBoxie:
源码编译:
1. 如果系统已有wdk7600环境,vs2015配置7600,请参考下述文章:
https://www.cnblogs.com/iBinary/p/8290595.html
2. 解决error LNK2001: 无法解析的外部符号 _memcmp/_wcsnicmp等被引用原因:这个过程浪费了大量的业余时间,引入各类函数lib无济于事,修复skd环境,大量的obj报错最终参考msdn文档解决,在Sandboxie的论坛上也发现了解决方案。
https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=vs-2019
https://community.sophos.com/products/sandboxie/f/forum/119641/important-sandboxie-open-source-code-is-available-for-download
3. 解决MinFilter编译被引用,属性-->链接器-->输入-->附加依赖项目:fltMgr.lib
4. 编译成功,不错的开端(vs2017同样编译成功,v141)。
安装沙盘:
这个步骤该阶段不太重要,不影响源码学习。Install/ReadMe按照步骤来做,NSIS已经更新到了v3,按照步骤安装和替换文件。Sandbox.sln同级目录下创建tools/iconv,解压拷贝,这些工作一次性便适用,不需要重复工。
DavidXanatos推出了SandBoxie-Plush,使用了QT,如果不加入其它操作系统分析引擎(可移植),Win下面向界面便捷开发更倾向于使用Libuidk实现(基于MFC商用框架已开源-国外人可能也不知道),容易重构。
https://github.com/sandboxie-plus/Sandboxie
源码学习:
可以下载发行版Sandboxie安装到主机,如果熟悉MFC这将是好的开端,能快速定位代码,控件响应和功能模块,代码重在理解和应用,汲取自己需要的知识。
创建沙盘(r3)

Create New SandBox找到对应的类
1. 输入沙盘名称做规则检测,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | if (len <= 32) {
for (; i < len; ++i) {
if (name[i] >= L '0' && name[i] <= L '9' )
continue ;
if (name[i] >= L 'A' && name[i] <= L 'Z' )
continue ;
if (name[i] >= L 'a' && name[i] <= L 'z' )
continue ;
if (name[i] == L '_' )
continue ;
break ;
}
}
if (i == 0 || i != len)
errmsg = MSG_3667;
|
2.调用SbieApi_IsBoxEnabled()判断沙盘名称是否已创建,发送API_SBIEDRV_CTLCODEDLL查询,封装了两个重要的参数:
1. API_IS_BOX_ENABLED(Driver API Codes)
2. Name_Address
3. Drv驱动 --> api.c(370行开始),对分发进行校验和处理:
3. 校验API_SBIEDRV_CTLCODEDLL
4. 分发api,回调查询code-->API_IS_BOX_ENABLED
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | __try {
ProbeForRead(
buf, sizeof ( ULONG64 ) * API_NUM_ARGS, sizeof ( ULONG64 ));
memzero(user_args, sizeof ( ULONG64 ) * API_NUM_ARGS);
memcpy (user_args, buf, buf_len);
func_code = ( ULONG )user_args[0];
if (func_code > API_FIRST && func_code < API_LAST)
func_ptr = Api_Functions[func_code - API_FIRST - 1];
if (func_ptr) {
status = func_ptr(proc, user_args);
} else
status = STATUS_INVALID_DEVICE_REQUEST;
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
Api_functions通过SetFunction初始化(conf_user.c-->106行),初始化分发函数.
Api_SetFunction(API_IS_BOX_ENABLED, Conf_Api_IsBoxEnabled)回调函数处理,如果不存在添加到链中,返回状态码标识是否成功,详细请见代码。
沙盘进程运行:
1. 选中沙盘点击运行,也可以直接拖到Dlg中,如下所示:
2. 思路有很多种,最简单直接的办法搜索函数,这里拖拽文件响应使用OnDropFiles,该工程也只有这一处拖拽使用,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void CMyFrame::OnDropFiles( HDROP hDrop)
{
WCHAR path[512];
if (! DragQueryFile(hDrop, 0, path, sizeof (path) / sizeof ( WCHAR ) - 1))
path[0] = L '\0' ;
if (path[0]) {
CString QuotedPath = L "\"" + CString(path) + L "\"" ;
CMyApp::RunStartExe(QuotedPath, CString());
}
}
void CMyApp::RunStartExe(
const CString &cmd, const CString &box, BOOL wait, BOOL inherit)
{
Common_RunStartExe(cmd, box, wait, inherit);
}
|
3. 沙盘如何将可执行文件加载,这是一个漫长的过程,RunStartExe.cpp --> 35行Common_RunStartExe,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | wcscpy(cmdline, L "/box:" );
if (box.IsEmpty())
wcscat(cmdline, L "__ask__" );
else
wcscat(cmdline, box);
wcscat(cmdline, L " " );
wcscat(cmdline, cmd);
if (! SbieDll_RunFromHome(START_EXE, cmdline, &si, &pi));
|
4. SbieDll_RunFromHome被定义在了proc.c进程模块,最终会通过CreateProcessW来启动文件,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if (si->lpReserved) {
inherit = TRUE;
si->lpReserved = NULL;
} else
inherit = FALSE;
if (si->dwXCountChars == tzuk && si->dwYCountChars == tzuk) {
creation_flags = ABOVE_NORMAL_PRIORITY_CLASS;
si->dwXCountChars = si->dwYCountChars = 0;
} else
creation_flags = 0;
memzero(pi, sizeof (PROCESS_INFORMATION));
ok = CreateProcess(NULL, path, NULL, NULL, inherit, creation_flags,
NULL, NULL, si, pi)
|
参数:"E:\Program Files\Sandboxie\Start.exe" /box:__ask__ "C:\Users\Administrator\Downloads\powershell.txt"
5. Start.exe,可以选择x64跟踪,也可以选择参数带入源码,解析格式box和_,查询进程信息,box:__ask__,如下所示:
1 2 3 4 | if (_wcsnicmp(cmd, L "box:" , 4) == 0) {
if (*tmp == L '-' &&
(! SbieApi_QueryProcessInfo(
( HANDLE )( ULONG_PTR )GetCurrentProcessId(), 0))) {
|
针对一些指令会进行处理,Parse_Command_Line()返回执行文件路径。
Validate_Box_Name选择使用沙盘,代码如下:
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 | if (! disable_force_on_this_program) {
if (_wcsicmp(BoxName, L "__ask__" ) == 0 ||
_wcsicmp(BoxName, L "current" ) == 0) {
if (auto_select_default_box) {
wcscpy(BoxName, L "DefaultBox" );
if (SbieApi_IsBoxEnabled(BoxName) != STATUS_SUCCESS)
auto_select_default_box = FALSE;
}
if (! auto_select_default_box)
wcscpy(BoxName, DoBoxDialog());
if (! BoxName[0]) {
if (disable_force_on_this_program) {
return FALSE;
}
return die(EXIT_FAILURE);
}
}
if (SbieApi_IsBoxEnabled(BoxName) != STATUS_SUCCESS) {
if (run_silent)
ExitProcess(ERROR_UNKNOWN_PROPERTY);
SetLastError(0);
Show_Error(SbieDll_FormatMessage1(MSG_3204, BoxName));
return die(EXIT_FAILURE);
}
}
|
6. 通过return die(RestartInSandbox())完成进程启动,RestartInSandbox函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | dir = ( WCHAR *)MyHeapAlloc(1024 * sizeof ( WCHAR ));
GetCurrentDirectory(1020, dir);
cmd = ( WCHAR *)MyHeapAlloc(16384 * sizeof ( WCHAR ));
cmd[0] = L '\"' ;
GetModuleFileName(NULL, cmd + 1, MAX_PATH);
ptr = cmd + wcslen(cmd);
wcscpy(ptr, L "\" /env:00000000_" SBIE L "_CURRENT_DIRECTORY=\"" );
wcscat(ptr, dir);
wcscat(ptr, L "\" /env:=Refresh " );
ptr += wcslen(ptr);
if (run_elevated_2) {
wcscpy(ptr, L "/elevate " );
ptr = cmd + wcslen(cmd);
}
if (wait_for_process) {
wcscpy(ptr, L "/wait " );
ptr = cmd + wcslen(cmd);
}
wcscpy(ptr, ChildCmdLine);
|
7. 调用Callsvc.c(dll模块)处理req,req结构体下文会介绍,数据如下:
1 2 3 | rpl = (
PROCESS_RUN_SANDBOXED_RPL
*)SbieDll_CallServer(&req->h);
|
SandBoxie-LPC通信

LPC参考:https://bbs.pediy.com/thread-144492.htm
8. 本地通信模块需要熟悉LPC,推荐如下:
https://bbs.pediy.com/thread-144492.htm
https://bbs.pediy.com/thread-162365.htm
Client-Server调试方式,每个人都有自己的学习习惯,文章使用的第二种方式:
1. vs执行start源码,vs执行SbieSvc源码,编译好全部工程,输出目录同一个文件夹下,安装驱动,然后进行调试。(未尝试,理论可行)
2. vs执行start源码,windbg附加SbieSvc服务,IDA+源码辅助学习。
Client:
SbieDll_RunSandboxed()
负责tagPROCESS_RUN_SANDBOXED_REQ结构数据填充,前8bit数据MSG_HEADER结构体。
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 | req->h.msgid = MSGID_PROCESS_RUN_SANDBOXED;
wcscpy(req->boxname, box_name);
req->si_flags = si->dwFlags;
req->si_show_window = si->wShowWindow;
req->creation_flags = creation_flags;
ptr = ( WCHAR *)(( ULONG_PTR )req + sizeof (PROCESS_RUN_SANDBOXED_REQ));
req->cmd_ofs = ( ULONG )(( ULONG_PTR )ptr - ( ULONG_PTR )req);
req->cmd_len = cmd_len;
if (cmd_len) {
wmemcpy(ptr, cmd, cmd_len);
ptr += cmd_len;
}
*ptr = L '\0' ;
++ptr;
req->dir_ofs = ( ULONG )(( ULONG_PTR )ptr - ( ULONG_PTR )req);
req->dir_len = dir_len;
if (dir_len) {
wmemcpy(ptr, dir, dir_len);
ptr += dir_len;
}
*ptr = L '\0' ;
++ptr;
req->env_ofs = ( ULONG )(( ULONG_PTR )ptr - ( ULONG_PTR )req);
req->env_len = env_len;
if (env_len) {
wmemcpy(ptr, env, env_len);
ptr += env_len;
}
rpl = (PROCESS_RUN_SANDBOXED_RPL *)SbieDll_CallServer(&req->h);
|
tagPROCESS_RUN_SANDBOXED_REQ结构,后面尾随的是消息块包括执行cmd和dir,env环境,内存数据如下所示:
SbieDll_CallServer()
负责将客户端结构数据分发至服务端处理。
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | _FX MSG_HEADER *SbieDll_CallServer(MSG_HEADER *req)
{
static volatile ULONG last_sequence = 0;
UCHAR curr_sequence;
THREAD_DATA *data = Dll_GetTlsData(NULL);
UCHAR spaceReq[MAX_PORTMSG_LENGTH], spaceRpl[MAX_PORTMSG_LENGTH];
NTSTATUS status;
PORT_MESSAGE *msg;
UCHAR *buf, *msg_data;
ULONG buf_len, send_len;
MSG_HEADER *rpl;
if (! data->PortHandle) {
BOOLEAN Silent = (req->msgid == MSGID_SBIE_INI_GET_VERSION ||
req->msgid == MSGID_SBIE_INI_GET_USER ||
req->msgid == MSGID_PROCESS_CHECK_INIT_COMPLETE);
if (! SbieDll_ConnectPort(Silent))
return NULL;
}
curr_sequence = ( UCHAR ) InterlockedIncrement(&last_sequence);
buf = ( UCHAR *)req;
buf_len = req->length;
while (buf_len) {
msg = (PORT_MESSAGE *)spaceReq;
memzero(msg, data->SizeofPortMsg);
msg_data = ( UCHAR *)msg + data->SizeofPortMsg;
if (buf_len > data->MaxDataLen)
send_len = data->MaxDataLen;
else
send_len = buf_len;
msg->u1.s1.DataLength = ( USHORT )send_len;
msg->u1.s1.TotalLength = ( USHORT )(data->SizeofPortMsg + send_len);
memcpy (msg_data, buf, send_len);
if (buf == ( UCHAR *)req) {
msg_data[3] = curr_sequence;
}
buf += send_len;
buf_len -= send_len;
status = NtRequestWaitReplyPort(data->PortHandle,
(PORT_MESSAGE *)spaceReq, (PORT_MESSAGE *)spaceRpl);
if (! NT_SUCCESS(status))
break ;
msg = (PORT_MESSAGE *)spaceRpl;
if (buf_len && msg->u1.s1.DataLength) {
SbieApi_Log(2203, L "early reply" );
return NULL;
}
}
|
Service:
Sandboxie客户端循环发送MSG消息到服Svc.Pipserver.cpp(SbieSvcPort),服务端由NtReplyWaitReceivePort循环等待客户端数据,源码中封装如下:
NtReplyWaitReceivePort():
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | void PipeServer::Thread()
{
NTSTATUS status;
UCHAR space[MAX_PORTMSG_LENGTH], spaceReply[MAX_PORTMSG_LENGTH];
PORT_MESSAGE *msg = (PORT_MESSAGE *)space;
HANDLE hReplyPort;
PORT_MESSAGE *ReplyMsg;
hReplyPort = m_hServerPort;
ReplyMsg = NULL;
while (1) {
if (ReplyMsg) {
memcpy (spaceReply, ReplyMsg, ReplyMsg->u1.s1.TotalLength);
ReplyMsg = (PORT_MESSAGE *)spaceReply;
}
status = NtReplyWaitReceivePort(hReplyPort, NULL, ReplyMsg, msg);
if (! m_hServerPort)
break ;
if (ReplyMsg) {
hReplyPort = m_hServerPort;
ReplyMsg = NULL;
if (! NT_SUCCESS(status))
continue ;
} else if (! NT_SUCCESS(status)) {
if (status == STATUS_UNSUCCESSFUL) {
continue ;
}
break ;
}
if (msg->u2.s2.Type == LPC_CONNECTION_REQUEST) {
PortConnect(msg);
} else if (msg->u2.s2.Type == LPC_REQUEST) {
CLIENT_THREAD *client = (CLIENT_THREAD *)PortFindClient(msg);
if (! client)
continue ;
if (! client->replying)
PortRequest(client->hPort, msg, client);
msg->u2.ZeroInit = 0;
if (client->replying)
PortReply(msg, client);
else {
msg->u1.s1.DataLength = ( USHORT ) 0;
msg->u1.s1.TotalLength = sizeof (PORT_MESSAGE);
}
hReplyPort = client->hPort;
ReplyMsg = msg;
client->in_use = FALSE;
} else if (msg->u2.s2.Type == LPC_PORT_CLOSED ||
msg->u2.s2.Type == LPC_CLIENT_DIED) {
PortDisconnect(msg);
}
}
}
|
构造PORT_MESSAGE数据包,data->MaxDataLen数值是288bit,TotalLength总大小是328bit,包含结构体本身大小,LPC传输超过256bit进行内存共享传输。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | msg = (PORT_MESSAGE *)spaceReq;
memzero(msg, data->SizeofPortMsg);
msg_data = ( UCHAR *)msg + data->SizeofPortMsg;
if (buf_len > data->MaxDataLen)
send_len = data->MaxDataLen;
else
send_len = buf_len;
msg->u1.s1.DataLength = ( USHORT )send_len;
msg->u1.s1.TotalLength = ( USHORT )(data->SizeofPortMsg + send_len);
memcpy (msg_data, buf, send_len);
.......
buf += send_len;
buf_len -= send_len;
|
windbg下断SbieSvc.exe-->NtReplyWaitReceivePort,客户端发送MSG至服务端消息队列,也可以直接审计源码,如下所示:
Sbiesvc.exe拖入ida,定位代码段:
LPC处理LPC_CONNECTION_REQUEST,LPC_REQUEST,LPC_PORT_CLOSED,LPC_CLIENT_DIED类型。
客户端client->replying标志,客户端在循环传输过程中调用PortRequest。
1 2 | if (! client->replying)
PortRequest(client->hPort, msg, client);
|
PortRequest():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if (! client->buf_hdr) {
ULONG *msg_Data = ( ULONG *)msg->Data;
ULONG msgid = msg_Data[1];
client->sequence = (( UCHAR *)msg_Data)[3];
(( UCHAR *)msg_Data)[3] = 0;
buf_len = msg_Data[0];
if (msgid && buf_len &&
buf_len < MAX_REQUEST_LENGTH &&
buf_len >= sizeof (MSG_HEADER) &&
buf_len >= msg->u1.s1.DataLength) {
client->buf_hdr = AllocMsg(buf_len);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | client->buf_ptr = ( UCHAR *)client->buf_hdr;
}
if (! client->buf_hdr) {
client->sequence = 0;
goto finish;
}
memcpy (client->buf_ptr, msg->Data, msg->u1.s1.DataLength);
client->buf_ptr += msg->u1.s1.DataLength;
buf_len += msg->u1.s1.DataLength;
client->buf_hdr = (MSG_HEADER *)buf_ptr;
client->buf_ptr = ( UCHAR *)buf_ptr
client->replying = TRUE;
if (buf_len < client->buf_hdr->length)
return ;
buf_ptr = CallTarget(client->buf_hdr, PortHandle, msg);
|
CallTarget():
负责LPC Handler函数分发,LPC Handler根据Msgid执行功能函数。
定位分发函数,断点0x1205观察,通过搜索定位函数代码,如下所示:
*ProcessServer::Handler()
IDA-Windbg对比源码无误,下断g运行,如下所示:
运行后会触发LPC回调分发,MSGID_PROCESS_RUN_SANDBOXED调用号断下1205,ProcessServer.cpp(line:428),启动新进程主模块。
*ProcessServer::RunSandboxedHandler()
1 | MSG_HEADER *ProcessServer::RunSandboxedHandler(MSG_HEADER *msg)
|
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-7-16 15:03
被一半人生编辑
,原因: