Python實現(xiàn)向好友發(fā)送微信消息
前言
原理:Windows逆向,通過內(nèi)聯(lián)匯編的形式調(diào)用發(fā)消息的函數(shù)
下面的代碼PC微信版本是:3.7.0.26 , python使用的32位的3.8.13版本
如果只是需要最后的Python代碼請直接翻到最后看 優(yōu)化篇
c語言發(fā)微信消息
因為c語言本身支持內(nèi)聯(lián)匯編,所以寫發(fā)消息的代碼很簡單,需要找到發(fā)消息的call,構(gòu)造一下參數(shù)即可
// 微信通用結(jié)構(gòu)體 struct WxBaseStruct { wchar_t* buffer; DWORD length; DWORD maxLength; DWORD fill1; DWORD fill2; WxBaseStruct(wchar_t* pStr) { buffer = pStr; length = wcslen(pStr); maxLength = wcslen(pStr) * 2; fill1 = 0x0; fill2 = 0x0; } }; void SendText( wchar_t* wsTextMsg) { // 發(fā)送的好友,filehelper是文件傳輸助手 wchar_t wsWxId[0x10] = L"filehelper"; WxBaseStruct wxWxid(wsWxId); // 發(fā)送的消息內(nèi)容 WxBaseStruct wxTextMsg(wsTextMsg); wchar_t** pWxmsg = &wxTextMsg.buffer; char buffer[0x3B0] = { 0 }; char wxNull[0x100] = { 0 }; DWORD dllBaseAddress = (DWORD)GetModuleHandleA("WeChatWin.dll"); // 發(fā)消息的函數(shù)call地址 DWORD callAddress = dllBaseAddress + 0x521D30; __asm { lea eax, wxNull; push 0x1; push eax; mov edi, pWxmsg; push edi; lea edx, wxWxid; lea ecx, buffer; call callAddress; add esp, 0xC; } }
這部分不懂的可以百度pc微信發(fā)消息call,相關(guān)文章很多,基本從找call,調(diào)用call都有了。
寫好代碼然后封裝成dll注入到微信即可
Python調(diào)用
原理:c語言寫的dll將發(fā)消息的函數(shù)導出,這樣就能通過符號找到SendText的地址,然后通過CreateRemoteThread
來調(diào)用,這也就是為什么我SendText里將wsWxId寫死的原因
CreateRemoteThread調(diào)用的外部函數(shù)只能傳遞一個參數(shù)。要想傳遞多個參數(shù),兩種方式:通過傳入結(jié)構(gòu)體的方式;或者寫一個匯編函數(shù)調(diào)用call轉(zhuǎn)機器碼后寫入進程空間,然后調(diào)用這個寫入的函數(shù)。
另外,為了方便我直接使用pymem庫,pip install pymem
,也可以自己使用ctypes封裝,參考pymem的源碼,以WriteProcessMemory 為例
import ctypes dll = ctypes.WinDLL('kernel32.dll') WriteProcessMemory = dll.WriteProcessMemory WriteProcessMemory.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t) ] WriteProcessMemory.restype = ctypes.c_long
這個定義的WriteProcessMemory
就是pymem.ressources.kernel32.WriteProcessMemory
import pymem import ctypes def start_thread(process_handle, address, params=None): '''調(diào)用CreateRemoteThread process_handle: 外部進程句柄 address: 要調(diào)用的函數(shù)地址 params: 參數(shù)地址 ''' params = params or 0 NULL_SECURITY_ATTRIBUTES = ctypes.cast(0, pymem.ressources.structure.LPSECURITY_ATTRIBUTES) thread_h = pymem.ressources.kernel32.CreateRemoteThread( process_handle, NULL_SECURITY_ATTRIBUTES, 0, address, params, 0, ctypes.byref(ctypes.c_ulong(0)) ) last_error = ctypes.windll.kernel32.GetLastError() if last_error: pymem.logger.warning('Got an error in start thread, code: %s' % last_error) pymem.ressources.kernel32.WaitForSingleObject(thread_h, -1) return thread_h def main(wxpid, content): # 獲取進程句柄 process_handle = pymem.process.open(wxpid) # 這里通過加載dll(sendText.dll) ,獲取到 SendText的地址偏移 # 當然你也可以直接用查看pe的軟件直接看SendText函數(shù)的偏移 ctypes.CDLL(r'F:\Code\匯編\sendText\Release\sendText.dll') local_sendText_handle = pymem.ressources.kernel32.GetModuleHandleW("sendText.dll") SendText_address = pymem.ressources.kernel32.GetProcAddress(local_sendText_handle, b"SendText") sendText_Offset = SendText_address - local_sendText_handle # 獲取到微信進程中sendText.dll的句柄 process_sendText_handle = pymem.process.module_from_name(process_handle, "sendText.dll") # 微信進程內(nèi)SendText的函數(shù)地址就等于 sendText.dll的基址加上偏移 SendText = process_sendText_handle.lpBaseOfDll + sendText_Offset # 開始構(gòu)造消息,因為是參數(shù)是wchar_t類型的,所以需要編碼為utf-16的 msg = content.encode('utf-16') # 在微信進程申請一塊內(nèi)存空間,大小為1000個字節(jié) address = pymem.memory.allocate_memory(process_handle, 1000) # 輸出SendText地址和申請的內(nèi)存地址 print(SendText, "address:", hex(address)) # 往申請的內(nèi)存地址寫入我們要發(fā)送的消息內(nèi)容 pymem.ressources.kernel32.WriteProcessMemory(process_handle, address, msg, len(msg), None) # 調(diào)用CreateRemoteThread發(fā)送消息 thread_h = start_thread(process_handle, SendText, address) print(thread_h) if __name__ == "__main__": wxpid = 18196 main(wxpid, "Python test!")
不用c編寫dll如何發(fā)消息
用c寫dll再用Python來調(diào)用dll里的函數(shù)太麻煩了,能不能只用Python就實現(xiàn)發(fā)消息的功能。
回答這個問題前先看看寫的dll里SendText函數(shù)的匯編形式
一共有四部分內(nèi)容,第一部分是地址段,第二部分是機器語言,第三部分是匯編語言,第四部分是注釋。
猜測:既然函數(shù)就是第二部分的機器語言組成的,那么我直接將第二部分的內(nèi)容拷貝到另一個地址,是否能正常執(zhí)行。為了方便和準確,我直接使用代碼拷貝了。
首先在sendtext.dll最下面選一塊沒有被使用的地址(508C1D80)
import pymem import ctypes def copy_code(wxpid, source_addr, buffer_len, src_addr): # 獲取進程句柄 process_handle = pymem.process.open(wxpid) # 申請一塊臨時內(nèi)存空間存放讀取的機器碼 buffer = ctypes.create_string_buffer(buffer_len) # 讀出source_addr地址里的內(nèi)容到buffer,長度為buffer_len pymem.ressources.kernel32.ReadProcessMemory(process_handle, source_addr, buffer, buffer_len, None) # 寫入buffer.raw內(nèi)容到src_addr,長度為buffer_len pymem.ressources.kernel32.WriteProcessMemory(process_handle, src_addr, buffer.raw, buffer_len, None) # 打印內(nèi)容 print(buffer.raw.hex()) if __name__ == "__main__": wxpid = 18196 source_addr = 0x508C1010 buffer_len = 0x127 src_addr = 0x508C1D80 copy_code(wxpid, source_addr, buffer_len, src_addr )
0x508C1010就是上面那種匯編圖第一段的首地址,buffer_len 就是圖的最后一個地址減第一個地址+1的值,0x508C1D80就是我選擇的一塊空白的空間
復制完成后先不直接調(diào)用,先用x64dbg看看翻譯的匯編指令是不是一樣的。我對比了一下,發(fā)現(xiàn)有一點出入,也就是說相同的機器碼放在不同的內(nèi)存地址解釋出來的匯編指定是不一樣的,主要不同之處在于call指定后面的地址。圖中三個地方:508C10DD、508C10F0和508C112E
要想調(diào)用成功,首先得清楚為什么這三個call后面的地址不一樣??梢詮哪夸浱D(zhuǎn)到call、jmp指令地址計算看看,也就是說E8后面跟的地址是通過call的地址減去E8這條指令所在的地址再減5得出來的
以508C10DD為例,圖中顯示的匯編是call <sendtext._memset>
,后面的其實就是一個地址,在x64dbg里點擊這條匯編按空格就可以編輯,看到真實的地址是call 0x508C1D2C
計算表達式:0x508C1D2C-0x508C10DD-5 = 00000C4A
,而機器碼是 E8 4A0C0000
基本對上了,順序應(yīng)該只是大端和小端的問題,這個就不去研究了。這里的5是指E8 4A0C0000
的字節(jié)數(shù),其實真正的計算公式是call的地址減去當前指令的下一條指令的地址,而下一條指令的地址就是當前指令的地址+當前指令所占字節(jié)。就像圖中的jne
指令則是減2
調(diào)用我們寫入的機器碼
上面說我在0x508C1D80寫入的機器碼,call后面的地址不對。先手動用x64dbg按空格鍵將地址修改成和dll中調(diào)用原函數(shù)的匯編一樣,然后再使用Python調(diào)用這個地址發(fā)消息。只需要把SendText = process_sendText_handle.lpBaseOfDll + sendText_Offset
改成SendText = 0x508C1D80
測試是調(diào)用成功的,也就是說知道函數(shù)機器碼的情況下,我們完全可以自己在內(nèi)存自己構(gòu)造一個發(fā)消息的函數(shù),而不用c語言寫dll。
要想實現(xiàn)上面的機器碼能完全脫離dll還需要一些操作,因為用x64dbg看到的一些地址前面都帶了sendtext.508C1960,說明這個地址是dll里的地址,如果dll不存在了,則這些地址里的內(nèi)容也沒有意義了
還是先處理那三個call, call <sendtext._memset>,回車進到該函數(shù)發(fā)現(xiàn)是vcruntime140.dll
里的memset,那么我把這條指令改成 call vcruntime14.memset
應(yīng)該也是能調(diào)用成功的吧,看了下vcruntime14.memset的地址是553DDA10,就改成call 0x553DDA10
,試了下,確實是成功的。
508C112E的匯編是call <sendtext.@__security_check_cookie@4>
,回車進入該函數(shù)就三條指令
cmp ecx,dword ptr ds:[<___security_cookie>]
bnd jne <sendtext.failure>
bnd ret
是一個判斷,成功就ret,失敗就jne。所以這就第一條指令是有效的,將508C112E的call指令直接改成cmp ecx,dword ptr ds:[<___security_cookie>]
試試,字節(jié)數(shù)不一樣,所以我就不去改sendtext函數(shù)的原地址,我改0x508C1D80的代碼,也就是我自己寫入的代碼,也是成功調(diào)用的
圖中的jne指令并不需要改,這是相對地址,剩下需要改的就是一些內(nèi)存地址了。比如508C1023出現(xiàn)的0x508C20AC,這個代碼沒太看懂。0x508C209C顯示是filehelper,也就是我們寫死的。用ce搜索utf-16的字符串filehelper, 然后替換這兩個地址(508C1023、508C1028)。假設(shè)搜索到的地址是010EE490,則把508C209C改成010EE490,508C20AC改成010EE4A0。再試著發(fā)下消息,也是成功的,其他幾個地址也做類似處理,如果ce沒有搜到,就用Python申請,然后寫入相應(yīng)的內(nèi)容即可
處理完后的機器碼寫入到WechatWin.dll調(diào)用會崩潰,原因可能是<___security_cookie>的地址也是dll中,我百度了下,這是微軟的GS編譯,用于驗證堆棧平衡的,可以在vs2017中項目-屬性->C/C+±?代碼生成->安全檢查->禁用安全檢查,就可以關(guān)閉掉了。接著生成的匯編代碼就不會包含cookie這種東西了
另外寫入到WechatWin.dll的vcruntime14.memset的機器碼也要改下,做完這些就可以調(diào)用成功了。
上面代碼有個小錯誤, msg = content.encode('utf-16')
這里應(yīng)該改成msg = content.encode('utf-16le')
兩個的區(qū)別請看:https://blog.csdn.net/QQxiaoqiang1573/article/details/84937863
第一次優(yōu)化
上面折騰了那么多,寫入?yún)R編之后還是要手動改vcruntime14.memset的機器碼,能不能在寫入?yún)R編的時候自動計算機器碼呢?上面已經(jīng)知道了計算公式,那么自動計算應(yīng)該也不難
期間又遇到兩個小問題,GetModuleHandleA的地址也是變化的,需要動態(tài)獲取。vcruntime140.dll沒有被加載,是在注入sendtext.dll后才被加載。
完整代碼如下,如果你想在自己電腦上運行,其中有個地方需要改。C:\Software\WeChat\3.7.0.26\vcruntime140.dll
這個路徑得改成你自己微信3.7.0.26下的vcruntime140.dll,必須要是微信下的vcruntime140.dll,系統(tǒng)的不行,因為偏移不一樣。用系統(tǒng)的則需要改0xDA10這個偏移量
import os import pymem import ctypes import time def calc_code(calladdr, codeaddr): code = calladdr - codeaddr - 5 hex_code = hex(code & 0xFFFFFFFF) return hex_code def convert_addr(addr): if isinstance(addr, int): addr = hex(addr) if addr.startswith("0x") or addr.startswith("0X"): addr = addr[2:] if len(addr) < 8: addr = (8-len(addr))*'0' + addr tmp = [] for i in range(0, 8, 2): tmp.append(addr[i:i+2]) tmp.reverse() return ''.join(tmp) def start_thread(process_handle, address, params=None): '''調(diào)用CreateRemoteThread process_handle: 外部進程句柄 address: 要調(diào)用的函數(shù)地址 params: 參數(shù)地址 ''' params = params or 0 NULL_SECURITY_ATTRIBUTES = ctypes.cast(0, pymem.ressources.structure.LPSECURITY_ATTRIBUTES) thread_h = pymem.ressources.kernel32.CreateRemoteThread( process_handle, NULL_SECURITY_ATTRIBUTES, 0, address, params, 0, ctypes.byref(ctypes.c_ulong(0)) ) last_error = ctypes.windll.kernel32.GetLastError() if last_error: pymem.logger.warning('Got an error in start thread, code: %s' % last_error) pymem.ressources.kernel32.WaitForSingleObject(thread_h, -1) return thread_h def main(wxpid, content): format_code = '558bec81ecfc040000a1{filehelper10}0f1005{filehelper}8945c466a1{buffer}668945c88d45b48bc866c745d200000f1145b4560f57c08945d457660fd645ca8d5102668b0183c1026685c075f52bcac745e000000000d1f9894dd8c745e4000000008d04098b4d088bd18945dc894de88d7202668b0283c2026685c075f52bd6d1fa8955ec8d5102668b0183c1026685c075f52bcac745f400000000d1f968b00300006a00c745f8000000008d04098945f08d45e88945088d8504fbffff50e8{memset1}68000100008d85b4feffff6a0050e8{memset2}83c41868{wechatwin}ff15{GetModuleHandleA}05301d52008945fc8d85b4feffff6a01508b7d08578d55d48d8d04fbffffff55fc83c40c5f5e8be55dc3' process_handle = pymem.process.open(wxpid) filehelper_address = pymem.memory.allocate_memory(process_handle, 50) text = "filehelper".encode("utf-16le") pymem.ressources.kernel32.WriteProcessMemory(process_handle, filehelper_address, text, len(text), None) filehelper_hex_code = convert_addr(filehelper_address) filehelper10_hex_code = convert_addr(filehelper_address+0x10) buffer_address = pymem.memory.allocate_memory(process_handle, 16) buffer_hex_code = convert_addr(buffer_address) WeChatWin_address = pymem.memory.allocate_memory(process_handle, 100) msg = "WeChatWin.dll".encode("ascii") pymem.ressources.kernel32.WriteProcessMemory(process_handle, WeChatWin_address, msg, len(msg), None) wechatwin_hex_code = convert_addr(WeChatWin_address) ctypes.CDLL('kernel32.dll') local_kernel32_handle = pymem.ressources.kernel32.GetModuleHandleW("kernel32.dll") GetModuleHandleA_address = pymem.ressources.kernel32.GetProcAddress(local_kernel32_handle, b"GetModuleHandleA") GetModuleHandleA_Offset = GetModuleHandleA_address - local_kernel32_handle process_kernel32_handle = pymem.process.module_from_name(process_handle, "kernel32.dll") GetModuleHandleA = process_kernel32_handle.lpBaseOfDll + GetModuleHandleA_Offset GetModuleHandleA_address = pymem.memory.allocate_memory(process_handle, 4) pymem.memory.write_int(process_handle, GetModuleHandleA_address, GetModuleHandleA) GetModuleHandleA_hex_code = convert_addr(GetModuleHandleA_address) process_vcruntime140_handle = pymem.process.module_from_name(process_handle, "vcruntime140.dll") if not process_vcruntime140_handle: pymem.process.inject_dll(process_handle, r'C:\Software\WeChat\3.7.0.26\vcruntime140.dll'.encode("ascii")) process_vcruntime140_handle = pymem.process.module_from_name(process_handle, "vcruntime140.dll") memset = process_vcruntime140_handle.lpBaseOfDll + 0xDA10 code_address = pymem.memory.allocate_memory(process_handle, 500) memset1 = convert_addr(calc_code(memset, code_address+0xBE)) memset2 = convert_addr(calc_code(memset, code_address+0xD1)) hex_code = format_code.format(filehelper10=filehelper10_hex_code, filehelper=filehelper_hex_code, buffer=buffer_hex_code, wechatwin=wechatwin_hex_code, memset1=memset1, memset2=memset2, GetModuleHandleA=GetModuleHandleA_hex_code) hex_code = bytes.fromhex(hex_code) pymem.ressources.kernel32.WriteProcessMemory(process_handle, code_address, hex_code, len(hex_code), None) msg = content.encode('utf-16le') address = pymem.memory.allocate_memory(process_handle, 1000) pymem.ressources.kernel32.WriteProcessMemory(process_handle, address, msg, len(msg), None) print(hex(code_address)) # 調(diào)用CreateRemoteThread發(fā)送消息 thread_h = start_thread(process_handle, code_address, address) time.sleep(0.5) pymem.memory.free_memory(process_handle, filehelper_address) pymem.memory.free_memory(process_handle, buffer_address) pymem.memory.free_memory(process_handle, WeChatWin_address) pymem.memory.free_memory(process_handle, code_address) pymem.memory.free_memory(process_handle, GetModuleHandleA_address) if __name__ == "__main__": wxpid = 24600 process_handle = pymem.process.open(wxpid) main(wxpid, "你好")
第二次優(yōu)化
優(yōu)化主要還有兩個點:發(fā)送的人不要寫死,也可以通過參數(shù)傳入,這個實現(xiàn)很簡單,寫死的那個filehelper也是在內(nèi)存構(gòu)造的,當然也可以構(gòu)造任意一個好友的wxid;上面的代碼也很麻煩,還要自己拼裝十六進程的機器碼,能不能只寫匯編,然后自動轉(zhuǎn)成機器碼呢?先說結(jié)果:是可以的,Python就有很多匯編轉(zhuǎn)機器碼的庫,比如keystone和unicorn等,待我在研究研究
x86/x64 Call Jmp指令區(qū)別
- Call指令主要實現(xiàn)對一個函數(shù)的調(diào)用。Jmp指令主要實現(xiàn)地址的調(diào)轉(zhuǎn)。
- Call指令和Jmp指令的區(qū)別
1:Call指令和Jmp指令的機器碼不同。
2:Call指令會對當前指令的下一條指令的地址進行壓棧操作,來實現(xiàn)函數(shù)的返回。
相當于
Push eip+5
Jmp xxxxxxxx
Call指令的二進制形態(tài)(機器碼)
1:X86
Call --- e8/ ff15(但是其他比如 call eax 等是不相同的)
E8 xxxxxxxx 其中xxxxxxxx是偏移地址
計算方法:目標地址-當前地址-5 = 偏移地址
Ff15 xxxxxxxx 其中xxxxxxxx是絕對地址(FF15會對當前的這個絕對地址解*號,也就是絕對地址[目標地址])
Jmp ---e9 /ff25
E9 xxxxxxxx其中xxxxxxxx是偏移地址
計算方法:目標地址-當前地址-5 = 偏移地址
Ff25 xxxxxxxx其中xxxxxxxx是絕對地址(FF15會對當前的這個絕對地址解*號,也就是絕對地址[目標地址])
2:X64
E8 xxxxxxxx 其中xxxxxxxx是偏移地址
計算方法:目標地址-當前地址-5 = 偏移地址
Ff15 xxxxxxxx 其中xxxxxxxx是相對地址(FF15會對當前的這個相對地址解*號,也就是相對地址[目標地址])
Ff25 xxxxxxxx其中xxxxxxxx是相對地址(FF15會對當前的這個相對地址解*號,也就是相對地址[目標地址])
到此這篇關(guān)于Python實現(xiàn)向好友發(fā)送微信消息 的文章就介紹到這了,更多相關(guān)Python微信消息內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python使用sort和class實現(xiàn)的多級排序功能示例
這篇文章主要介紹了Python使用sort和class實現(xiàn)的多級排序功能,涉及Python基于面向?qū)ο蟮脑乇闅v、列表排序、添加等相關(guān)操作技巧,需要的朋友可以參考下2018-08-08python實現(xiàn)批量轉(zhuǎn)換圖片為黑白
這篇文章主要為大家詳細介紹了python實現(xiàn)批量轉(zhuǎn)換圖片為黑白,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-06-06django獲取from表單multiple-select的value和id的方法
今天小編就為大家分享一篇django獲取from表單multiple-select的value和id的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07Python中循環(huán)后使用list.append()數(shù)據(jù)被覆蓋問題的解決
這篇文章主要給大家介紹了關(guān)于Python中循環(huán)后使用list.append()數(shù)據(jù)被覆蓋問題的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-07-07Python爬蟲實現(xiàn)selenium處理iframe作用域問題
這篇文章主要介紹了Python爬蟲實現(xiàn)selenium處理iframe作用域問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01Python?操作?MongoDB數(shù)據(jù)庫的方法(非?ODM)
這篇文章主要介紹了Python?操作?MongoDB?----非?ODM的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03