• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Windows逆向之Windows进程动态patch入门 与 pyqt5搭建GUI

武飞扬头像
juejin
帮助68

目标

希望学习对Windows进程的动态patch,我们选择的exe是buuoj的“不一样的flag”。这题是迷宫题的hello world,没有加壳,没有任何代码混淆,且可以把它当成一个超小型的游戏,有助于提升信心

为了直观,以GUI的形式提供对目标进程的动态patch功能:

  1. 未检测到目标进程,相关的输入控件不可使用。
  2. 检测到目标进程时,读取目标进程内存中的数据,显示目标进程信息、迷宫和当前人物的坐标。
  3. 检测到目标进程时,提供“允许在障碍物上”、“允许不合法输入”和修改人物坐标功能(这些需求是根据其反汇编代码确定的)。

效果图:

检测到进程:

1-检测到进程效果图.jpg

未检测到进程:

2-未检测到进程效果图.jpg

依赖

  • 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,然后去拿pidclassnametitle可以用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
系列文章
更多 icon
同类精品
更多 icon
继续加载