1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
11 #include <atlsecurity.h>
15 #include "sandbox/win/sandbox_poc/main_ui_window.h"
16 #include "base/logging.h"
17 #include "sandbox/win/sandbox_poc/resource.h"
18 #include "sandbox/win/src/acl.h"
19 #include "sandbox/win/src/sandbox.h"
20 #include "sandbox/win/src/win_utils.h"
22 HWND
MainUIWindow::list_view_
= NULL
;
24 const wchar_t MainUIWindow::kDefaultDll_
[] = L
"\\POCDLL.dll";
25 const wchar_t MainUIWindow::kDefaultEntryPoint_
[] = L
"Run";
26 const wchar_t MainUIWindow::kDefaultLogFile_
[] = L
"";
28 MainUIWindow::MainUIWindow()
29 : instance_handle_(NULL
),
36 MainUIWindow::~MainUIWindow() {
39 unsigned int MainUIWindow::CreateMainWindowAndLoop(
41 wchar_t* command_line
,
43 sandbox::BrokerServices
* broker
) {
48 instance_handle_
= instance
;
49 spawn_target_
= command_line
;
52 // We'll use spawn_target_ later for creating a child process, but
53 // CreateProcess doesn't like double quotes, so we remove them along with
54 // tabs and spaces from the start and end of the string
55 const wchar_t *trim_removal
= L
" \r\t\"";
56 spawn_target_
.erase(0, spawn_target_
.find_first_not_of(trim_removal
));
57 spawn_target_
.erase(spawn_target_
.find_last_not_of(trim_removal
) + 1);
59 WNDCLASSEX window_class
= {0};
60 window_class
.cbSize
= sizeof(WNDCLASSEX
);
61 window_class
.style
= CS_HREDRAW
| CS_VREDRAW
;
62 window_class
.lpfnWndProc
= MainUIWindow::WndProc
;
63 window_class
.cbClsExtra
= 0;
64 window_class
.cbWndExtra
= 0;
65 window_class
.hInstance
= instance
;
67 ::LoadIcon(instance
, MAKEINTRESOURCE(IDI_SANDBOX
));
68 window_class
.hCursor
= ::LoadCursor(NULL
, IDC_ARROW
);
69 window_class
.hbrBackground
= GetStockBrush(WHITE_BRUSH
);
70 window_class
.lpszMenuName
= MAKEINTRESOURCE(IDR_MENU_MAIN_UI
);
71 window_class
.lpszClassName
= L
"sandbox_ui_1";
72 window_class
.hIconSm
= NULL
;
74 INITCOMMONCONTROLSEX controls
= {
75 sizeof(INITCOMMONCONTROLSEX
),
76 ICC_STANDARD_CLASSES
| ICC_LISTVIEW_CLASSES
78 ::InitCommonControlsEx(&controls
);
80 if (!::RegisterClassEx(&window_class
))
81 return ::GetLastError();
83 // Create a main window of size 600x400
84 HWND window
= ::CreateWindowW(window_class
.lpszClassName
,
92 NULL
, // NULL = use class menu
97 return ::GetLastError();
99 ::SetWindowLongPtr(window
,
101 reinterpret_cast<LONG_PTR
>(this));
103 ::SetWindowText(window
, L
"Sandbox Proof of Concept");
105 ::ShowWindow(window
, show_command
);
108 // Now lets start the message pump retrieving messages for any window that
109 // belongs to the current thread
110 while (::GetMessage(&message
, NULL
, 0, 0)) {
111 ::TranslateMessage(&message
);
112 ::DispatchMessage(&message
);
118 LRESULT CALLBACK
MainUIWindow::WndProc(HWND window
,
122 MainUIWindow
* host
= FromWindow(window
);
124 #define HANDLE_MSG(hwnd, message, fn) \
125 case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
127 switch (message_id
) {
129 // 'host' is not yet available when we get the WM_CREATE message
130 return HANDLE_WM_CREATE(window
, wparam
, lparam
, OnCreate
);
132 return HANDLE_WM_DESTROY(window
, wparam
, lparam
, host
->OnDestroy
);
134 return HANDLE_WM_SIZE(window
, wparam
, lparam
, host
->OnSize
);
136 // Look at which menu item was clicked on (or which accelerator)
137 int id
= LOWORD(wparam
);
142 case ID_COMMANDS_SPAWNTARGET
:
143 host
->OnCommandsLaunch(window
);
146 // Some other menu item or accelerator
150 return ERROR_SUCCESS
;
154 // Some other WM_message, let it pass to DefWndProc
158 return DefWindowProc(window
, message_id
, wparam
, lparam
);
161 INT_PTR CALLBACK
MainUIWindow::SpawnTargetWndProc(HWND dialog
,
165 UNREFERENCED_PARAMETER(lparam
);
167 // Grab a reference to the main UI window (from the window handle)
168 MainUIWindow
* host
= FromWindow(GetParent(dialog
));
171 switch (message_id
) {
172 case WM_INITDIALOG
: {
173 // Initialize the window text for DLL name edit box
174 HWND edit_box_dll_name
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
175 wchar_t current_dir
[MAX_PATH
];
176 if (GetCurrentDirectory(MAX_PATH
, current_dir
)) {
177 base::string16 dll_path
= base::string16(current_dir
) +
178 base::string16(kDefaultDll_
);
179 ::SetWindowText(edit_box_dll_name
, dll_path
.c_str());
182 // Initialize the window text for Entry Point edit box
183 HWND edit_box_entry_point
= ::GetDlgItem(dialog
, IDC_ENTRY_POINT
);
184 ::SetWindowText(edit_box_entry_point
, kDefaultEntryPoint_
);
186 // Initialize the window text for Log File edit box
187 HWND edit_box_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
188 ::SetWindowText(edit_box_log_file
, kDefaultLogFile_
);
190 return static_cast<INT_PTR
>(TRUE
);
193 // If the user presses the OK button (Launch)
194 if (LOWORD(wparam
) == IDOK
) {
195 if (host
->OnLaunchDll(dialog
)) {
196 if (host
->SpawnTarget()) {
197 ::EndDialog(dialog
, LOWORD(wparam
));
200 return static_cast<INT_PTR
>(TRUE
);
201 } else if (LOWORD(wparam
) == IDCANCEL
) {
202 // If the user presses the Cancel button
203 ::EndDialog(dialog
, LOWORD(wparam
));
204 return static_cast<INT_PTR
>(TRUE
);
205 } else if (LOWORD(wparam
) == IDC_BROWSE_DLL
) {
206 // If the user presses the Browse button to look for a DLL
207 base::string16 dll_path
= host
->OnShowBrowseForDllDlg(dialog
);
208 if (dll_path
.length() > 0) {
209 // Initialize the window text for Log File edit box
210 HWND edit_box_dll_path
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
211 ::SetWindowText(edit_box_dll_path
, dll_path
.c_str());
213 return static_cast<INT_PTR
>(TRUE
);
214 } else if (LOWORD(wparam
) == IDC_BROWSE_LOG
) {
215 // If the user presses the Browse button to look for a log file
216 base::string16 log_path
= host
->OnShowBrowseForLogFileDlg(dialog
);
217 if (log_path
.length() > 0) {
218 // Initialize the window text for Log File edit box
219 HWND edit_box_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
220 ::SetWindowText(edit_box_log_file
, log_path
.c_str());
222 return static_cast<INT_PTR
>(TRUE
);
228 return static_cast<INT_PTR
>(FALSE
);
231 MainUIWindow
* MainUIWindow::FromWindow(HWND main_window
) {
232 // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
233 // so that we can retrieve it with this function later. This prevents us
234 // from having to define all the message handling functions (that we refer to
235 // in the window proc) as static
236 ::GetWindowLongPtr(main_window
, GWLP_USERDATA
);
237 return reinterpret_cast<MainUIWindow
*>(
238 ::GetWindowLongPtr(main_window
, GWLP_USERDATA
));
241 BOOL
MainUIWindow::OnCreate(HWND parent_window
, LPCREATESTRUCT
) {
242 // Create the listview that will the main app UI
243 list_view_
= ::CreateWindow(WC_LISTVIEW
, // Class name
245 WS_CHILD
| WS_VISIBLE
| LVS_REPORT
|
246 LVS_NOCOLUMNHEADER
| WS_BORDER
,
251 parent_window
, // parent
253 ::GetModuleHandle(NULL
),
260 LVCOLUMN list_view_column
= {0};
261 list_view_column
.mask
= LVCF_FMT
| LVCF_WIDTH
;
262 list_view_column
.fmt
= LVCFMT_LEFT
;
263 list_view_column
.cx
= 10000; // Maximum size of an entry in the list view.
264 ListView_InsertColumn(list_view_
, 0, &list_view_column
);
266 // Set list view to show green font on black background
267 ListView_SetBkColor(list_view_
, CLR_NONE
);
268 ListView_SetTextColor(list_view_
, RGB(0x0, 0x0, 0x0));
269 ListView_SetTextBkColor(list_view_
, CLR_NONE
);
274 void MainUIWindow::OnDestroy(HWND window
) {
275 UNREFERENCED_PARAMETER(window
);
277 // Post a quit message because our application is over when the
278 // user closes this window.
279 ::PostQuitMessage(0);
282 void MainUIWindow::OnSize(HWND window
, UINT state
, int cx
, int cy
) {
283 UNREFERENCED_PARAMETER(window
);
284 UNREFERENCED_PARAMETER(state
);
286 // If we have a valid inner child, resize it to cover the entire
287 // client area of the main UI window.
289 ::MoveWindow(list_view_
,
298 void MainUIWindow::OnPaint(HWND window
) {
299 PAINTSTRUCT paintstruct
;
300 ::BeginPaint(window
, &paintstruct
);
301 // add painting code here if required
302 ::EndPaint(window
, &paintstruct
);
305 void MainUIWindow::OnFileExit() {
306 ::PostQuitMessage(0);
309 void MainUIWindow::OnCommandsLaunch(HWND window
) {
310 // User wants to see the Select DLL dialog box
311 ::DialogBox(instance_handle_
,
312 MAKEINTRESOURCE(IDD_LAUNCH_DLL
),
317 bool MainUIWindow::OnLaunchDll(HWND dialog
) {
318 HWND edit_box_dll_name
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
319 HWND edit_box_entry_point
= ::GetDlgItem(dialog
, IDC_ENTRY_POINT
);
320 HWND edit_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
322 wchar_t dll_path
[MAX_PATH
];
323 wchar_t entry_point
[MAX_PATH
];
324 wchar_t log_file
[MAX_PATH
];
326 int dll_name_len
= ::GetWindowText(edit_box_dll_name
, dll_path
, MAX_PATH
);
327 int entry_point_len
= ::GetWindowText(edit_box_entry_point
,
328 entry_point
, MAX_PATH
);
329 // Log file is optional (can be blank)
330 ::GetWindowText(edit_log_file
, log_file
, MAX_PATH
);
332 if (0 >= dll_name_len
) {
334 L
"Please specify a DLL for the target to load",
340 if (GetFileAttributes(dll_path
) == INVALID_FILE_ATTRIBUTES
) {
342 L
"DLL specified was not found",
348 if (0 >= entry_point_len
) {
350 L
"Please specify an entry point for the DLL",
351 L
"No entry point specified",
356 // store these values in the member variables for use in SpawnTarget
357 log_file_
= base::string16(L
"\"") + log_file
+ base::string16(L
"\"");
358 dll_path_
= dll_path
;
359 entry_point_
= entry_point
;
364 DWORD WINAPI
MainUIWindow::ListenPipeThunk(void *param
) {
365 return reinterpret_cast<MainUIWindow
*>(param
)->ListenPipe();
368 DWORD WINAPI
MainUIWindow::WaitForTargetThunk(void *param
) {
369 return reinterpret_cast<MainUIWindow
*>(param
)->WaitForTarget();
372 // Thread waiting for the target application to die. It displays
373 // a message in the list view when it happens.
374 DWORD
MainUIWindow::WaitForTarget() {
375 WaitForSingleObject(target_
.hProcess
, INFINITE
);
378 if (!GetExitCodeProcess(target_
.hProcess
, &exit_code
)) {
379 exit_code
= 0xFFFF; // Default exit code
382 ::CloseHandle(target_
.hProcess
);
383 ::CloseHandle(target_
.hThread
);
385 AddDebugMessage(L
"Targed exited with return code %d", exit_code
);
389 // Thread waiting for messages on the log pipe. It displays the messages
391 DWORD
MainUIWindow::ListenPipe() {
392 HANDLE logfile_handle
= NULL
;
393 ATL::CString file_to_open
= log_file_
.c_str();
394 file_to_open
.Remove(L
'\"');
395 if (file_to_open
.GetLength()) {
396 logfile_handle
= ::CreateFile(file_to_open
.GetBuffer(),
398 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
399 NULL
, // Default security attributes
401 FILE_ATTRIBUTE_NORMAL
,
402 NULL
); // No template
403 if (INVALID_HANDLE_VALUE
== logfile_handle
) {
404 AddDebugMessage(L
"Failed to open \"%ls\" for logging. Error %d",
405 file_to_open
.GetBuffer(), ::GetLastError());
406 logfile_handle
= NULL
;
410 const int kSizeBuffer
= 1024;
411 BYTE read_buffer
[kSizeBuffer
] = {0};
412 ATL::CStringA read_buffer_global
;
413 ATL::CStringA string_to_print
;
415 DWORD last_error
= 0;
416 while(last_error
== ERROR_SUCCESS
|| last_error
== ERROR_PIPE_LISTENING
||
417 last_error
== ERROR_NO_DATA
)
419 DWORD read_data_length
;
420 if (::ReadFile(pipe_handle_
,
422 kSizeBuffer
- 1, // Max read size
424 NULL
)) { // Not overlapped
425 if (logfile_handle
) {
426 DWORD write_data_length
;
427 ::WriteFile(logfile_handle
,
431 FALSE
); // Not overlapped
434 // Append the new buffer to the current buffer
435 read_buffer
[read_data_length
] = NULL
;
436 read_buffer_global
+= reinterpret_cast<char *>(read_buffer
);
437 read_buffer_global
.Remove(10); // Remove the CRs
439 // If we completed a new line, output it
440 int endline
= read_buffer_global
.Find(13); // search for LF
441 while (-1 != endline
) {
442 string_to_print
= read_buffer_global
;
443 string_to_print
.Delete(endline
, string_to_print
.GetLength());
444 read_buffer_global
.Delete(0, endline
);
446 // print the line (with the ending LF)
447 OutputDebugStringA(string_to_print
.GetBuffer());
449 // Remove the ending LF
450 read_buffer_global
.Delete(0, 1);
452 // Add the line to the log
453 AddDebugMessage(L
"%S", string_to_print
.GetBuffer());
455 endline
= read_buffer_global
.Find(13);
457 last_error
= ERROR_SUCCESS
;
459 last_error
= GetLastError();
464 if (read_buffer_global
.GetLength()) {
465 AddDebugMessage(L
"%S", read_buffer_global
.GetBuffer());
468 CloseHandle(pipe_handle_
);
470 if (logfile_handle
) {
471 CloseHandle(logfile_handle
);
477 bool MainUIWindow::SpawnTarget() {
478 // Generate the pipe name
480 CoCreateGuid(&random_id
);
482 wchar_t log_pipe
[MAX_PATH
] = {0};
483 wnsprintf(log_pipe
, MAX_PATH
- 1,
484 L
"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
490 // We concatenate the four strings, add three spaces and a zero termination
491 // We use the resulting string as a param to CreateProcess (in SpawnTarget)
492 // Documented maximum for command line in CreateProcess is 32K (msdn)
493 size_t size_call
= spawn_target_
.length() + entry_point_
.length() +
494 dll_path_
.length() + wcslen(log_pipe
) + 6;
495 if (32 * 1024 < (size_call
* sizeof(wchar_t))) {
496 AddDebugMessage(L
"The length of the arguments exceeded 32K. "
497 L
"Aborting operation.");
501 wchar_t * arguments
= new wchar_t[size_call
];
502 wnsprintf(arguments
, static_cast<int>(size_call
), L
"%ls %ls \"%ls\" %ls",
503 spawn_target_
.c_str(), entry_point_
.c_str(),
504 dll_path_
.c_str(), log_pipe
);
506 arguments
[size_call
- 1] = L
'\0';
508 sandbox::TargetPolicy
* policy
= broker_
->CreatePolicy();
509 policy
->SetJobLevel(sandbox::JOB_LOCKDOWN
, 0);
510 policy
->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS
,
511 sandbox::USER_LOCKDOWN
);
512 policy
->SetAlternateDesktop(true);
513 policy
->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW
);
515 // Set the rule to allow the POC dll to be loaded by the target. Note that
516 // the rule allows 'all access' to the DLL, which could mean that the target
517 // could modify the DLL on disk.
518 policy
->AddRule(sandbox::TargetPolicy::SUBSYS_FILES
,
519 sandbox::TargetPolicy::FILES_ALLOW_ANY
, dll_path_
.c_str());
521 sandbox::ResultCode result
= broker_
->SpawnTarget(spawn_target_
.c_str(),
528 bool return_value
= false;
529 if (sandbox::SBOX_ALL_OK
!= result
) {
531 L
"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
532 spawn_target_
.c_str(), arguments
, result
);
533 return_value
= false;
537 ::CreateThread(NULL
, // Default security attributes
538 NULL
, // Default stack size
539 &MainUIWindow::WaitForTargetThunk
,
544 pipe_handle_
= ::CreateNamedPipe(log_pipe
,
545 PIPE_ACCESS_INBOUND
| WRITE_DAC
,
546 PIPE_TYPE_MESSAGE
| PIPE_NOWAIT
,
547 1, // Number of instances.
548 512, // Out buffer size.
549 512, // In buffer size.
550 NMPWAIT_USE_DEFAULT_WAIT
,
551 NULL
); // Default security descriptor
553 if (INVALID_HANDLE_VALUE
== pipe_handle_
)
554 AddDebugMessage(L
"Failed to create pipe. Error %d", ::GetLastError());
556 if (!sandbox::AddKnownSidToObject(pipe_handle_
, SE_KERNEL_OBJECT
,
557 WinWorldSid
, GRANT_ACCESS
,
559 AddDebugMessage(L
"Failed to set security on pipe. Error %d",
562 ::CreateThread(NULL
, // Default security attributes
563 NULL
, // Default stack size
564 &MainUIWindow::ListenPipeThunk
,
569 ::ResumeThread(target_
.hThread
);
571 AddDebugMessage(L
"Successfully spawned target w/args (%ls)", arguments
);
579 base::string16
MainUIWindow::OnShowBrowseForDllDlg(HWND owner
) {
580 wchar_t filename
[MAX_PATH
];
581 wcscpy_s(filename
, MAX_PATH
, L
"");
583 OPENFILENAMEW file_info
= {0};
584 file_info
.lStructSize
= sizeof(file_info
);
585 file_info
.hwndOwner
= owner
;
586 file_info
.lpstrFile
= filename
;
587 file_info
.nMaxFile
= MAX_PATH
;
588 file_info
.lpstrFilter
= L
"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
590 file_info
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
;
592 if (GetOpenFileName(&file_info
)) {
593 return file_info
.lpstrFile
;
599 base::string16
MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner
) {
600 wchar_t filename
[MAX_PATH
];
601 wcscpy_s(filename
, MAX_PATH
, L
"");
603 OPENFILENAMEW file_info
= {0};
604 file_info
.lStructSize
= sizeof(file_info
);
605 file_info
.hwndOwner
= owner
;
606 file_info
.lpstrFile
= filename
;
607 file_info
.nMaxFile
= MAX_PATH
;
608 file_info
.lpstrFilter
= L
"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
610 file_info
.Flags
= OFN_OVERWRITEPROMPT
| OFN_PATHMUSTEXIST
;
612 if (GetSaveFileName(&file_info
)) {
613 return file_info
.lpstrFile
;
619 void MainUIWindow::AddDebugMessage(const wchar_t* format
, ...) {
624 const int kMaxDebugBuffSize
= 1024;
627 _crt_va_start(arg_list
, format
);
629 wchar_t text
[kMaxDebugBuffSize
+ 1];
630 vswprintf_s(text
, kMaxDebugBuffSize
, format
, arg_list
);
631 text
[kMaxDebugBuffSize
] = L
'\0';
633 InsertLineInListView(text
);
637 void MainUIWindow::InsertLineInListView(wchar_t* debug_message
) {
638 DCHECK(debug_message
);
642 // Prepend the time to the message
643 const int kSizeTime
= 100;
644 size_t size_message_with_time
= wcslen(debug_message
) + kSizeTime
;
645 wchar_t * message_time
= new wchar_t[size_message_with_time
];
648 time_temp
= time(NULL
);
650 struct tm time
= {0};
651 localtime_s(&time
, &time_temp
);
654 return_code
= wcsftime(message_time
, kSizeTime
, L
"[%H:%M:%S] ", &time
);
656 wcscat_s(message_time
, size_message_with_time
, debug_message
);
658 // We add the debug message to the top of the listview
660 item
.iItem
= ListView_GetItemCount(list_view_
);
662 item
.mask
= LVIF_TEXT
| LVIF_PARAM
;
663 item
.pszText
= message_time
;
666 ListView_InsertItem(list_view_
, &item
);
668 delete[] message_time
;