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