Home | History | Annotate | Download | only in sandbox_poc
      1 // Copyright (c) 2006-2010 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.
      4 
      5 #include <windows.h>
      6 #include <CommCtrl.h>
      7 #include <commdlg.h>
      8 #include <time.h>
      9 #include <windowsx.h>
     10 #include <atlbase.h>
     11 #include <atlsecurity.h>
     12 #include <algorithm>
     13 #include <sstream>
     14 
     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"
     21 
     22 HWND MainUIWindow::list_view_ = NULL;
     23 
     24 const wchar_t MainUIWindow::kDefaultDll_[]        = L"\\POCDLL.dll";
     25 const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
     26 const wchar_t MainUIWindow::kDefaultLogFile_[]    = L"";
     27 
     28 MainUIWindow::MainUIWindow()
     29     : instance_handle_(NULL),
     30       spawn_target_(L""),
     31       dll_path_(L""),
     32       entry_point_(L""),
     33       broker_(NULL) {
     34 }
     35 
     36 MainUIWindow::~MainUIWindow() {
     37 }
     38 
     39 unsigned int MainUIWindow::CreateMainWindowAndLoop(
     40     HINSTANCE instance,
     41     wchar_t* command_line,
     42     int show_command,
     43     sandbox::BrokerServices* broker) {
     44   DCHECK(instance);
     45   DCHECK(command_line);
     46   DCHECK(broker);
     47 
     48   instance_handle_ = instance;
     49   spawn_target_ = command_line;
     50   broker_ = broker;
     51 
     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);
     58 
     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;
     66   window_class.hIcon         =
     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;
     73 
     74   INITCOMMONCONTROLSEX controls = {
     75     sizeof(INITCOMMONCONTROLSEX),
     76     ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES
     77   };
     78   ::InitCommonControlsEx(&controls);
     79 
     80   if (!::RegisterClassEx(&window_class))
     81     return ::GetLastError();
     82 
     83   // Create a main window of size 600x400
     84   HWND window = ::CreateWindowW(window_class.lpszClassName,
     85                                 L"",            // window name
     86                                 WS_OVERLAPPEDWINDOW,
     87                                 CW_USEDEFAULT,  // x
     88                                 CW_USEDEFAULT,  // y
     89                                 600,            // width
     90                                 400,            // height
     91                                 NULL,           // parent
     92                                 NULL,           // NULL = use class menu
     93                                 instance,
     94                                 0);             // lpParam
     95 
     96   if (NULL == window)
     97     return ::GetLastError();
     98 
     99   ::SetWindowLongPtr(window,
    100                      GWLP_USERDATA,
    101                      reinterpret_cast<LONG_PTR>(this));
    102 
    103   ::SetWindowText(window, L"Sandbox Proof of Concept");
    104 
    105   ::ShowWindow(window, show_command);
    106 
    107   MSG message;
    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);
    113   }
    114 
    115   return 0;
    116 }
    117 
    118 LRESULT CALLBACK MainUIWindow::WndProc(HWND window,
    119                                        UINT message_id,
    120                                        WPARAM wparam,
    121                                        LPARAM lparam) {
    122   MainUIWindow* host = FromWindow(window);
    123 
    124   #define HANDLE_MSG(hwnd, message, fn)    \
    125     case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
    126 
    127   switch (message_id) {
    128     case WM_CREATE:
    129       // 'host' is not yet available when we get the WM_CREATE message
    130       return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate);
    131     case WM_DESTROY:
    132       return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy);
    133     case WM_SIZE:
    134       return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize);
    135     case WM_COMMAND: {
    136       // Look at which menu item was clicked on (or which accelerator)
    137       int id = LOWORD(wparam);
    138       switch (id) {
    139         case ID_FILE_EXIT:
    140           host->OnFileExit();
    141           break;
    142         case ID_COMMANDS_SPAWNTARGET:
    143           host->OnCommandsLaunch(window);
    144           break;
    145         default:
    146           // Some other menu item or accelerator
    147           break;
    148       }
    149 
    150       return ERROR_SUCCESS;
    151     }
    152 
    153     default:
    154       // Some other WM_message, let it pass to DefWndProc
    155       break;
    156   }
    157 
    158   return DefWindowProc(window, message_id, wparam, lparam);
    159 }
    160 
    161 INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog,
    162                                                   UINT message_id,
    163                                                   WPARAM wparam,
    164                                                   LPARAM lparam) {
    165   UNREFERENCED_PARAMETER(lparam);
    166 
    167   // Grab a reference to the main UI window (from the window handle)
    168   MainUIWindow* host = FromWindow(GetParent(dialog));
    169   DCHECK(host);
    170 
    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         std::wstring dll_path = std::wstring(current_dir) +
    178                                 std::wstring(kDefaultDll_);
    179         ::SetWindowText(edit_box_dll_name, dll_path.c_str());
    180       }
    181 
    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_);
    185 
    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_);
    189 
    190       return static_cast<INT_PTR>(TRUE);
    191     }
    192     case WM_COMMAND:
    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));
    198           }
    199         }
    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         std::wstring 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());
    212         }
    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         std::wstring 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());
    221         }
    222         return static_cast<INT_PTR>(TRUE);
    223       }
    224 
    225       break;
    226   }
    227 
    228   return static_cast<INT_PTR>(FALSE);
    229 }
    230 
    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));
    239 }
    240 
    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
    244                               L"",            // Window name
    245                               WS_CHILD | WS_VISIBLE | LVS_REPORT |
    246                               LVS_NOCOLUMNHEADER | WS_BORDER,
    247                               0,              // x
    248                               0,              // y
    249                               0,              // width
    250                               0,              // height
    251                               parent_window,  // parent
    252                               NULL,           // menu
    253                               ::GetModuleHandle(NULL),
    254                               0);             // lpParam
    255 
    256   DCHECK(list_view_);
    257   if (!list_view_)
    258     return FALSE;
    259 
    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);
    265 
    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);
    270 
    271   return TRUE;
    272 }
    273 
    274 void MainUIWindow::OnDestroy(HWND window) {
    275   UNREFERENCED_PARAMETER(window);
    276 
    277   // Post a quit message because our application is over when the
    278   // user closes this window.
    279   ::PostQuitMessage(0);
    280 }
    281 
    282 void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) {
    283   UNREFERENCED_PARAMETER(window);
    284   UNREFERENCED_PARAMETER(state);
    285 
    286   // If we have a valid inner child, resize it to cover the entire
    287   // client area of the main UI window.
    288   if (list_view_) {
    289     ::MoveWindow(list_view_,
    290                  0,      // x
    291                  0,      // y
    292                  cx,     // width
    293                  cy,     // height
    294                  TRUE);  // repaint
    295   }
    296 }
    297 
    298 void MainUIWindow::OnPaint(HWND window) {
    299   PAINTSTRUCT paintstruct;
    300   ::BeginPaint(window, &paintstruct);
    301   // add painting code here if required
    302   ::EndPaint(window, &paintstruct);
    303 }
    304 
    305 void MainUIWindow::OnFileExit() {
    306   ::PostQuitMessage(0);
    307 }
    308 
    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),
    313               window,
    314               SpawnTargetWndProc);
    315 }
    316 
    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);
    321 
    322   wchar_t dll_path[MAX_PATH];
    323   wchar_t entry_point[MAX_PATH];
    324   wchar_t log_file[MAX_PATH];
    325 
    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);
    331 
    332   if (0 >= dll_name_len) {
    333     ::MessageBox(dialog,
    334                  L"Please specify a DLL for the target to load",
    335                  L"No DLL specified",
    336                  MB_ICONERROR);
    337     return false;
    338   }
    339 
    340   if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) {
    341     ::MessageBox(dialog,
    342                  L"DLL specified was not found",
    343                  L"DLL not found",
    344                  MB_ICONERROR);
    345     return false;
    346   }
    347 
    348   if (0 >= entry_point_len) {
    349     ::MessageBox(dialog,
    350                  L"Please specify an entry point for the DLL",
    351                  L"No entry point specified",
    352                  MB_ICONERROR);
    353     return false;
    354   }
    355 
    356   // store these values in the member variables for use in SpawnTarget
    357   log_file_ = std::wstring(L"\"") + log_file + std::wstring(L"\"");
    358   dll_path_ = dll_path;
    359   entry_point_ = entry_point;
    360 
    361   return true;
    362 }
    363 
    364 DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
    365   return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
    366 }
    367 
    368 DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) {
    369   return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget();
    370 }
    371 
    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);
    376 
    377   DWORD exit_code = 0;
    378   if (!GetExitCodeProcess(target_.hProcess, &exit_code)) {
    379     exit_code = 0xFFFF;  // Default exit code
    380   }
    381 
    382   ::CloseHandle(target_.hProcess);
    383   ::CloseHandle(target_.hThread);
    384 
    385   AddDebugMessage(L"Targed exited with return code %d", exit_code);
    386   return 0;
    387 }
    388 
    389 // Thread waiting for messages on the log pipe. It displays the messages
    390 // in the listview.
    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(),
    397                                   GENERIC_WRITE,
    398                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
    399                                   NULL,  // Default security attributes
    400                                   CREATE_ALWAYS,
    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;
    407     }
    408   }
    409 
    410   const int kSizeBuffer = 1024;
    411   BYTE read_buffer[kSizeBuffer] = {0};
    412   ATL::CStringA read_buffer_global;
    413   ATL::CStringA string_to_print;
    414 
    415   DWORD last_error = 0;
    416   while(last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING ||
    417         last_error == ERROR_NO_DATA)
    418   {
    419     DWORD read_data_length;
    420     if (::ReadFile(pipe_handle_,
    421                   read_buffer,
    422                   kSizeBuffer - 1,  // Max read size
    423                   &read_data_length,
    424                   NULL)) {  // Not overlapped
    425       if (logfile_handle) {
    426         DWORD write_data_length;
    427         ::WriteFile(logfile_handle,
    428                     read_buffer,
    429                     read_data_length,
    430                     &write_data_length,
    431                     FALSE);  // Not overlapped
    432       }
    433 
    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
    438 
    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);
    445 
    446         //  print the line (with the ending LF)
    447         OutputDebugStringA(string_to_print.GetBuffer());
    448 
    449         // Remove the ending LF
    450         read_buffer_global.Delete(0, 1);
    451 
    452         // Add the line to the log
    453         AddDebugMessage(L"%S", string_to_print.GetBuffer());
    454 
    455         endline = read_buffer_global.Find(13);
    456       }
    457       last_error = ERROR_SUCCESS;
    458     } else {
    459       last_error = GetLastError();
    460       Sleep(100);
    461     }
    462   }
    463 
    464   if (read_buffer_global.GetLength()) {
    465     AddDebugMessage(L"%S", read_buffer_global.GetBuffer());
    466   }
    467 
    468   CloseHandle(pipe_handle_);
    469 
    470   if (logfile_handle) {
    471     CloseHandle(logfile_handle);
    472   }
    473 
    474   return 0;
    475 }
    476 
    477 bool MainUIWindow::SpawnTarget() {
    478   // Generate the pipe name
    479   GUID random_id;
    480   CoCreateGuid(&random_id);
    481 
    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",
    485             random_id.Data1,
    486             random_id.Data2,
    487             random_id.Data3,
    488             random_id.Data4);
    489 
    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.");
    498     return false;
    499   }
    500 
    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);
    505 
    506   arguments[size_call - 1] = L'\0';
    507 
    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);
    514 
    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());
    520 
    521   sandbox::ResultCode result = broker_->SpawnTarget(spawn_target_.c_str(),
    522                                                     arguments, policy,
    523                                                     &target_);
    524 
    525   policy->Release();
    526   policy = NULL;
    527 
    528   bool return_value = false;
    529   if (sandbox::SBOX_ALL_OK != result) {
    530     AddDebugMessage(
    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;
    534   } else {
    535 
    536     DWORD thread_id;
    537     ::CreateThread(NULL,  // Default security attributes
    538                    NULL,  // Default stack size
    539                    &MainUIWindow::WaitForTargetThunk,
    540                    this,
    541                    0,  // No flags
    542                    &thread_id);
    543 
    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
    552 
    553     if (INVALID_HANDLE_VALUE == pipe_handle_)
    554       AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError());
    555 
    556     if (!sandbox::AddKnownSidToKernelObject(pipe_handle_, WinWorldSid,
    557                                             FILE_ALL_ACCESS))
    558       AddDebugMessage(L"Failed to set security on pipe. Error %d",
    559                       ::GetLastError());
    560 
    561     ::CreateThread(NULL,  // Default security attributes
    562                    NULL,  // Default stack size
    563                    &MainUIWindow::ListenPipeThunk,
    564                    this,
    565                    0,  // No flags
    566                    &thread_id);
    567 
    568     ::ResumeThread(target_.hThread);
    569 
    570     AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
    571     return_value = true;
    572   }
    573 
    574   delete[] arguments;
    575   return return_value;
    576 }
    577 
    578 std::wstring MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
    579   wchar_t filename[MAX_PATH];
    580   wcscpy_s(filename, MAX_PATH, L"");
    581 
    582   OPENFILENAMEW file_info = {0};
    583   file_info.lStructSize = sizeof(file_info);
    584   file_info.hwndOwner = owner;
    585   file_info.lpstrFile = filename;
    586   file_info.nMaxFile = MAX_PATH;
    587   file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
    588 
    589   file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
    590 
    591   if (GetOpenFileName(&file_info)) {
    592     return file_info.lpstrFile;
    593   }
    594 
    595   return L"";
    596 }
    597 
    598 std::wstring MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
    599   wchar_t filename[MAX_PATH];
    600   wcscpy_s(filename, MAX_PATH, L"");
    601 
    602   OPENFILENAMEW file_info = {0};
    603   file_info.lStructSize = sizeof(file_info);
    604   file_info.hwndOwner = owner;
    605   file_info.lpstrFile = filename;
    606   file_info.nMaxFile = MAX_PATH;
    607   file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
    608 
    609   file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
    610 
    611   if (GetSaveFileName(&file_info)) {
    612     return file_info.lpstrFile;
    613   }
    614 
    615   return L"";
    616 }
    617 
    618 void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
    619   DCHECK(format);
    620   if (!format)
    621     return;
    622 
    623   const int kMaxDebugBuffSize = 1024;
    624 
    625   va_list arg_list;
    626   _crt_va_start(arg_list, format);
    627 
    628   wchar_t text[kMaxDebugBuffSize + 1];
    629   vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
    630   text[kMaxDebugBuffSize] = L'\0';
    631 
    632   InsertLineInListView(text);
    633 }
    634 
    635 
    636 void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
    637   DCHECK(debug_message);
    638   if (!debug_message)
    639     return;
    640 
    641   // Prepend the time to the message
    642   const int kSizeTime = 100;
    643   size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
    644   wchar_t * message_time = new wchar_t[size_message_with_time];
    645 
    646   time_t time_temp;
    647   time_temp = time(NULL);
    648 
    649   struct tm time = {0};
    650   localtime_s(&time, &time_temp);
    651 
    652   size_t return_code;
    653   return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
    654 
    655   wcscat_s(message_time, size_message_with_time, debug_message);
    656 
    657   // We add the debug message to the top of the listview
    658   LVITEM item;
    659   item.iItem = ListView_GetItemCount(list_view_);
    660   item.iSubItem = 0;
    661   item.mask = LVIF_TEXT | LVIF_PARAM;
    662   item.pszText = message_time;
    663   item.lParam = 0;
    664 
    665   ListView_InsertItem(list_view_, &item);
    666 
    667   delete[] message_time;
    668 }
    669