[原创]PyQt开发反编译工具学习记录篇
中秋假期转眼间过去两天了,这次没出去玩,就留在家里学习下PyQt的开发知识。这里仅记录一下学习的相关记录,留下存档,便于以后翻阅。主要的目的是编写一个反编译和回编译的工具,稍后可以慢慢随着需求补充上一些搜索和修改的功能,处理一些游戏的自动破解,类似MM和一些其他支付都有特征可参考,适合做自动化处理。
0x1:第一天,环境配置,简单测试代码
PyQt的简介
PyQt是一个创建GUI应用程序的工具包,它是Python编程语言和Qt库的成功融合,Qt库是目前最强大的库之一。PyQt是由Phil Thompson 开发,实现了一个Python模块集。它有超过300类,将近6000个函数和方法,它是一个多平台的工具包,可以运行在所有主要操作系统上,包括UNIX,Windows和Mac。 PyQt采用双许可证,开发人员可以选择GPL和商业许可。在此之前,GPL的版本只能用在Unix上,从PyQt的版本4开始,GPL许可证可用于所有支持的平台。
环境的配置
我们这里使用的Python版本为2.7.8 ,对应的PyQt版本为4.11.1,下面是这两个的官网:
https://www.python.org/downloads/
http://pyqt.sourceforge.net
下面我整理好放到百度网盘的:
http://pan.baidu.com/s/1dD1o5qT
两个软件选择32位的,可以运行在64位电脑环境下,win7/win8都已经测试没问题。
一些学习网站的整理:
http://www.riverbankcomputing.com
http://wiki.woodpecker.org.cn/moin/PyQt
http://www.commandprompt.com/community/pyqt/book1
http://wiki.python.org/moin/PyQt
http://code.google.com/p/pyqt-doc-cn/
第一个测试代码的编写
然后就开始第一个测试代码的编写:
import sys
'''这里我们进行必须的引用。基础的GUI控件位于QtGui模块中。'''
from PyQt4 import QtGui
'''每一个PyQT4应用必须创建一个应用对象。应用对象位于QtGui模块中。sys.argv参数是从命令行返回的一个参数列表。Python脚本可以从shell来运行。这是一种我们启动脚本的方法。'''
app = QtGui.QApplication(sys.argv)
'''QWidget控件是PyQT4中所有用户接口对象的基类。我们为QWidget提供了缺省的构造程序。缺省的构造程序没有父类。一个没有父类的控件叫做窗口。'''
widget = QtGui.QWidget()
'''resize()方法可以调整控件的尺寸。这里是250px宽,150px高。'''
widget.resize(250, 150)
'''这里我们指定我们窗口的标题。标题在标题栏里显示。'''
widget.setWindowTitle('Love Guige,Love Python')
'''show() 方法将控件显示在屏幕上。'''
widget.show()
'''sys.exit()方法可以确保一个干净的退出。环境参数会显示出应用程序是何种方式结束的'''
sys.exit(app.exec_())效果图如下:

一个简单的界面程序就完成了,那么剩下的我们接下来继续探索添加两个Button,并且让其执行一些功能。
0x2:第二天,添加Button,执行一些功能
思考过程
我们想要做反编译的动作,首先是要准备反编译所需要的相关东西。所以我们下载apktool.jar来执行相关动作,我这里采用的是一个朋友编译的1.5版本的apktool,修复了一些bug,相关网址为:
http://bbs.chinaunix.net/thread-4096302-1-1.html
使用apktool.jar的命令即可,我们可以用Python来执行cmd的命令的,所以这个是可以实现的。
这个方法处理好以后,就是需要跟按钮点击事件关联起来即可。那么下面我们就分开实现这个。
反编译方法的实现
我们来写一个简单的脚本测试反编译方法的实现:
import os
#####设置apk路径,我们这里先使用固定路径#####
apk_path=os.getcwd()+"\\aa.apk"
def apkD(apk_path):
###打印一下路径,看看是否正确#####
print(apk_path)
###apktool的命令使用#####
apkToD='java -jar apktool.jar d '+apk_path
###查看一下命令,是否正确,有写错###
print(apkToD)
###调用os的system命令来执行cmd的相关命令###
os.system(apkToD)
apkD(apk_path)代码中已经写清楚了详细的注释,这里我们可以看一下截图,证明我们的方法是有效的:

按钮点击事件的实现:
这里采用重写Button,然后实现其鼠标点击事件:
################################## 自定义按钮类 ####################################
class MyButton(QtGui.QToolButton):
'''自定义按钮类'''
############################## 构造、析构函数 ##################################
def __init__(self,parent=None):
'''构造函数'''
# 调用父类构造函数
super(MyButton,self).__init__(parent)
# 设置按钮尺寸
self.setFixedSize(QtCore.QSize(800,120))
# 设置按钮背景颜色
self.setStyleSheet('''background-color:red;''')
################################ 事件 #########################################
def mousePressEvent(self,event):
'''鼠标按下事件'''
# 判断是否为鼠标左键按下
if event.button() == QtCore.Qt.LeftButton:
# 发射点击信号
self.clicked.emit(True)
# 传递至父窗口响应鼠标按下事件
self.parent().mousePressEvent(event)效果图如下:

将所有代码整合起来
import sys
import os
from PyQt4 import QtGui
from PyQt4 import QtCore
###apk的位置,当前类下,我们这里先放到当前文件所在目录###
apk_path=os.getcwd()+"\\aa.apk"
################################# 自定义窗口类 #####################################
class MyWindow(QtGui.QWidget):
'''自定义窗口类'''
############################# 构造、析构函数 #################################
def __init__(self,parent=None):
'''构造函数'''
# 调用父类构造函数
super(MyWindow,self).__init__(parent)
# 设置窗口固定尺寸
self.setFixedSize(QtCore.QSize(800,600))
# 创建主控件
bodyWidget = QtGui.QWidget(self)
# 创建主布局
mainLayout = QtGui.QVBoxLayout(bodyWidget)
# 创建自定义按钮
button = MyButton(self)
# 设置文本内容
button.setText("Apk_Decode")
# 添加控件
mainLayout.addWidget(button)
# 设置按钮点击连接槽函数
button.clicked.connect(self.OnClick)
############################### 命令 ########################################
def OnClick(self):
'''响应点击'''
QtGui.QMessageBox.about(self,"apk_d","apk is decoding")
############################### 反编译方法 ########################################
def apk_decode(self,path):
print(path)
apkToD='java -jar apktool.jar d '+path
print(apkToD)
os.system(apkToD)
################################ 事件 ########################################
def mousePressEvent(self,event):
'''鼠标按下事件'''
# 判断是否为鼠标左键按下
if event.button() == QtCore.Qt.LeftButton:
# 设置窗口背景颜色
self.setStyleSheet('''''')
self.apk_decode(apk_path)
################################## 自定义按钮类 ####################################
class MyButton(QtGui.QToolButton):
'''自定义按钮类'''
############################## 构造、析构函数 ##################################
def __init__(self,parent=None):
'''构造函数'''
# 调用父类构造函数
super(MyButton,self).__init__(parent)
# 设置按钮尺寸
self.setFixedSize(QtCore.QSize(800,120))
# 设置按钮背景颜色
self.setStyleSheet('''background-color:red;''')
################################ 事件 #########################################
def mousePressEvent(self,event):
'''鼠标按下事件'''
# 判断是否为鼠标左键按下
if event.button() == QtCore.Qt.LeftButton:
# 发射点击信号
self.clicked.emit(True)
# 传递至父窗口响应鼠标按下事件
self.parent().mousePressEvent(event)
################################## 主函数 ##########################################
if __name__ == "__main__":
'''主函数'''
# 声明变量
app = QtGui.QApplication(sys.argv)
# 创建窗口
window = MyWindow()
# 设置窗口显示
window.show()
#应用程序事件循环
sys.exit(app.exec_())代码中的注释比较详细,这里不多叙述了。
效果图:

总结
上面我们已经可以简单实现按钮的反编译了,毕竟没有白白浪费时间,证明我们是可以做一个反编译工具的,那么平时操作中,我们肯定不会是在代码中写死apk的路径的。常用的方法是加一个选择apk路径的window,然后选定apk,这样来确定apk所在的路径的。那么下一篇我们就一起来探索如何实现这个功能。
中秋第二天,下午就在探索中度过了。。。
0x3:第二天晚上,处理window
寻找相关api代码
QFileDialog类提供了类似的其他静态函数,如下表,通过这些函数,用户可以方便地定制自己的文件对话框。
静态函数 说明
getOpenFileName 获得用户选择的文件名
getSaveFileName 获得用户保存的文件名
getExistingDirectory 获得用户选择的已存在的目录名
getOpenFileNames 获得用户选择的文件名列表
按钮事件的总结
按钮的点击事件,经过一下午的分析,果断抛弃上一课的总结使用的鼠标点击事件,而直接对按钮的响应事件作出处理。也可以说通过今天下午的学习,比上午进步了一点点。。。
self.connect(decode,SIGNAL('clicked()'),self.apkD)
然后就是下午对布局文件的继续熟悉,感觉写布局代码越来越像写安卓代码的布局了,好了,下面我贴上全部代码,里面已经写了比较详细的注释了,相信有编码基础的读者是可以看懂的:
# -*- coding: utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import os
class GridLayout2(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setWindowTitle('Android_APK')
###第一行
self.apk_path = QLabel('APk Path')
self.apk_path_edit = QLineEdit()
###第二行,三个按钮,这里第三个按钮的点击事件还没写
file_search=QPushButton("Search_File")
decode = QPushButton("Apk_Decode")
build = QPushButton("Apk_Build")
###寻找文件按钮的点击事件
self.connect(file_search,SIGNAL('clicked()'),self.openFile)
###反编译按钮的点击事件
self.connect(decode,SIGNAL('clicked()'),self.apkD)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.apk_path, 1, 0)
grid.addWidget(self.apk_path_edit, 1, 1)
grid.addWidget(file_search,2,0)
grid.addWidget(decode, 2,1)
grid.addWidget(build, 2,2)
self.setLayout(grid)
self.resize(350, 300)
def openFile(self):
##调用QfileDialog的相关方法来打开文件选择框
s=QFileDialog.getOpenFileName(self,"Open file dialog","/","Python files(*.apk)")
##定义全局变量,用于接收需要反编译的apk的路径
global APK_STR
APK_STR=str(s)
self.apk_path_edit.setText(APK_STR)
def apkD(self):
###上面openFIle方法中定义的全局变量,打印一下路径,看看是否正确#####
print(APK_STR)
###apktool的命令使用#####
apkToD='java -jar apktool.jar d '+APK_STR
###调用os的system命令来执行cmd的相关命令###
os.system(apkToD)
app = QApplication(sys.argv)
qb = GridLayout2()
qb.show()
sys.exit(app.exec_())效果截图
Search_file的点击事件:

Apk_Decode的点击事件:

总结
上面的大家可以看到,我们已经可以实现两个功能了,即打开window找文件,然后进行反编译,但是在反编译完成以后,我们需要对里面的smali文件进行操作,然后再回编译文件,签名,才能对手机进行安装,所以下一篇文章,我们就主要是编写回编译的方法以及对apk签名的方法。
0x4:第三天的最终成果:
为了更方便的使用我们自己编写的工具,所以我们对于第三篇文章中的search_file button来进行处理,我们使用拖拽文件到QLineEdit控件上面来显示文件路径,而不用打开window来选择了,这样对我们的操作来说更加方便一点。
拖拽事件的处理
拖拽事件,经过一晚上的查询以及翻看Java已经写好的工具,终于找到dragEnterEvent和dropEvent两个事件。关于这两个事件的详细介绍,自行google搜索一下即可。
要想使用这个事件,我们需要重写一下QLineEdit控件,并且在其init方法里面设置setAcceptDrops为True来接收拖拽事件。
所以现在的问题就是我们如何来获取得到拖入文件的路径呢?
经过查询mimeData有一些方法可以参考,经过测试,urls()属性还是比较贴近我们所需的,但是还需要对其进行处理得到文件路径。
详细代码如下:
def dropEvent(self, event):
###获取拖放过来的文件的路径
st=str(event.mimeData().urls())
print(st)
###这里没发现好的api,所以用mimeData()的urls属性了,text()不好用,所以下面就是字符串截取,来还原路径
a='///'
b=')]'
num2=st.index(b)-1
num=st.index(a)+3
st=st[num:num2]
##这里定义全局变量,拖入文件或者文件夹的路径
global APK_STR
APK_STR=st
self.setText(st)重新做一下布局文件
详细代码如下,有详细注释,这里不再多说:
QWidget.__init__(self, parent)
self.setWindowTitle('Android_APK')
###第一行
self.apk_decode=QPushButton("Apk_Decode")
self.apk_path_edit = MyEditText(self)
###第二行,三个按钮,这里第三个按钮的点击事件还没写
self.apk_build=QPushButton("Apk_Build")
self.apk_build_edit = MyEditText()
###第三行
self.sign = QPushButton("Apk_Sign")
self.apk_sign_edit = MyEditText()
##第四行
self.shootcut=QPushButton("Shoot_Cut")
self.shootcut_text=QLabel("please keep moile with usb")
###布局文件的编写,四行两列
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.apk_decode, 1, 0)
grid.addWidget(self.apk_path_edit, 1, 1)
grid.addWidget(self.apk_build, 2, 0)
grid.addWidget(self.apk_build_edit, 2, 1)
grid.addWidget(self.sign,3,0)
grid.addWidget(self.apk_sign_edit, 3,1)
grid.addWidget(self.shootcut,4,0)
grid.addWidget(self.shootcut_text, 4,1)
###设置窗口的高宽,将gridview放入大布局中
self.setLayout(grid)
self.resize(450, 300)效果图如下:

完整的代码
下面附上完整的代码,有兴趣的朋友,直接测试即可。记得先配置好pyqt相关开发环境。
# -*- coding: utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import os
import time
class MainLayout(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setWindowTitle('Android_APK')
###第一行
self.apk_decode=QPushButton("Apk_Decode")
self.apk_path_edit = MyEditText(self)
###第二行,三个按钮,这里第三个按钮的点击事件还没写
self.apk_build=QPushButton("Apk_Build")
self.apk_build_edit = MyEditText()
###第三行
self.sign = QPushButton("Apk_Sign")
self.apk_sign_edit = MyEditText()
##第四行
self.shootcut=QPushButton("Shoot_Cut")
self.shootcut_text=QLabel("please keep moile with usb")
###反编译按钮的点击事件
self.connect(self.apk_decode,SIGNAL('clicked()'),self.apkD)
###回编译按钮的点击事件
self.connect(self.apk_build,SIGNAL('clicked()'),self.apkB)
###签名按钮的点击事件
self.connect(self.sign,SIGNAL('clicked()'),self.apkS)
###截屏按钮的点击事件
self.connect(self.shootcut,SIGNAL('clicked()'),self.shootCut)
###布局文件的编写,四行两列
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(self.apk_decode, 1, 0)
grid.addWidget(self.apk_path_edit, 1, 1)
grid.addWidget(self.apk_build, 2, 0)
grid.addWidget(self.apk_build_edit, 2, 1)
grid.addWidget(self.sign,3,0)
grid.addWidget(self.apk_sign_edit, 3,1)
grid.addWidget(self.shootcut,4,0)
grid.addWidget(self.shootcut_text, 4,1)
###设置窗口的高宽,将gridview放入大布局中
self.setLayout(grid)
self.resize(450, 300)
''''def openFile(self):
##调用QfileDialog的相关方法来打开文件选择框
s=QFileDialog.getOpenFileName(self,"Open file dialog","/","Apk files(*.apk)")
##定义全局变量,用于接收需要反编译的apk的路径
global APK_STR
APK_STR=str(s)
self.apk_path_edit.setText(APK_STR)'''
def apkD(self):
###上面openFIle方法中定义的全局变量,打印一下路径,看看是否正确#####
print(APK_STR)
###apktool的命令使用#####
apkToD='java -jar apktool.jar d '+APK_STR
###调用os的system命令来执行cmd的相关命令###
os.system(apkToD)
def apkB(self):
###上面openFIle方法中定义的全局变量,打印一下路径,看看是否正确#####
print(APK_STR)
###apktool的命令使用#####
apkToD='java -jar apktool.jar b '+APK_STR
###调用os的system命令来执行cmd的相关命令###
os.system(apkToD)
def apkS(self):
apkSign='java -jar signapk.jar testkey.x509.pem testkey.pk8 '+APK_STR+' Signed_apk.apk'
print apkSign
###调用os的system命令来执行cmd的相关命令###
os.system(apkSign)
def shootCut(self):
pre_com='adb wait-for-device'
os.system(pre_com)
###将截图保存到local下
fir_com='adb shell screencap -p /data/local/tmp/tmp.png'
print (fir_com)
os.system(fir_com)
###当前时间,精确到秒,来保存图片命名
timestamp = time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(time.time()))
path=os.getcwd()
###将其导出来
sec_com='adb pull /data/local/tmp/tmp.png '+path+'\\'+timestamp+'.png'
print(sec_com)
os.system(sec_com)
thi_com='adb shell rm /data/local/tmp/tmp.png'
os.system(thi_com)
class MyEditText(QLineEdit):
def __init__(self,parent=None):
super(MyEditText,self).__init__(parent)
#这里在init初始化,接受拖入事件
self.setAcceptDrops(True)
###重写两个拖入文件的操作方法
def dragEnterEvent(self,event):
event.accept()
def dropEvent(self, event):
###获取拖放过来的文件的路径
st=str(event.mimeData().urls())
print(st)
###这里没发现好的api,所以用mimeData()的urls属性了,text()不好用,所以下面就是字符串截取,来还原路径
a='///'
b=')]'
num2=st.index(b)-1
num=st.index(a)+3
st=st[num:num2]
##这里定义全局变量,拖入文件或者文件夹的路径
global APK_STR
APK_STR=st
self.setText(st)
###程序的主入口
if __name__ == "__main__":
app = QApplication(sys.argv)
qb = MainLayout()
qb.show()
sys.exit(app.exec_())效果图,前面几篇基本都有了,这里不再贴出了。
注意:
1.反编译采用的是apktool.jar的命令,所以得将其放到.py文件对应目录
2.签名采用google的公钥来过的,所以其文件也必须在同目录下
3.截屏动作,得保证手机正常连接usb,adb进程不被占用,所以如果读者有自己编译的adb是最好的,若此功能不好用,直接做个cmd脚本将上述方法中的命令放入即可
4.这四篇文章,仅记录一下PyQt的相关学习过程,读者可以随意对其进行扩展开发,做一个IDE工具集合搜索,修改,保存以及一些自动化工具
结束语:
虽然中秋几天假期没有出去玩,不过从头开始学习PyQt还是有一些进步的。代码方面编写肯定是有很多疏漏,其对应功能方面也可以做更多优化,有兴趣的朋友,请继续加油。
仅以一句话勉励自己:
勤能补拙是良训,一分辛苦一分才。
本文相关附件之类的,我就上传到百度网盘了:
http://pan.baidu.com/s/1o6sONoQ
现在是的文档中贴代码看的肯定不方便,不知道待会儿发到论坛上如何,所以先发上博客地址,博客上面是四篇分开叙述,本篇文章是全部简单整理成的,所以再具体一点的思考过程可以参考博客:
http://blog.csdn.net/guiguzi1110
PyQt开发反编译工具中秋学习记录篇.pdf
