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.
12 #include <atlsecurity.h>
16 #include "sandbox/win/sandbox_poc/main_ui_window.h"
17 #include "base/logging.h"
18 #include "sandbox/win/sandbox_poc/resource.h"
19 #include "sandbox/win/src/acl.h"
20 #include "sandbox/win/src/sandbox.h"
21 #include "sandbox/win/src/win_utils.h"
23 HWND
MainUIWindow::list_view_
= NULL
;
25 const wchar_t MainUIWindow::kDefaultDll_
[] = L
"\\POCDLL.dll";
26 const wchar_t MainUIWindow::kDefaultEntryPoint_
[] = L
"Run";
27 const wchar_t MainUIWindow::kDefaultLogFile_
[] = L
"";
29 MainUIWindow::MainUIWindow()
32 instance_handle_(NULL
),
37 MainUIWindow::~MainUIWindow() {
40 unsigned int MainUIWindow::CreateMainWindowAndLoop(
42 wchar_t* command_line
,
44 sandbox::BrokerServices
* broker
) {
49 instance_handle_
= instance
;
50 spawn_target_
= command_line
;
53 // We'll use spawn_target_ later for creating a child process, but
54 // CreateProcess doesn't like double quotes, so we remove them along with
55 // tabs and spaces from the start and end of the string
56 const wchar_t *trim_removal
= L
" \r\t\"";
57 spawn_target_
.erase(0, spawn_target_
.find_first_not_of(trim_removal
));
58 spawn_target_
.erase(spawn_target_
.find_last_not_of(trim_removal
) + 1);
60 WNDCLASSEX window_class
= {0};
61 window_class
.cbSize
= sizeof(WNDCLASSEX
);
62 window_class
.style
= CS_HREDRAW
| CS_VREDRAW
;
63 window_class
.lpfnWndProc
= MainUIWindow::WndProc
;
64 window_class
.cbClsExtra
= 0;
65 window_class
.cbWndExtra
= 0;
66 window_class
.hInstance
= instance
;
68 ::LoadIcon(instance
, MAKEINTRESOURCE(IDI_SANDBOX
));
69 window_class
.hCursor
= ::LoadCursor(NULL
, IDC_ARROW
);
70 window_class
.hbrBackground
= GetStockBrush(WHITE_BRUSH
);
71 window_class
.lpszMenuName
= MAKEINTRESOURCE(IDR_MENU_MAIN_UI
);
72 window_class
.lpszClassName
= L
"sandbox_ui_1";
73 window_class
.hIconSm
= NULL
;
75 INITCOMMONCONTROLSEX controls
= {
76 sizeof(INITCOMMONCONTROLSEX
),
77 ICC_STANDARD_CLASSES
| ICC_LISTVIEW_CLASSES
79 ::InitCommonControlsEx(&controls
);
81 if (!::RegisterClassEx(&window_class
))
82 return ::GetLastError();
84 // Create a main window of size 600x400
85 HWND window
= ::CreateWindowW(window_class
.lpszClassName
,
93 NULL
, // NULL = use class menu
98 return ::GetLastError();
100 ::SetWindowLongPtr(window
,
102 reinterpret_cast<LONG_PTR
>(this));
104 ::SetWindowText(window
, L
"Sandbox Proof of Concept");
106 ::ShowWindow(window
, show_command
);
109 // Now lets start the message pump retrieving messages for any window that
110 // belongs to the current thread
111 while (::GetMessage(&message
, NULL
, 0, 0)) {
112 ::TranslateMessage(&message
);
113 ::DispatchMessage(&message
);
119 LRESULT CALLBACK
MainUIWindow::WndProc(HWND window
,
123 MainUIWindow
* host
= FromWindow(window
);
125 #define HANDLE_MSG(hwnd, message, fn) \
126 case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
128 switch (message_id
) {
130 // 'host' is not yet available when we get the WM_CREATE message
131 return HANDLE_WM_CREATE(window
, wparam
, lparam
, OnCreate
);
133 return HANDLE_WM_DESTROY(window
, wparam
, lparam
, host
->OnDestroy
);
135 return HANDLE_WM_SIZE(window
, wparam
, lparam
, host
->OnSize
);
137 // Look at which menu item was clicked on (or which accelerator)
138 int id
= LOWORD(wparam
);
143 case ID_COMMANDS_SPAWNTARGET
:
144 host
->OnCommandsLaunch(window
);
147 // Some other menu item or accelerator
151 return ERROR_SUCCESS
;
155 // Some other WM_message, let it pass to DefWndProc
159 return DefWindowProc(window
, message_id
, wparam
, lparam
);
162 INT_PTR CALLBACK
MainUIWindow::SpawnTargetWndProc(HWND dialog
,
166 UNREFERENCED_PARAMETER(lparam
);
168 // Grab a reference to the main UI window (from the window handle)
169 MainUIWindow
* host
= FromWindow(GetParent(dialog
));
172 switch (message_id
) {
173 case WM_INITDIALOG
: {
174 // Initialize the window text for DLL name edit box
175 HWND edit_box_dll_name
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
176 wchar_t current_dir
[MAX_PATH
];
177 if (GetCurrentDirectory(MAX_PATH
, current_dir
)) {
178 base::string16 dll_path
= base::string16(current_dir
) +
179 base::string16(kDefaultDll_
);
180 ::SetWindowText(edit_box_dll_name
, dll_path
.c_str());
183 // Initialize the window text for Entry Point edit box
184 HWND edit_box_entry_point
= ::GetDlgItem(dialog
, IDC_ENTRY_POINT
);
185 ::SetWindowText(edit_box_entry_point
, kDefaultEntryPoint_
);
187 // Initialize the window text for Log File edit box
188 HWND edit_box_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
189 ::SetWindowText(edit_box_log_file
, kDefaultLogFile_
);
191 return static_cast<INT_PTR
>(TRUE
);
194 // If the user presses the OK button (Launch)
195 if (LOWORD(wparam
) == IDOK
) {
196 if (host
->OnLaunchDll(dialog
)) {
197 if (host
->SpawnTarget()) {
198 ::EndDialog(dialog
, LOWORD(wparam
));
201 return static_cast<INT_PTR
>(TRUE
);
202 } else if (LOWORD(wparam
) == IDCANCEL
) {
203 // If the user presses the Cancel button
204 ::EndDialog(dialog
, LOWORD(wparam
));
205 return static_cast<INT_PTR
>(TRUE
);
206 } else if (LOWORD(wparam
) == IDC_BROWSE_DLL
) {
207 // If the user presses the Browse button to look for a DLL
208 base::string16 dll_path
= host
->OnShowBrowseForDllDlg(dialog
);
209 if (dll_path
.length() > 0) {
210 // Initialize the window text for Log File edit box
211 HWND edit_box_dll_path
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
212 ::SetWindowText(edit_box_dll_path
, dll_path
.c_str());
214 return static_cast<INT_PTR
>(TRUE
);
215 } else if (LOWORD(wparam
) == IDC_BROWSE_LOG
) {
216 // If the user presses the Browse button to look for a log file
217 base::string16 log_path
= host
->OnShowBrowseForLogFileDlg(dialog
);
218 if (log_path
.length() > 0) {
219 // Initialize the window text for Log File edit box
220 HWND edit_box_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
221 ::SetWindowText(edit_box_log_file
, log_path
.c_str());
223 return static_cast<INT_PTR
>(TRUE
);
229 return static_cast<INT_PTR
>(FALSE
);
232 MainUIWindow
* MainUIWindow::FromWindow(HWND main_window
) {
233 // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
234 // so that we can retrieve it with this function later. This prevents us
235 // from having to define all the message handling functions (that we refer to
236 // in the window proc) as static
237 ::GetWindowLongPtr(main_window
, GWLP_USERDATA
);
238 return reinterpret_cast<MainUIWindow
*>(
239 ::GetWindowLongPtr(main_window
, GWLP_USERDATA
));
242 BOOL
MainUIWindow::OnCreate(HWND parent_window
, LPCREATESTRUCT
) {
243 // Create the listview that will the main app UI
244 list_view_
= ::CreateWindow(WC_LISTVIEW
, // Class name
246 WS_CHILD
| WS_VISIBLE
| LVS_REPORT
|
247 LVS_NOCOLUMNHEADER
| WS_BORDER
,
252 parent_window
, // parent
254 ::GetModuleHandle(NULL
),
261 LVCOLUMN list_view_column
= {0};
262 list_view_column
.mask
= LVCF_FMT
| LVCF_WIDTH
;
263 list_view_column
.fmt
= LVCFMT_LEFT
;
264 list_view_column
.cx
= 10000; // Maximum size of an entry in the list view.
265 ListView_InsertColumn(list_view_
, 0, &list_view_column
);
267 // Set list view to show green font on black background
268 ListView_SetBkColor(list_view_
, CLR_NONE
);
269 ListView_SetTextColor(list_view_
, RGB(0x0, 0x0, 0x0));
270 ListView_SetTextBkColor(list_view_
, CLR_NONE
);
275 void MainUIWindow::OnDestroy(HWND window
) {
276 UNREFERENCED_PARAMETER(window
);
278 // Post a quit message because our application is over when the
279 // user closes this window.
280 ::PostQuitMessage(0);
283 void MainUIWindow::OnSize(HWND window
, UINT state
, int cx
, int cy
) {
284 UNREFERENCED_PARAMETER(window
);
285 UNREFERENCED_PARAMETER(state
);
287 // If we have a valid inner child, resize it to cover the entire
288 // client area of the main UI window.
290 ::MoveWindow(list_view_
,
299 void MainUIWindow::OnPaint(HWND window
) {
300 PAINTSTRUCT paintstruct
;
301 ::BeginPaint(window
, &paintstruct
);
302 // add painting code here if required
303 ::EndPaint(window
, &paintstruct
);
306 void MainUIWindow::OnFileExit() {
307 ::PostQuitMessage(0);
310 void MainUIWindow::OnCommandsLaunch(HWND window
) {
311 // User wants to see the Select DLL dialog box
312 ::DialogBox(instance_handle_
,
313 MAKEINTRESOURCE(IDD_LAUNCH_DLL
),
318 bool MainUIWindow::OnLaunchDll(HWND dialog
) {
319 HWND edit_box_dll_name
= ::GetDlgItem(dialog
, IDC_DLL_NAME
);
320 HWND edit_box_entry_point
= ::GetDlgItem(dialog
, IDC_ENTRY_POINT
);
321 HWND edit_log_file
= ::GetDlgItem(dialog
, IDC_LOG_FILE
);
323 wchar_t dll_path
[MAX_PATH
];
324 wchar_t entry_point
[MAX_PATH
];
325 wchar_t log_file
[MAX_PATH
];
327 int dll_name_len
= ::GetWindowText(edit_box_dll_name
, dll_path
, MAX_PATH
);
328 int entry_point_len
= ::GetWindowText(edit_box_entry_point
,
329 entry_point
, MAX_PATH
);
330 // Log file is optional (can be blank)
331 ::GetWindowText(edit_log_file
, log_file
, MAX_PATH
);
333 if (0 >= dll_name_len
) {
335 L
"Please specify a DLL for the target to load",
341 if (GetFileAttributes(dll_path
) == INVALID_FILE_ATTRIBUTES
) {
343 L
"DLL specified was not found",
349 if (0 >= entry_point_len
) {
351 L
"Please specify an entry point for the DLL",
352 L
"No entry point specified",
357 // store these values in the member variables for use in SpawnTarget
358 log_file_
= base::string16(L
"\"") + log_file
+ base::string16(L
"\"");
359 dll_path_
= dll_path
;
360 entry_point_
= entry_point
;
365 DWORD WINAPI
MainUIWindow::ListenPipeThunk(void *param
) {
366 return reinterpret_cast<MainUIWindow
*>(param
)->ListenPipe();
369 DWORD WINAPI
MainUIWindow::WaitForTargetThunk(void *param
) {
370 return reinterpret_cast<MainUIWindow
*>(param
)->WaitForTarget();
373 // Thread waiting for the target application to die. It displays
374 // a message in the list view when it happens.
375 DWORD
MainUIWindow::WaitForTarget() {
376 WaitForSingleObject(target_
.hProcess
, INFINITE
);
379 if (!GetExitCodeProcess(target_
.hProcess
, &exit_code
)) {
380 exit_code
= 0xFFFF; // Default exit code
383 ::CloseHandle(target_
.hProcess
);
384 ::CloseHandle(target_
.hThread
);
386 AddDebugMessage(L
"Targed exited with return code %d", exit_code
);
390 // Thread waiting for messages on the log pipe. It displays the messages
392 DWORD
MainUIWindow::ListenPipe() {
393 HANDLE logfile_handle
= NULL
;
394 ATL::CString file_to_open
= log_file_
.c_str();
395 file_to_open
.Remove(L
'\"');
396 if (file_to_open
.GetLength()) {
397 logfile_handle
= ::CreateFile(file_to_open
.GetBuffer(),
399 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
400 NULL
, // Default security attributes
402 FILE_ATTRIBUTE_NORMAL
,
403 NULL
); // No template
404 if (INVALID_HANDLE_VALUE
== logfile_handle
) {
405 AddDebugMessage(L
"Failed to open \"%ls\" for logging. Error %d",
406 file_to_open
.GetBuffer(), ::GetLastError());
407 logfile_handle
= NULL
;
411 const int kSizeBuffer
= 1024;
412 BYTE read_buffer
[kSizeBuffer
] = {0};
413 ATL::CStringA read_buffer_global
;
414 ATL::CStringA string_to_print
;
416 DWORD last_error
= 0;
417 while(last_error
== ERROR_SUCCESS
|| last_error
== ERROR_PIPE_LISTENING
||
418 last_error
== ERROR_NO_DATA
)
420 DWORD read_data_length
;
421 if (::ReadFile(pipe_handle_
,
423 kSizeBuffer
- 1, // Max read size
425 NULL
)) { // Not overlapped
426 if (logfile_handle
) {
427 DWORD write_data_length
;
428 ::WriteFile(logfile_handle
,
432 FALSE
); // Not overlapped
435 // Append the new buffer to the current buffer
436 read_buffer
[read_data_length
] = NULL
;
437 read_buffer_global
+= reinterpret_cast<char *>(read_buffer
);
438 read_buffer_global
.Remove(10); // Remove the CRs
440 // If we completed a new line, output it
441 int endline
= read_buffer_global
.Find(13); // search for LF
442 while (-1 != endline
) {
443 string_to_print
= read_buffer_global
;
444 string_to_print
.Delete(endline
, string_to_print
.GetLength());
445 read_buffer_global
.Delete(0, endline
);
447 // print the line (with the ending LF)
448 OutputDebugStringA(string_to_print
.GetBuffer());
450 // Remove the ending LF
451 read_buffer_global
.Delete(0, 1);
453 // Add the line to the log
454 AddDebugMessage(L
"%S", string_to_print
.GetBuffer());
456 endline
= read_buffer_global
.Find(13);
458 last_error
= ERROR_SUCCESS
;
460 last_error
= GetLastError();
465 if (read_buffer_global
.GetLength()) {
466 AddDebugMessage(L
"%S", read_buffer_global
.GetBuffer());
469 CloseHandle(pipe_handle_
);
471 if (logfile_handle
) {
472 CloseHandle(logfile_handle
);
478 bool MainUIWindow::SpawnTarget() {
479 // Generate the pipe name
481 CoCreateGuid(&random_id
);
483 wchar_t log_pipe
[MAX_PATH
] = {0};
484 wnsprintf(log_pipe
, MAX_PATH
- 1,
485 L
"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
491 // We concatenate the four strings, add three spaces and a zero termination
492 // We use the resulting string as a param to CreateProcess (in SpawnTarget)
493 // Documented maximum for command line in CreateProcess is 32K (msdn)
494 size_t size_call
= spawn_target_
.length() + entry_point_
.length() +
495 dll_path_
.length() + wcslen(log_pipe
) + 6;
496 if (32 * 1024 < (size_call
* sizeof(wchar_t))) {
497 AddDebugMessage(L
"The length of the arguments exceeded 32K. "
498 L
"Aborting operation.");
502 wchar_t * arguments
= new wchar_t[size_call
];
503 wnsprintf(arguments
, static_cast<int>(size_call
), L
"%ls %ls \"%ls\" %ls",
504 spawn_target_
.c_str(), entry_point_
.c_str(),
505 dll_path_
.c_str(), log_pipe
);
507 arguments
[size_call
- 1] = L
'\0';
509 sandbox::TargetPolicy
* policy
= broker_
->CreatePolicy();
510 policy
->SetJobLevel(sandbox::JOB_LOCKDOWN
, 0);
511 policy
->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS
,
512 sandbox::USER_LOCKDOWN
);
513 policy
->SetAlternateDesktop(true);
514 policy
->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW
);
516 // Set the rule to allow the POC dll to be loaded by the target. Note that
517 // the rule allows 'all access' to the DLL, which could mean that the target
518 // could modify the DLL on disk.
519 policy
->AddRule(sandbox::TargetPolicy::SUBSYS_FILES
,
520 sandbox::TargetPolicy::FILES_ALLOW_ANY
, dll_path_
.c_str());
522 sandbox::ResultCode result
= broker_
->SpawnTarget(spawn_target_
.c_str(),
529 bool return_value
= false;
530 if (sandbox::SBOX_ALL_OK
!= result
) {
532 L
"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
533 spawn_target_
.c_str(), arguments
, result
);
534 return_value
= false;
538 ::CreateThread(NULL
, // Default security attributes
539 NULL
, // Default stack size
540 &MainUIWindow::WaitForTargetThunk
,
545 pipe_handle_
= ::CreateNamedPipe(log_pipe
,
546 PIPE_ACCESS_INBOUND
| WRITE_DAC
,
547 PIPE_TYPE_MESSAGE
| PIPE_NOWAIT
,
548 1, // Number of instances.
549 512, // Out buffer size.
550 512, // In buffer size.
551 NMPWAIT_USE_DEFAULT_WAIT
,
552 NULL
); // Default security descriptor
554 if (INVALID_HANDLE_VALUE
== pipe_handle_
)
555 AddDebugMessage(L
"Failed to create pipe. Error %d", ::GetLastError());
557 if (!sandbox::AddKnownSidToObject(pipe_handle_
, SE_KERNEL_OBJECT
,
558 WinWorldSid
, GRANT_ACCESS
,
560 AddDebugMessage(L
"Failed to set security on pipe. Error %d",
563 ::CreateThread(NULL
, // Default security attributes
564 NULL
, // Default stack size
565 &MainUIWindow::ListenPipeThunk
,
570 ::ResumeThread(target_
.hThread
);
572 AddDebugMessage(L
"Successfully spawned target w/args (%ls)", arguments
);
580 base::string16
MainUIWindow::OnShowBrowseForDllDlg(HWND owner
) {
581 wchar_t filename
[MAX_PATH
];
582 wcscpy_s(filename
, MAX_PATH
, L
"");
584 OPENFILENAMEW file_info
= {0};
585 file_info
.lStructSize
= sizeof(file_info
);
586 file_info
.hwndOwner
= owner
;
587 file_info
.lpstrFile
= filename
;
588 file_info
.nMaxFile
= MAX_PATH
;
589 file_info
.lpstrFilter
= L
"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
591 file_info
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
;
593 if (GetOpenFileName(&file_info
)) {
594 return file_info
.lpstrFile
;
600 base::string16
MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner
) {
601 wchar_t filename
[MAX_PATH
];
602 wcscpy_s(filename
, MAX_PATH
, L
"");
604 OPENFILENAMEW file_info
= {0};
605 file_info
.lStructSize
= sizeof(file_info
);
606 file_info
.hwndOwner
= owner
;
607 file_info
.lpstrFile
= filename
;
608 file_info
.nMaxFile
= MAX_PATH
;
609 file_info
.lpstrFilter
= L
"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
611 file_info
.Flags
= OFN_OVERWRITEPROMPT
| OFN_PATHMUSTEXIST
;
613 if (GetSaveFileName(&file_info
)) {
614 return file_info
.lpstrFile
;
620 void MainUIWindow::AddDebugMessage(const wchar_t* format
, ...) {
625 const int kMaxDebugBuffSize
= 1024;
628 va_start(arg_list
, format
);
630 wchar_t text
[kMaxDebugBuffSize
+ 1];
631 vswprintf_s(text
, kMaxDebugBuffSize
, format
, arg_list
);
632 text
[kMaxDebugBuffSize
] = L
'\0';
634 InsertLineInListView(text
);
638 void MainUIWindow::InsertLineInListView(wchar_t* debug_message
) {
639 DCHECK(debug_message
);
643 // Prepend the time to the message
644 const int kSizeTime
= 100;
645 size_t size_message_with_time
= wcslen(debug_message
) + kSizeTime
;
646 wchar_t * message_time
= new wchar_t[size_message_with_time
];
649 time_temp
= time(NULL
);
651 struct tm time
= {0};
652 localtime_s(&time
, &time_temp
);
655 return_code
= wcsftime(message_time
, kSizeTime
, L
"[%H:%M:%S] ", &time
);
657 wcscat_s(message_time
, size_message_with_time
, debug_message
);
659 // We add the debug message to the top of the listview
661 item
.iItem
= ListView_GetItemCount(list_view_
);
663 item
.mask
= LVIF_TEXT
| LVIF_PARAM
;
664 item
.pszText
= message_time
;
667 ListView_InsertItem(list_view_
, &item
);
669 delete[] message_time
;