├── README.md ├── bin └── winmine.exe ├── main.py ├── mine_injector.py └── ui.py /README.md: -------------------------------------------------------------------------------- 1 | # MinesweeperPythonCrack 2 | 用Python写的扫雷外挂 3 | 实现了如下功能 4 | * 自动扫雷 5 | * 时间回滚 6 | * 遇雷不结束 7 | -------------------------------------------------------------------------------- /bin/winmine.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheep426/MinesweeperPythonCrack/96f2c22587bcdd168bbb2693cb1611d253c7369b/bin/winmine.exe -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import sys 3 | import psutil 4 | import ctypes 5 | import win32api 6 | import struct 7 | from ctypes.wintypes import * 8 | 9 | import mine_injector 10 | 11 | def injectDll(app_name=None): 12 | PAGE_READWRITE = 0x04 13 | PROCESS_ALL_ACCESS = (0x000F0000|0x00100000|0xFFF) 14 | VIRTUAL_MEM = (0x1000 | 0x2000) 15 | 16 | #dll_path = "bin/CheatTools.dll".encode("ascii",'ignore') 17 | #dll_len = len(dll_path) 18 | 19 | pid_list = psutil.pids() 20 | found = False 21 | 22 | for pid in pid_list: 23 | process_data = psutil.Process(pid) 24 | if process_data.name() == app_name: 25 | found = True 26 | break 27 | print ('pid',pid) 28 | 29 | if found: 30 | mine_data = mine_injector.mine_obj(pid) 31 | print (mine_data.get_mine_list() ) 32 | #mine_data.revert_time() 33 | mine_data.auto_play() 34 | #mine_data.stop_time() 35 | #mine_data.disable_bome() 36 | else: 37 | print ("%s not found" % app_name) 38 | 39 | if __name__ == "__main__": 40 | injectDll("winmine.exe") 41 | 42 | 43 | -------------------------------------------------------------------------------- /mine_injector.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import sys 3 | import psutil 4 | import ctypes 5 | import win32api 6 | import struct 7 | from ctypes.wintypes import * 8 | 9 | OpenProcess = ctypes.windll.kernel32.OpenProcess 10 | OpenProcess.restype = HANDLE 11 | OpenProcess.argtypes = (DWORD, BOOL, DWORD) 12 | 13 | 14 | ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory 15 | ReadProcessMemory.restype = BOOL 16 | ReadProcessMemory.argtypes = (HANDLE, LPCVOID, LPVOID, DWORD, DWORD) 17 | 18 | WriteProcessMemory = ctypes.windll.kernel32.WriteProcessMemory 19 | WriteProcessMemory.restype = BOOL 20 | WriteProcessMemory.argtypes = (HANDLE, LPVOID, LPCVOID, DWORD, DWORD) 21 | 22 | VirtualAllocEx = ctypes.windll.kernel32.VirtualAllocEx 23 | VirtualAllocEx.restype = LPVOID 24 | VirtualAllocEx.argtypes = (HANDLE, LPVOID, DWORD, DWORD, DWORD) 25 | 26 | PROCESS_ALL_ACCESS = (0x000F0000|0x00100000|0xFFF) 27 | VIRTUAL_MEM = (0x1000|0x2000) 28 | PAGE_READWRITE = 0x00000040 29 | MEM_DECOMMIT = 0x00004000 30 | 31 | class mine_obj(): 32 | def __init__(self,pid): 33 | self.row_address = 0x01005338 34 | self.col_address = 0x01005334 35 | self.game_time_address = 0x100579C 36 | self.bome_count_address = 0x01005194 37 | self.pid = pid 38 | 39 | self.block_start_address = 0x01005340 40 | 41 | self.handle =OpenProcess(PROCESS_ALL_ACCESS,False,int(pid)) 42 | if not self.handle: 43 | print ("error get handle of pid %s" % pid) 44 | raise Exception("error %s" % ctypes.GetLastError() ) 45 | exit(1) 46 | 47 | def read_memory(self,handle,address,size): 48 | numberOfBytesRead = 0 49 | if size == 4: 50 | buffer = ctypes.c_int() 51 | elif size ==2: 52 | buffer = ctypes.c_short() 53 | elif size ==1: 54 | buffer = ctypes.c_char() 55 | result = ReadProcessMemory(handle, address, ctypes.byref(buffer), size,numberOfBytesRead) 56 | if result is None or result == 0: 57 | raise Exception("error %s" % ctypes.GetLastError() ) 58 | return buffer.value 59 | 60 | def get_row_data(self): 61 | return self.read_memory(self.handle,self.row_address,4) 62 | 63 | def get_col_data(self): 64 | return self.read_memory(self.handle,self.col_address,4) 65 | 66 | def get_game_time_data(self): 67 | return self.read_memory(self.handle,self.game_time_address,4) 68 | 69 | def get_bome_count_data(self): 70 | return self.read_memory(self.handle,self.bome_count_address,4) 71 | 72 | def refresh_data(self): 73 | return 0 74 | 75 | def allocate(self,hProcess, lpAddress, dwSize, flAllocationType, flProtect): 76 | lpBuffer = VirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect) 77 | if lpBuffer is None or lpBuffer == 0: 78 | raise Exception('Error: %s' %ctypes.GetLastError()) 79 | return lpBuffer 80 | 81 | def write_buffer(self,hProcess, lpBaseAddress, lpBuffer, nSize): 82 | dwNumberOfBytesWritten = WriteProcessMemory.argtypes[-1]() 83 | #result = WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, ctypes.addressof(dwNumberOfBytesWritten)) 84 | result = WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, 0) 85 | if result is None or result == 0: 86 | raise Exception('Error: %s' %ctypes.GetLastError()) 87 | return result 88 | 89 | def stop_time(self): 90 | #6bytes 91 | #这里应该要用virtualProtect来绕过dep的保护。但是不知道为什么不需要直接写入就可以了. 92 | shellcode = b"\x90\x90\x90\x90\x90\x90" 93 | shellcode_length = 6 94 | game_time_add_asm_address = 0x1002FF5 95 | self.write_buffer(self.handle,game_time_add_asm_address,shellcode,shellcode_length) 96 | 97 | def disable_bome(self): 98 | shellcode = b"\x90\x90\x90\x90\x58" 99 | shellcode_length = len(shellcode) 100 | check_bome_asm_address = 0x10035AB 101 | self.write_buffer(self.handle,check_bome_asm_address,shellcode,shellcode_length) 102 | 103 | def get_mine_list(self): 104 | mine_dict = {} 105 | col_count = self.get_col_data() 106 | row_count = self.get_row_data() 107 | 108 | for row in range(1,row_count+1): 109 | for col in range(1,col_count+1): 110 | address = int( hex(self.block_start_address + row * 32 + col),16) 111 | block_data = self.read_memory(self.handle,address,1) 112 | if not row in mine_dict: 113 | mine_dict[row] = {} 114 | if block_data == b'\x8f': 115 | mine_dict[row][col] = True 116 | else: 117 | mine_dict[row][col] = False 118 | return mine_dict 119 | 120 | def click(self,x,y): 121 | ''' 122 | push ebp ; 先保存ebp 123 | mov ebp,esp ;把当前的esp放到ebp 124 | push dword ptr 3 125 | push dword ptr 4 126 | mov eax, 0x1003512 ;参数压入栈之后调用函数 127 | call eax 128 | xor eax,eax 129 | mov esp,ebp 130 | pop ebp ;恢复堆栈 131 | ret 132 | ''' 133 | shellcode_list = [] 134 | shellcode_list.append(b'\x55\x89\xE5\x6a') 135 | shellcode_list.append(bytes([x])) 136 | shellcode_list.append(b'\x6a') 137 | shellcode_list.append(bytes([y])) 138 | shellcode_list.append(b'\xb8\x12\x35\x00\x01\xFF\xD0\x31\xC0\x89\xEC\x5D\xC3') 139 | 140 | shellcode = b''.join( shellcode_list ) 141 | shellcode_length = len(shellcode) 142 | arg_address = self.allocate(self.handle,0,shellcode_length,VIRTUAL_MEM,PAGE_READWRITE) 143 | whhh=self.write_buffer(self.handle,arg_address,shellcode,shellcode_length) 144 | 145 | thread_id = ctypes.c_ulong(0) 146 | thread =ctypes.windll.kernel32.CreateRemoteThread(self.handle,None,0,arg_address,None,0,ctypes.byref(thread_id)) 147 | if not thread: 148 | raise Exception('Error: %s' %ctypes.GetLastError()) 149 | ctypes.windll.kernel32.WaitForSingleObject(thread,0xFFFFFFFF) 150 | ctypes.windll.kernel32.CloseHandle(thread) 151 | ctypes.windll.kernel32.VirtualFreeEx(self.handle, arg_address, shellcode_length, MEM_DECOMMIT) 152 | 153 | def revert_time(self): 154 | ''' 155 | push ebp 156 | mov ebp,esp 157 | mov eax,0 158 | mov [0x100579c],eax 159 | mov esp,ebp 160 | pop ebp 161 | ret 162 | ''' 163 | shellcode = b"\x55\x89\xE5\xB8\x00\x00\x00\x00\xA3\x9C\x57\x00\x01\x89\xEC\x5D\xC3" 164 | self.run_shell_code(shellcode) 165 | 166 | def run_shell_code(self,shellcode): 167 | shellcode_length = len(shellcode) 168 | arg_address = self.allocate(self.handle,0,shellcode_length,VIRTUAL_MEM,PAGE_READWRITE) 169 | whhh=self.write_buffer(self.handle,arg_address,shellcode,shellcode_length) 170 | 171 | thread_id = ctypes.c_ulong(0) 172 | thread =ctypes.windll.kernel32.CreateRemoteThread(self.handle,None,0,arg_address,None,0,ctypes.byref(thread_id)) 173 | if not thread: 174 | raise Exception('Error: %s' %ctypes.GetLastError()) 175 | ctypes.windll.kernel32.WaitForSingleObject(thread,0xFFFFFFFF) 176 | #一定要close要不会挂,暂时原因不知道 177 | ctypes.windll.kernel32.CloseHandle(thread) 178 | #调试的时候这句要关闭,要不attach上去的时候地址就被处理了 179 | ctypes.windll.kernel32.VirtualFreeEx(self.handle, arg_address, shellcode_length, MEM_DECOMMIT) 180 | 181 | 182 | def auto_play(self): 183 | mine_dict = self.get_mine_list() 184 | col_count = self.get_col_data() 185 | row_count = self.get_row_data() 186 | for row in range(1,row_count+1): 187 | for col in range(1,col_count+1): 188 | if not mine_dict[row][col]: 189 | self.click(row,col) -------------------------------------------------------------------------------- /ui.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import sys 3 | from PyQt5.QtWidgets import QApplication, QWidget , QPushButton , QMessageBox,QGridLayout,QListWidget,QHBoxLayout 4 | import sys 5 | import psutil 6 | import mine_injector 7 | 8 | class MainWindow(QWidget): 9 | def __init__(self): 10 | QWidget.__init__(self) 11 | self.init_game = False 12 | self.load_mine_data() 13 | 14 | if not self.init_game: 15 | msg = QMessageBox() 16 | msg.setIcon(QMessageBox.Information) 17 | msg.setText(u"请先运行winmine.exe") 18 | msg.setWindowTitle("错误") 19 | msg.exec_() 20 | else: 21 | self.do_layout() 22 | 23 | 24 | def do_layout(self): 25 | layout = QGridLayout() 26 | self.setLayout(layout) 27 | self.listwidget = QListWidget(self) 28 | col_count = self.mine_obj.get_col_data() 29 | row_count = self.mine_obj.get_row_data() 30 | mine_list = self.mine_obj.get_mine_list() 31 | row_counter = 0 32 | for row in range(1,row_count+1): 33 | for col in range(1,col_count+1): 34 | if mine_list[row][col]: 35 | self.listwidget.insertItem(row_counter,u"第%s行 第%s列 为雷" %( row,col)) 36 | row_counter=row_counter+1 37 | 38 | self.auto_play_button = QPushButton(self) 39 | self.auto_play_button.setText(u"一件扫雷") 40 | self.auto_play_button.clicked.connect(self.click_auto_play) 41 | 42 | self.revert_time_button = QPushButton(self) 43 | self.revert_time_button.setText(u"时间归0") 44 | self.revert_time_button.clicked.connect(self.click_revert_time) 45 | 46 | 47 | self.stop_time_button = QPushButton(self) 48 | self.stop_time_button.setText(u"暂停计时") 49 | self.stop_time_button.clicked.connect(self.click_stop_time) 50 | 51 | self.disable_bome_button = QPushButton(self) 52 | self.disable_bome_button.setText(u"遇雷不失败") 53 | self.disable_bome_button.clicked.connect(self.click_disable_bome) 54 | 55 | 56 | layout.addWidget(self.listwidget,0,0,1,2) 57 | layout.addWidget(self.auto_play_button,1,0) 58 | layout.addWidget(self.stop_time_button,1,1) 59 | layout.addWidget(self.revert_time_button,2,0) 60 | layout.addWidget(self.disable_bome_button,2,1) 61 | 62 | def click_auto_play(self): 63 | self.mine_obj.auto_play() 64 | 65 | def click_revert_time(self): 66 | self.mine_obj.revert_time() 67 | 68 | def click_stop_time(self): 69 | self.mine_obj.stop_time() 70 | 71 | def click_disable_bome(self): 72 | self.mine_obj.disable_bome() 73 | 74 | def load_mine_data(self): 75 | pid_list = psutil.pids() 76 | found = False 77 | for pid in pid_list: 78 | process_data = psutil.Process(pid) 79 | if process_data.name() == "winmine.exe": 80 | found = True 81 | break 82 | if found: 83 | self.mine_obj = mine_injector.mine_obj(pid) 84 | self.init_game = True 85 | else: 86 | self.init_game = False 87 | 88 | 89 | 90 | if __name__ == "__main__": 91 | app = QApplication(sys.argv) 92 | w = MainWindow() 93 | 94 | w.resize(640,480) 95 | w.setWindowTitle("扫雷外挂") 96 | 97 | if w.init_game: 98 | print ("now show") 99 | w.show() 100 | sys.exit(app.exec_()) --------------------------------------------------------------------------------