Windows逆向之Windows进程动态patch入门 与 pyqt5搭建GUI
目标
希望学习对Windows进程的动态patch,我们选择的exe是buuoj的“不一样的flag”。这题是迷宫题的hello world,没有加壳,没有任何代码混淆,且可以把它当成一个超小型的游戏,
有助于提升信心。
为了直观,以GUI的形式提供对目标进程的动态patch功能:
- 未检测到目标进程,相关的输入控件不可使用。
- 检测到目标进程时,读取目标进程内存中的数据,显示目标进程信息、迷宫和当前人物的坐标。
- 检测到目标进程时,提供“允许在障碍物上”、“允许不合法输入”和修改人物坐标功能(这些需求是根据其反汇编代码确定的)。
效果图:
检测到进程:
未检测到进程:
依赖
- spy :
<visual studio安装路径>\2019\Community\Common7\Tools\spyxx_amd64.exe
。 - 用pyqt5提供的qtdesigner来设计GUI。
- 用pyinstaller打包成exe。
- pywin32和pyqt5:
pip install pywin32
pip install PyQt5
pip install PyQt5-tools
pip install pyinstaller
pyqt5安装完毕后可以把<python安装路径>\Lib\site-packages\pyqt5_tools
加到环境变量上。
不想用visual studio cpp,太臃肿了。所以选择了pywin32。
分析
打开IDA,立刻得到反汇编代码。
void main()
{
char v0; // [sp 17h] [bp-35h]@1
int v1; // [sp 30h] [bp-1Ch]@1
int v2; // [sp 34h] [bp-18h]@1
signed int v3; // [sp 38h] [bp-14h]@2
signed int i; // [sp 3Ch] [bp-10h]@14
int v5; // [sp 40h] [bp-Ch]@20
__main();
v1 = 0;
v2 = 0;
qmemcpy(&v0, _data_start__, 0x19u);
while ( 1 )
{
puts("you can choose one action to execute");
puts("1 up");
puts("2 down");
puts("3 left");
printf("4 right\n:");
scanf("%d", &v3);
if ( v3 == 2 )
{
v1;
}
else if ( v3 > 2 )
{
if ( v3 == 3 )
{
--v2;
}
else
{
if ( v3 != 4 )
LABEL_13:
exit(1);
v2;
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
--v1;
}
for ( i = 0; i <= 1; i )
{
if ( *(&v1 i) < 0 || *(&v1 i) > 4 )
exit(1);
}
if ( *((_BYTE *)&v5 5 * v1 v2 - 41) == '1' )
exit(1);
if ( *((_BYTE *)&v5 5 * v1 v2 - 41) == '#' )
{
puts("\nok, the order you enter is the flag!");
exit(0);
}
}
}
这道迷宫题很简单,只说结论:通过patch
if ( *((_BYTE *)&v5 5 * v1 v2 - 41) == '1' ) exit(1);
实现“允许在障碍物上”。通过patch
if ( v3 != 4 )
LABEL_13:
exit(1);
if ( v3 != 1 ) goto LABEL_13;
实现“允许不合法输入”。
GUI
打开<python安装路径>\Lib\site-packages\qt5_applications\Qt\bin
下的designer.exe
。拖完ui后用pyuic把.ui
文件(实质上是xml文件)转为.py
:
pyuic5 -o destination.py source.ui
其中:
- -o表示生成一个文件。
destination.py
是要生成的.py文件。
见“代码”章节的different_flag_ui.py
。
Windows进程动态patch
监控进程
因为还需要响应UI,我们需要开一个子线程,pyqt5提供的解决方案是QThread pyqtSignal。子线程的伪代码:
while True:
监控进程是否存在,然后self.某signal.emit()向主线程发数据
time.sleep(1)
- 子线程不要更新任何ui,ui和业务逻辑要分开。
- pyqtSignal的int对应c语言的signed int,试出bug了才知道的。
可以用手工输入pid的方式来监控进程。但这里我们选择了另一种方式:先win32gui.FindWindow(wanted_window_classname, wanted_window_title)
获取hWnd
,然后去拿pid
。classname
和title
可以用visual studio附带的spy 工具来拿。
读取任意个字节
用ctypes.create_string_buffer()
创建字符串,然后kernel32.ReadProcessMemory
读取。读取到的value是bytes类型。
kernel32
,指kernel32.dll
。
kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
def readMemStr(hProcess, address, buffLen):
ReadProcessMemory = kernel32.ReadProcessMemory
p = ctypes.create_string_buffer(buffLen)
ReadProcessMemory(
int(hProcess),
address,
p,
buffLen,
None)
return p.value
写入任意个字节
网上大多数信息都是写入一个int的,怎样才能写入任意个字节呢?翻了很多乐色信息,终于翻到了参考链接1。用ctypes.c_char_p(bytes类型的数据)
,传入kernel32.WriteProcessMemory
。
# data: bytes
def writeMem(hProcess, address, data, buffLen):
writeProcess = kernel32.WriteProcessMemory
writeProcess(
int(hProcess),
address,
ctypes.c_char_p(data),
buffLen,
None)
return data
打包成exe
pyinstaller -F -w -i=assets/icon.ico main.py
-i
表示图标文件,图标只支持.ico
。
代码
different_flag_ui.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'different_flag_ui.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap("assets/icon.png"),
QtGui.QIcon.Normal,
QtGui.QIcon.Off)
MainWindow.setWindowIcon(icon)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.allowObstacle = QtWidgets.QCheckBox(self.centralwidget)
self.allowObstacle.setGeometry(QtCore.QRect(30, 120, 151, 41))
font = QtGui.QFont()
font.setPointSize(14)
self.allowObstacle.setFont(font)
self.allowObstacle.setObjectName("allowObstacle")
self.allowIllegalInput = QtWidgets.QCheckBox(self.centralwidget)
self.allowIllegalInput.setGeometry(QtCore.QRect(30, 190, 151, 41))
font = QtGui.QFont()
font.setPointSize(14)
self.allowIllegalInput.setFont(font)
self.allowIllegalInput.setObjectName("allowIllegalInput")
self.posLabel = QtWidgets.QLabel(self.centralwidget)
self.posLabel.setGeometry(QtCore.QRect(30, 320, 351, 71))
font = QtGui.QFont()
font.setPointSize(14)
self.posLabel.setFont(font)
self.posLabel.setObjectName("posLabel")
self.mazeTag = QtWidgets.QLabel(self.centralwidget)
self.mazeTag.setGeometry(QtCore.QRect(480, 90, 81, 31))
font = QtGui.QFont()
font.setPointSize(14)
self.mazeTag.setFont(font)
self.mazeTag.setAlignment(QtCore.Qt.AlignCenter)
self.mazeTag.setObjectName("mazeTag")
self.mazeLabel = QtWidgets.QLabel(self.centralwidget)
self.mazeLabel.setGeometry(QtCore.QRect(410, 140, 231, 101))
font = QtGui.QFont()
font.setPointSize(10)
self.mazeLabel.setFont(font)
self.mazeLabel.setAlignment(
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)
self.mazeLabel.setObjectName("mazeLabel")
self.xLineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.xLineEdit.setGeometry(QtCore.QRect(460, 330, 121, 20))
font = QtGui.QFont()
font.setPointSize(10)
self.xLineEdit.setFont(font)
self.xLineEdit.setMaxLength(19)
self.xLineEdit.setObjectName("xLineEdit")
self.yLineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.yLineEdit.setGeometry(QtCore.QRect(460, 380, 121, 20))
font = QtGui.QFont()
font.setPointSize(10)
self.yLineEdit.setFont(font)
self.yLineEdit.setMaxLength(19)
self.yLineEdit.setObjectName("yLineEdit")
self.modifyPosTag = QtWidgets.QLabel(self.centralwidget)
self.modifyPosTag.setGeometry(QtCore.QRect(450, 260, 141, 31))
font = QtGui.QFont()
font.setPointSize(14)
self.modifyPosTag.setFont(font)
self.modifyPosTag.setAlignment(QtCore.Qt.AlignCenter)
self.modifyPosTag.setObjectName("modifyPosTag")
self.processInfo = QtWidgets.QLabel(self.centralwidget)
self.processInfo.setGeometry(QtCore.QRect(30, 30, 641, 41))
font = QtGui.QFont()
font.setPointSize(12)
self.processInfo.setFont(font)
self.processInfo.setObjectName("processInfo")
self.modifyButton = QtWidgets.QPushButton(self.centralwidget)
self.modifyButton.setGeometry(QtCore.QRect(460, 430, 121, 23))
font = QtGui.QFont()
font.setPointSize(10)
self.modifyButton.setFont(font)
self.modifyButton.setObjectName("modifyButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "不一样的flag动态patch"))
self.allowObstacle.setText(_translate("MainWindow", "允许在障碍物上"))
self.allowIllegalInput.setText(_translate("MainWindow", "允许非法输入"))
self.posLabel.setText(_translate("MainWindow", "未检测到“不一样的flag.exe”进程"))
self.mazeTag.setText(_translate("MainWindow", "迷宫"))
self.mazeLabel.setText(
_translate(
"MainWindow",
"未检测到“不一样的flag.exe”进程"))
self.xLineEdit.setPlaceholderText(_translate("MainWindow", "人物x坐标"))
self.yLineEdit.setPlaceholderText(_translate("MainWindow", "人物y坐标"))
self.modifyPosTag.setText(_translate("MainWindow", "修改人物坐标"))
self.processInfo.setText(
_translate(
"MainWindow",
"未检测到“不一样的flag.exe”进程"))
self.modifyButton.setText(_translate("MainWindow", "确定"))
trainer.py
from win32api import OpenProcess
from win32con import PROCESS_ALL_ACCESS
from win32process import GetWindowThreadProcessId
import ctypes
kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
def getProcessHandle(hWnd):
# 获取窗口pid
hpid, pid = GetWindowThreadProcessId(hWnd)
# 获取进程句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
return hProcess, pid
def readMemVal(hProcess, address, buffLen):
ReadProcessMemory = kernel32.ReadProcessMemory
addr = ctypes.c_ulong()
ReadProcessMemory(
int(hProcess),
address,
ctypes.byref(addr),
buffLen,
None)
return addr.value
def readMemStr(hProcess, address, buffLen):
ReadProcessMemory = kernel32.ReadProcessMemory
p = ctypes.create_string_buffer(buffLen)
ReadProcessMemory(
int(hProcess),
address,
p,
buffLen,
None)
return p.value
def writeMemInt(hProcess, address, data):
writeProcessInt = kernel32.WriteProcessMemory
writeProcessInt(
int(hProcess),
address,
ctypes.byref(
ctypes.c_ulong(data)),
4,
None)
return data
# data: bytes
def writeMem(hProcess, address, data, buffLen):
writeProcess = kernel32.WriteProcessMemory
writeProcess(
int(hProcess),
address,
ctypes.c_char_p(data),
buffLen,
None)
return data
# jmp = 0xeb, jnz = 0x75, jz = 0x74
def setAllowObstacle(hProcess, isChecked):
# 判s[v1][v2] == '1'的跳转
address = 0x40146f
if isChecked:
# jmp short loc_40147D
writeMem(hProcess, address, b"\xeb\x0c", 2)
else:
# jnz short loc_40147D
writeMem(hProcess, address, b"\x75\x0c", 2)
val = readMemVal(hProcess, address, 3)
print(hex(val)) # 小端 75 0c c7
def setAllowIllegalInput(hProcess, isChecked):
# 判定输入是否合法:4013CF和4013DB
ad1, ad2 = 0x4013CF, 0x4013DB
if isChecked:
# jmp short loc_4013DF
writeMem(hProcess, ad1, b"\xeb\x0e", 2)
# jmp short loc_401400
writeMem(hProcess, ad2, b"\xeb\x23", 2)
else:
# jz short loc_4013DF
writeMem(hProcess, ad1, b"\x74\x0e", 2)
# jz short loc_401400
writeMem(hProcess, ad2, b"\x74\x23", 2)
val = readMemVal(hProcess, ad1, 3)
print(hex(val)) # 小端 74 0e eb
val = readMemVal(hProcess, ad2, 3)
print(hex(val)) # 小端 74 23 eb
def modifyPos(x, y, hProcess):
esp = 0x60FEB0
xAddr = esp 0x30
yAddr = esp 0x34
writeMemInt(hProcess, xAddr, x)
writeMemInt(hProcess, yAddr, y)
main.py
from win32gui import FindWindow
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtGui import QIntValidator
from PyQt5.QtCore import pyqtSignal, QThread
from different_flag_ui import Ui_MainWindow
from PyQt5 import QtCore
import sys
from trainer import *
LB, UB = -2147483648, 2147483646
wanted_window_classname = "ConsoleWindowClass"
wanted_window_title = r"不一样的flag.exe"
class MonitorThread(QThread):
get_proc_signal = pyqtSignal(int, int, int)
get_maze_signal = pyqtSignal(str)
get_pos_signal = pyqtSignal(int, int)
proc_exist_signal = pyqtSignal(bool)
def __init__(self):
super().__init__()
def run(self):
while True:
hWnd = FindWindow(wanted_window_classname, wanted_window_title)
if not hWnd:
self.get_proc_signal.emit(0, 0, 0)
self.get_maze_signal.emit("")
self.get_pos_signal.emit(UB 1, UB 1)
self.proc_exist_signal.emit(False)
else:
hProcess, pid = getProcessHandle(hWnd)
self.get_proc_signal.emit(hWnd, pid, hProcess)
# 读迷宫字符串
bs = readMemStr(hProcess, 0x402000, 26)
self.get_maze_signal.emit(bs.decode("utf-8", "ignore"))
# 读人物坐标
esp = 0x60FEB0
xAddr = esp 0x30
yAddr = esp 0x34
x = readMemVal(hProcess, xAddr, 4)
y = readMemVal(hProcess, yAddr, 4)
self.get_pos_signal.emit(x, y)
self.proc_exist_signal.emit(True)
time.sleep(1)
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.hProcess = 0
self.pid = 0
self.allowObstacle.stateChanged.connect(self.allowObstacleChange)
self.allowIllegalInput.stateChanged.connect(
self.allowIllegalInputChange)
self.modifyButton.clicked.connect(self.modifyPos)
intValidator1 = QIntValidator(LB, UB)
intValidator2 = QIntValidator(LB, UB)
self.xLineEdit.setValidator(intValidator1)
self.yLineEdit.setValidator(intValidator2)
self.monitor_thread = MonitorThread()
self.monitor_thread.get_proc_signal.connect(self.set_process_info)
self.monitor_thread.get_maze_signal.connect(self.set_maze_label)
self.monitor_thread.get_pos_signal.connect(self.set_pos_label)
self.monitor_thread.proc_exist_signal.connect(
self.set_ui_enabled_state)
self.monitor_thread.start()
def set_ui_enabled_state(self, fl):
self.allowObstacle.setEnabled(fl)
self.allowIllegalInput.setEnabled(fl)
self.modifyButton.setEnabled(fl)
if not fl:
self.allowObstacle.setChecked(False)
self.allowIllegalInput.setChecked(False)
def set_process_info(self, hWnd, pid, hProcess):
self.hProcess = hProcess
self.pid = pid
self.processInfo.setText(
"不一样的flag.exe "
("hWnd = %s,进程pid = %s" % (hex(hWnd), self.pid) if hWnd else
"not found"))
def set_maze_label(self, s):
if not s:
self.mazeLabel.setText("未检测到“不一样的flag.exe”进程")
return
maze_str = "\n".join([s[i * 5:i * 5 5] for i in range(5)])
self.mazeLabel.setText(maze_str)
def set_pos_label(self, x, y):
if x == UB 1:
self.posLabel.setText("未检测到“不一样的flag.exe”进程")
return
self.posLabel.setText("当前人物坐标:(%s,%s)" % (x, y))
def allowObstacleChange(self):
isChecked = self.allowObstacle.isChecked()
# 进程不存在
if not self.pid:
return
print("allowObstacle", isChecked, "pid = %s" % self.pid)
setAllowObstacle(self.hProcess, isChecked)
def allowIllegalInputChange(self):
isChecked = self.allowIllegalInput.isChecked()
# 进程不存在
if not self.pid:
return
print("allowIllegalInput", isChecked, "pid = %s" % self.pid)
setAllowIllegalInput(self.hProcess, isChecked)
def modifyPos(self):
try:
x = int(self.xLineEdit.text())
except BaseException:
x = 0
try:
y = int(self.yLineEdit.text())
except BaseException:
y = 0
modifyPos(x, y, self.hProcess)
if __name__ == "__main__":
# 本机分辨率较高,使用以下语句即可让按钮大小符合期望
QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
x = MainWindow()
x.show()
sys.exit(app.exec_())
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanejkh
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01