diff --git a/pafish/Makefile.linux b/pafish/Makefile.linux index 406fa54..15702d9 100644 --- a/pafish/Makefile.linux +++ b/pafish/Makefile.linux @@ -5,7 +5,7 @@ WINDRES = i686-w64-mingw32-windres OBJ = Objects/MingW/main.o Objects/MingW/common.o Objects/MingW/utils.o Objects/MingW/debuggers.o Objects/MingW/sandboxie.o \ Objects/MingW/vbox.o Objects/MingW/gensandbox.o Objects/MingW/wine.o Objects/MingW/vmware.o \ Objects/MingW/qemu.o Objects/MingW/hooks.o Objects/MingW/cpu.o Objects/MingW/cuckoo.o Objects/MingW/bochs.o \ - Objects/MingW/pafish_private.res + Objects/MingW/rtt.o Objects/MingW/pafish_private.res LINKOBJ = $(OBJ) LIBS = -lwsock32 -liphlpapi -lsetupapi -lmpr -lole32 -lwbemuuid -loleaut32 -lws2_32 -s INCS = @@ -64,5 +64,8 @@ Objects/MingW/cuckoo.o: $(GLOBALDEPS) cuckoo.c Objects/MingW/bochs.o: $(GLOBALDEPS) bochs.c $(CC) -c bochs.c -o Objects/MingW/bochs.o $(CFLAGS) +Objects/MingW/rtt.o: $(GLOBALDEPS) rtt.c + $(CC) -c rtt.c -o Objects/MingW/rtt.o $(CFLAGS) + Objects/MingW/pafish_private.res: Objects/MingW/pafish_private.rc $(WINDRES) Objects/MingW/pafish_private.rc --input-format=rc -o Objects/MingW/pafish_private.res -O coff diff --git a/pafish/Makefile.win b/pafish/Makefile.win index d3074c7..70f4718 100644 --- a/pafish/Makefile.win +++ b/pafish/Makefile.win @@ -5,7 +5,7 @@ WINDRES = windres.exe OBJ = Objects/MingW/main.o Objects/MingW/common.o Objects/MingW/utils.o Objects/MingW/debuggers.o Objects/MingW/sandboxie.o \ Objects/MingW/vbox.o Objects/MingW/gensandbox.o Objects/MingW/wine.o Objects/MingW/vmware.o \ Objects/MingW/qemu.o Objects/MingW/hooks.o Objects/MingW/cpu.o Objects/MingW/cuckoo.o Objects/MingW/bochs.o \ - Objects/MingW/pafish_private.res + Objects/MingW/rtt.o Objects/MingW/pafish_private.res LINKOBJ = $(OBJ) LIBS = -lwsock32 -liphlpapi -lsetupapi -lmpr -lole32 -lwbemuuid -loleaut32 -lws2_32 -s INCS = @@ -64,5 +64,8 @@ Objects/MingW/cuckoo.o: $(GLOBALDEPS) cuckoo.c Objects/MingW/bochs.o: $(GLOBALDEPS) bochs.c $(CC) -c bochs.c -o Objects/MingW/bochs.o $(CFLAGS) +Objects/MingW/rtt.o: $(GLOBALDEPS) rtt.c + $(CC) -c rtt.c -o Objects/MingW/rtt.o $(CFLAGS) + Objects/MingW/pafish_private.res: Objects/MingW/pafish_private.rc $(WINDRES) Objects/MingW/pafish_private.rc --input-format=rc -o Objects/MingW/pafish_private.res -O coff diff --git a/pafish/gensandbox.c b/pafish/gensandbox.c index 4efee78..705db90 100644 --- a/pafish/gensandbox.c +++ b/pafish/gensandbox.c @@ -17,20 +17,6 @@ */ typedef BOOL (WINAPI * IsNativeVhdBoot) (BOOL *); -int gensandbox_mouse_act() { - POINT position1, position2; - GetCursorPos(&position1); - Sleep(2000); /* Sleep time */ - GetCursorPos(&position2); - if ((position1.x == position2.x) && (position1.y == position2.y)) { - /* No mouse activity during the sleep */ - return TRUE; - } - else { - /* Mouse activity during the sleep */ - return FALSE; - } -} int gensandbox_username() { char username[200]; diff --git a/pafish/gensandbox.h b/pafish/gensandbox.h index 2128f0d..1f4cba5 100644 --- a/pafish/gensandbox.h +++ b/pafish/gensandbox.h @@ -2,8 +2,6 @@ #ifndef GENSAND_H #define GENSAND_H -int gensandbox_mouse_act(); - int gensandbox_username(); int gensandbox_path(); diff --git a/pafish/main.c b/pafish/main.c index f84c222..f9e5ecd 100644 --- a/pafish/main.c +++ b/pafish/main.c @@ -18,7 +18,7 @@ #include "cpu.h" #include "cuckoo.h" #include "bochs.h" - +#include "rtt.h" /* Pafish (Paranoid fish) @@ -44,6 +44,9 @@ int main(void) OSVERSIONINFO winver; unsigned short original_colors = 0; + /* Minimize window at first to not interefere with human behaviour simulators */ + ShowWindow(GetConsoleWindow(), SW_MINIMIZE); + write_log("Start"); #if ENABLE_DNS_TRACE write_trace_dns("analysis-start"); @@ -112,11 +115,32 @@ int main(void) "CPU VM traced by checking cpuid hypervisor vendor for known VM vendors", "hi_CPU_VM_hv_vendor_name"); - /* Generic sandbox detection tricks */ + /* Generic reverse turing tests */ + print_check_group("Generic reverse turing tests"); + exec_check("Checking mouse presence", &rtt_mouse_presence, + "Sandbox traced by absence of mouse device", + "hi_sandbox_mouse_presence"); + exec_check("Checking mouse movement", &rtt_mouse_move, + "Sandbox traced by missing mouse movement", + "hi_sandbox_rtt_mouse_movement"); + exec_check("Checking mouse speed", &rtt_mouse_speed_limit, + "Sandbox traced by missing mouse movement or supernatural speed", + "hi_sandbox_rtt_mouse_speed_limit"); + exec_check("Checking mouse click activity", &rtt_mouse_click, + "Sandbox traced by missing mouse click activity", + "hi_sandbox_rtt_mouse_click"); + exec_check("Checking mouse double click activity", &rtt_mouse_double_click, + "Sandbox traced by missing double click activity", + "hi_sandbox_rtt_mouse_double_click"); + exec_check("Checking dialog confirmation", &rtt_confirm_dialog, + "Sandbox traced by missing dialog confirmation", + "hi_sandbox_rtt_confirm_dialog"); + exec_check("Checking plausible dialog confirmation", &rtt_plausible_confirm_dialog, + "Sandbox traced by missing or implausible dialog confirmation", + "hi_sandbox_rtt_implausible_confirm_dialog"); + + /* Generic sandbox detection tricks */ print_check_group("Generic sandbox detection"); - exec_check("Using mouse activity", &gensandbox_mouse_act, - "Sandbox traced using mouse activity", - "hi_sandbox_mouse_act"); exec_check("Checking username", &gensandbox_username, "Sandbox traced by checking username", "hi_sandbox_username"); diff --git a/pafish/rtt.c b/pafish/rtt.c new file mode 100644 index 0000000..fc29a31 --- /dev/null +++ b/pafish/rtt.c @@ -0,0 +1,375 @@ +#include +#include + +#include "types.h" +#include "rtt.h" + +/* Dialog's constants */ +#define ID_OK 1 +#define ID_QUIT 2 +#define TEXT_OFF 20 + +/* Custom message indicating timer's end */ +#define WM_CUSTOM (WM_USER + 0x0001) + +/* Duration of each check */ +#define MAX_DURATION 3000 + +/* + * Checks, for the presence of a mouse device. + * + * Found in similar form in maldoc used by ColdRiver + * MD5: 48320f502811645fa1f2f614bd8a385a + */ +int rtt_mouse_presence() { + int res; + res = GetSystemMetrics(SM_MOUSEPRESENT); + return res > 0 ? FALSE : TRUE; +} + +/* + * Checks, if the mouse cursor moves. + * + * Found in similar form in IFSB/Gozi in version 2.13.24.1 leaked by vx-undeground.org + * See: https://github.com/vxunderground/MalwareSourceCode/blob/main/Leaks/Win32/Win32.Gozi.rar + */ +int rtt_mouse_move() { + POINT cur = { 0 }, prev = { 0 }; + u_int movement = 0; + int interval = 100; + int period = MAX_DURATION; + + do { + GetCursorPos(&cur); + + if (prev.x && prev.y) + movement = abs(cur.x - prev.x) + (abs(cur.y - prev.y) << 16); + + /* Cursor moved */ + if (movement) { + return FALSE; + } + + prev = cur; + Sleep(interval); + period -= interval; + } while (period); + + return TRUE; +} + +/* + * Checks, for supernatural cursor speeds. + * + * Referenced in Fireeye Blogpost from 2014 + * https://www.fireeye.com/blog/threat-research/2014/06/turing-test-in-reverse-new-sandbox-evasion-techniques-seek-human-interaction.html + */ +int rtt_mouse_speed_limit() { + POINT prev, cur; + int mx, my, dx, dy; + + int period = MAX_DURATION; + int interval = 10; + int cursor_updates = 0; + + mx = GetSystemMetrics(SM_CXFULLSCREEN) / 5; + my = GetSystemMetrics(SM_CYFULLSCREEN) / 5; + + GetCursorPos(&prev); + + while (period) { + Sleep(interval); /* Sleep time */ + + GetCursorPos(&cur); + dx = prev.x - cur.x; + dy = prev.y - cur.y; + + if ((dx != 0) || (dy != 0)) { + /* Cursor moved */ + cursor_updates++; + + if (abs(dx) > mx && abs(dy) > my) { + /* Cursor moved too fast */ + return TRUE; + } + } + prev = cur; + period -= interval; + } + + /* Mouse actually moved, but never moved too fast, therefore pass check */ + if (cursor_updates) { + return FALSE; + } + + /* No mouse activity, fail check as well */ + return TRUE; +} + +/* + * Timer callback, which sends a custom message to the message loop, in order + * to signal that the time is up. + */ +VOID CALLBACK timer_proc() { + PostMessageA(NULL, WM_CUSTOM, 0, 0); +} + +HHOOK hook; +BOOL is_success = FALSE; + +/* + * Callback for a low-level mouse hook, which checks, if a single click occurs. + */ +LRESULT CALLBACK single_click_proc(int nCode, WPARAM wParam, LPARAM lp) { + if (nCode >= 0) { + if (wParam == WM_LBUTTONUP) { + is_success = TRUE; + PostMessageA(NULL, WM_CUSTOM, 0 , 0); + } + } + return CallNextHookEx(hook, nCode, wParam, lp); +} + +/* + * Calculates the current time in milliseconds by querying system time as + * FILETIME and converting it to Unix epoch format. + * + * Taken from https://gist.github.com/e-yes/278302 + */ +u_int64 get_current_time_in_millis(){ + FILETIME ft; + LARGE_INTEGER li; + + /* + * Gets the amount of 100 nano seconds intervals elapsed since January 1, + * 1601 (UTC) and copy it * to a LARGE_INTEGER structure. + */ + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + + u_int64 ms = li.QuadPart; + ms -= 116444736000000000LL; /* Converts from file time to UNIX epoch time. */ + ms /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ + + return ms; +} + +/* Tracks the point in time of last click */ +u_int64 last = 0; + +/* Default double click time in milliseconds */ +u_int double_click_time = 500; + +/* + * Callback for a low-level mouse hook, which checks, if a double click occurs. + * The presence of a double click is assumed if two clicks are observed within + * the time frame double_click_time. + */ +LRESULT CALLBACK double_click_proc(int code, WPARAM wp, LPARAM lp) { + if (code >= 0) { + if (wp == WM_LBUTTONDOWN) { + + u_int64 now = get_current_time_in_millis(); + if((now - last) < double_click_time){ + is_success = TRUE; + PostMessageA(NULL, WM_CUSTOM, 0 , 0); + } + last = now; + } + } + return CallNextHookEx(hook, code, wp, lp); +} + +/* + * Installs a low-level mouse hook with the specified callback. + */ +int install_hook(LRESULT CALLBACK (*callback)(int code, WPARAM wp, LPARAM lp)){ + SetTimer(NULL, 0, MAX_DURATION, (TIMERPROC) &timer_proc); + hook = SetWindowsHookEx(WH_MOUSE_LL, callback, NULL, 0); + MSG msg; + + while (GetMessage(&msg, NULL, 0, 0) > 0) { + if (msg.message == WM_CUSTOM) { + /* Timer ended, exit message loop*/ + break; + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* Clean up */ + KillTimer(NULL, 0); + UnhookWindowsHookEx(hook); + + if (is_success) + return FALSE; + + return TRUE; +} +/* + * Checks for a single click with a technique used in the UpClicker trojan. + * See https://webcache.googleusercontent.com/search?q=cache:NeVZ4J1Y-cQJ:https://www.fireeye.com/blog/threat-research/2012/12/dont-click-the-left-mouse-button-trojan-upclicker.html+&cd=1&hl=en&ct=clnk&gl=de + */ +int rtt_mouse_click() { + return install_hook(&single_click_proc); +} + +/* + * Checks for dobule clicks. + * + * In the past several malware samples waited for a certain amount of clicks + * (not necessarily double clicks) with different techniques, see for example: + * + * https://www.ptsecurity.com/ww-en/analytics/antisandbox-techniques/ mentioning + * MyWeb backdoor used by APT15 in 2010, where three left clicks were required + * + * https://www.fireeye.com/blog/threat-research/2014/06/turing-test-in-reverse-new-sandbox-evasion-techniques-seek-human-interaction.html + * referencing the use of GetAsyncKeyState() + */ +int rtt_mouse_double_click() { + /* Determines double click time set on system */ + double_click_time = GetDoubleClickTime(); + /* Checks, if a double click occurs */ + return install_hook(&double_click_proc); +} + +BOOL is_timeout = FALSE; +BOOL is_within_rect = FALSE; + +LRESULT CALLBACK timed_dialog_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + + RECT rect; + int btn_w, btn_h; + + switch (msg) { + case WM_CREATE: + SetTimer(hwnd, 1, MAX_DURATION, NULL); + + /* Determines "usable" size inside of the current window */ + GetClientRect(hwnd, &rect); + + /* Calculates button size */ + btn_w = (rect.right - rect.left) / 2; + btn_h = (rect.bottom - rect.top) / 2; + + /* Create two buttons - randomly chosen and "Quit" */ + CreateWindowW(L"Button", L"OK", WS_VISIBLE | WS_CHILD, 0, TEXT_OFF, + btn_w, btn_h, hwnd, (HMENU) ID_OK, NULL, NULL); + + CreateWindowW(L"Button", L"Quit", WS_VISIBLE | WS_CHILD, 0 + btn_w, + TEXT_OFF, btn_w, btn_h, hwnd, (HMENU) ID_QUIT, NULL, NULL); + /* Ensure dialog is displayed at the very top */ + /* SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | */ + /* SWP_SHOWWINDOW); */ + break; + + case WM_TIMER: + is_timeout = TRUE; + DestroyWindow(hwnd); + break; + + case WM_COMMAND: + + { + /* + * Plausibility check, whether cursor is still within rect. + * Buttons are not focusable, therefore pressing return is not in this case possible + */ + POINT p; + GetCursorPos(&p); + GetWindowRect(hwnd, &rect); + + if (p.x >= rect.left && p.x <= rect.right && p.y >= rect.top + && p.y <= rect.bottom) + is_within_rect = TRUE; + } + + /* Destroys and recreates a new window on "Ok" */ + if (LOWORD(wp) == ID_OK) { + MessageBeep(MB_OK); + DestroyWindow(hwnd); + } + + /* Sets stop condition, if "Quit" was clicked */ + if (LOWORD(wp) == ID_QUIT) { + DestroyWindow(hwnd); + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + } + + return DefWindowProcW(hwnd, msg, wp, lp); +} +/* + * Displays a dialog and waits for interaction + */ +int confirm_dialog(BOOL is_plausibility_check) { + + MSG msg; + int mx, my, rx, ry, mw, mh; + + /* Defines class and window's attributes*/ + WNDCLASSW wc = { 0 }; + wc.lpszClassName = L"RTT window"; + wc.hInstance = NULL; + wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); + wc.lpfnWndProc = timed_dialog_proc; + wc.hCursor = LoadCursor(0, IDC_ARROW); + srand(GetTickCount()); + + /* Size of screen without taskbar */ + mx = GetSystemMetrics(SM_CXFULLSCREEN); + my = GetSystemMetrics(SM_CYFULLSCREEN); + + /* Limits for window size */ + mw = GetSystemMetrics(SM_CXMIN); + mh = GetSystemMetrics(SM_CYMIN) * 3; + + /* Randomize window position */ + rx = ((double) rand() / RAND_MAX) * (mx - (4 * mw)) + mw; + ry = ((double) rand() / RAND_MAX) * (my - (4 * mh)) + mh; + + RegisterClassW(&wc); + CreateWindowW(wc.lpszClassName, L"RTT window", + WS_OVERLAPPEDWINDOW | WS_VISIBLE, rx, ry, mw, + mh, 0, 0, NULL, 0); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (!is_timeout) { + if (is_plausibility_check) + return !is_within_rect; + else + return FALSE; + } + + return TRUE; +} + +/* + * Wrapper function for displaying a dialog and checking if interaction occurs. + * + * Found in similar form in XLM-macro with MD5: 50d518246c2b61f5b427948f87a0aa24 + * See also: https://www.lastline.com/labsblog/evolution-of-excel-4-0-macro-weaponization/ + */ +int rtt_confirm_dialog() { + return confirm_dialog(FALSE); +} + +/* + * Wrapper function like rtt_confirm_dialog(), but performs an additional check + * for plausibility by determining, whehter the cursor resides inside the + * bounding box of the displayed dialog add the time of confirmation. + * Thereby, machine-triggered SendMessageW-interactions can be identified. + */ +int rtt_plausible_confirm_dialog() { + return confirm_dialog(TRUE); +} diff --git a/pafish/rtt.h b/pafish/rtt.h new file mode 100644 index 0000000..3ffde6b --- /dev/null +++ b/pafish/rtt.h @@ -0,0 +1,18 @@ +#ifndef RTT_H +#define RTT_H + +int rtt_mouse_presence(); + +int rtt_mouse_move(); + +int rtt_mouse_speed_limit(); + +int rtt_mouse_click(); + +int rtt_mouse_double_click(); + +int rtt_confirm_dialog(); + +int rtt_plausible_confirm_dialog(); + +#endif