九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

PyQt的学习(1) 入门


PyQt的学习(1) 入门

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

动机:

刚学习完Python,借鉴以前学习C++时的经验,当时用MFC巩固了C++的学习,并且可以做出实际的程序,给了我继续学习的动力,学习Qt也是出于类似的目的。

为什么我要写,为了学习。不仅仅为了将来有个可以用的,好用的跨平台GUI库,也为了用Python快速开发时也能有个GUI库,但是又不喜欢TK,那么,综合考虑,Qt也就是不错的考虑了。

 

综述

这些文章基本遵循《C++ GUI Qt 4编程》第2版(C++ GUI Programming with Qt4, Second Edition)的流程,文字可能会很少,除非自己想出的例子,不然例子就是书中C++例子的Python版本,基本上,可以以此书为蓝本对照这些文章中的例子来看。

虽然用途不算太大,但是考虑到PyQt的好教材是如此的稀少,这样也不算完全没有价值吧,毕竟还有只明白Python不懂C++的人。

这里有一本PyQt的书《GUI Programming with Python: Qt Edition》,但是老到基本上是属于Qt2时代的东西了。

至于PyQt 嘛,你不会不知道到google上搜索一下,取得其下载地址吧?安装方式如此简单,点击下一步以后,就可以在Python中通过import PyQt4来使用这个库了。

 

HelloWorld

自从K&R;后,好像所有的程序语言讲解都是以HelloWorld开始,我们也不免俗吧,我们从HelloQT开始。并且,这个例子也可以作为一个测试程序,测试一下看看安装PyQt是否成功。

HelloQt.pyw

1 import sys
2 from PyQt4 import QtGui
3
4
5 app = QtGui.QApplication(sys.argv)
6 label = QtGui.QLabel("Hello Qt!")
7 label.show()
8
9 sys.exit(app.exec_())

 

这样,就是一个现实"Hello Qt"的窗口,利用了QLabel部件。--说明一下,Qt中将MFC中常常称作控件(control)的东西称作部件(Widget),其实一回事。

这里也可以看出Qt的足够简洁,我很欣赏,而其对象使用的风格和方式也是比较好的,不然,尝试用VS生成一个类似的MFC程序试试?^^

其实这里还不足以见证Qt的强大,看下面的例子,QLabel竟然至此HTML样式。。。。。-_-!

MoreHelloQt.pyw

1 import sys
2 from PyQt4 import QtGui
3
4
5 app = QtGui.QApplication(sys.argv)
6 label = QtGui.QLabel("

Hello QT!

") #这里我没有办法让CSDN不将其解释为HTML。。。所以,参考界面的源代码看看是什么吧
7 label.show()
8
9 sys.exit(app.exec_())

 

强大吧:)

 

Qt(C++) VS PyQt

这里,顺便比较一下PyQt与普通Qt(C++)生成程序的区别。一般而言,两者速度没有可比性,但是,速度在这里不是主要问题,原因在于PyQt的核心也就是Qt库,那是用C++写的,这样,一般而言不会占用太多时间的逻辑代码速度慢点,不会成为瓶颈,使得PyQt的程序可以跑得足够的快。但是,使用方式上,却没有失去Python的优雅语法,快速开发的能力,也结合了Qt的强大,呵呵,广告用语。。。。。。。。。。。来点实在的。


左边的是用C++开发出来的Qt程序,右边是PyQt开发出来的程序,由于都是使用了同一个库,看不出两者的区别。基本上,多懂了一种语言,就多了一种选择。。。比如说这个时候的Python。

 

建立连接

quit.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4 app = QtGui.QApplication(sys.argv)
 5
 6 quit = QtGui.QPushButton("Quit")
 7
 8 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
 9                        app, QtCore.SLOT("quit()"))
10
11 quit.show()
12 sys.exit(app.exec_())

 

这里展示了Qt的信槽模式,有点怪异的是,在Python中,信号,槽,都是用字符串来表示-_-!这点似乎有点奇怪。

我还不懂Qt的原理,也没有看过Qt的源代码,但是总是感觉这里奇怪,于是我翻看了一下Qt的SIGNAL,SLOT宏,于是一切也就没有那么奇怪了。

# define SLOT(a) qFlagLocation("1"#a QLOCATION)

# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)

本来,他们就是以字符串来表示的。。。。。。。。我原本还以为在Qt中这些都是通过回调函数的形式出现的呢。。。。。。。汗-_-!真那样,与一般的WxWidget原理有啥不同啊。。。呵呵,看来,Qt的原理还得好好理解理解。其实就现在的信息看,无非也就是Observer模式的一种扩展,再难也难不到哪儿去。

 

在PyQt的安装包中,有个tutorial,展示了更复杂一点的button使用方法,可以参考参考

morequit.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4
 5 app = QtGui.QApplication(sys.argv)
 6
 7 quit = QtGui.QPushButton("Quit")
 8 quit.resize(75, 30)
 9 quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
10
11 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
12                        app, QtCore.SLOT("quit()"))
13
14 quit.show()
15 sys.exit(app.exec_())

 

分别是设置Button的大小和按钮文字的字体和样式,这个阶段就不深抠细节了,了解概念和Qt大概的风格就好。

 

窗口部件的布局

用过MFC的人都知道在MFC中创建一个随窗口动态改变控件大小的程序的困难。。。。。。需要每次OnMove,OnSize的时候去重新计算控件的大小,为了保持布局合理,最好还得将所有控件的位置用百分比来计算,痛苦不言而喻,可以参考我写的正则表达式测试程序0.3版(要么就是更老的版本)。但是在Qt中好像就要简单的比较多。看看示例:

layout.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4 app = QtGui.QApplication(sys.argv)
 5
 6 window = QtGui.QWidget()
 7
 8 spinBox = QtGui.QSpinBox()
 9 slider = QtGui.QSlider(QtCore.Qt.Horizontal)
10 spinBox.setRange(0, 130)
11 slider.setRange(0, 130)
12
13 QtCore.QObject.connect(spinBox, QtCore.SIGNAL("valueChanged(int)"),
14                        slider, QtCore.SLOT("setValue(int)"))
15 QtCore.QObject.connect(slider, QtCore.SIGNAL("valueChanged(int)"),
16                        spinBox, QtCore.SLOT("setValue(int)"))
17 spinBox.setValue(35)
18
19 layout = QtGui.QHBoxLayout()
20 layout.addWidget(spinBox)
21 layout.addWidget(slider)
22 window.setLayout(layout)
23
24 window.show()
25 sys.exit(app.exec_())

 

行了,基本上PyQt的程序是怎么样子的,大概有个了解了:),下面就开始慢慢来了。我也需要睡觉去了。

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

通过纯静态分析来还原算法,获取《加密与解密》第2章的TraceMe的注册机


通过纯静态分析来还原算法,获取《加密与解密》第2章的TraceMe的注册机 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 
 

其实原书有源代码,并且也介绍了一种通过内存断点来获取注册码的方法。但是这里我是为了检验今天看静态分析这一章的成果,纯粹靠IDA Pro静态分析不带调试信息Release版本,也不动态调试的方法来分析算法。并还原算法,写出注册机。这里并没有鼓励或提倡以此方式来分析程序的意思。呵呵,书中好像也鼓励大家试试写出一个注册机来,作为购买了正版书籍的读者,自然要响应作者号召了。

另外,我虽然最近老是发一些奇怪的文章,但是我只是个C++程序员,是以开发游戏为生的,最近研究这些东西并不是准备将来以此牟利,仅仅是为了学习,最近的工作是反外挂,所以才学习这些东西。这方面我是纯粹的新手,发点文章属于记录学习过程和个人爱好,有不对或者稚嫩的地方请高手指教。

这里仅仅只看我注释的片段,全部源代码即完整程序请去下载《加密与解密》(第3版)的配套光盘第2章部分,或者购买原书。

关键函数如下:调用环境其实可知lpString1为用户输入的key,lpString2为用户输入的用户名,aiLen为长度

.text:00401340 ; int __cdecl KeyGenAndCompare(LPCSTR lpString1, LPSTR lpString2, int aiLen)

.text:00401340 KeyGenAndCompare proc near ; CODE XREF: DialogFunc+115p

.text:00401340

.text:00401340 lpString1 = dword ptr 4

.text:00401340 lpString2 = dword ptr 8

.text:00401340 aiLen = dword ptr 0Ch

.text:00401340

.text:00401340 000 push ebp

.text:00401341 004 mov ebp, [esp+4+lpString2]

.text:00401345 004 push esi

.text:00401346 008 push edi

.text:00401347 00C mov edi, [esp+0Ch+aiLen] ; 总是通过esp来操作,有点混乱,其实就是将长度保存到edi

.text:0040134B 00C mov ecx, 3

.text:00401350 00C xor esi, esi ; 清空esi

.text:00401352 00C xor eax, eax ; 清空eax

.text:00401354 00C cmp edi, ecx ; Compare Two Operands

.text:00401356 00C jle short loc_401379 ; 判断当长度小于等于3时,不进行计算了,因为肯定是错的

.text:00401358 00C push ebx

.text:00401359

.text:00401359 KeyGenLoop: ; CODE XREF: KeyGenAndCompare+36j

.text:00401359 010 cmp eax, 7 ; three below: if ( eax > 7 ) eax = 0;

.text:0040135C 010 jle short loc_401360 ; Jump if Less or Equal (ZF=1 | SF!=OF)

.text:0040135E 010 xor eax, eax ; Logical Exclusive OR

.text:00401360

.text:00401360 loc_401360: ; CODE XREF: KeyGenAndCompare+1Cj

.text:00401360 010 xor edx, edx ; Logical Exclusive OR

.text:00401362 010 xor ebx, ebx ; Logical Exclusive OR

.text:00401364 010 mov dl, [ecx+ebp] ; 这里可以知道是从第3个元素才开始比较的

.text:00401367 010 mov bl, gacCode[eax] ; 这里用eax从0增长,等于7时清零来循环遍历一个数组

.text:0040136D 010 imul edx, ebx ; Signed Multiply

.text:00401370 010 add esi, edx ; Add

.text:00401372 010 inc ecx ; Increment by 1

.text:00401373 010 inc eax ; Increment by 1

.text:00401374 010 cmp ecx, edi ; Compare Two Operands

.text:00401376 010 jl short KeyGenLoop ; Jump if Less (SF!=OF)

.text:00401378 010 pop ebx

.text:00401379

.text:00401379 loc_401379: ; CODE XREF: KeyGenAndCompare+16j

.text:00401379 00C push esi

.text:0040137A 010 push offset aLd ; "%ld"

.text:0040137F 014 push ebp ; LPSTR

.text:00401380 018 call ds:wsprintfA ; Indirect Call Near Procedure

.text:00401386 018 mov eax, [esp+18h+lpString1]

.text:0040138A 018 add esp, 0Ch ; Add

.text:0040138D 00C push ebp ; lpString2

.text:0040138E 010 push eax ; lpString1

.text:0040138F 014 call ds:lstrcmpA ; Indirect Call Near Procedure

.text:00401395 00C neg eax ; Two's Complement Negation

.text:00401397 00C sbb eax, eax ; Integer Subtraction with Borrow

.text:00401399 00C pop edi

.text:0040139A 008 pop esi

.text:0040139B 004 inc eax ; Increment by 1

.text:0040139C 004 pop ebp

.text:0040139D 000 retn ; Return Near from Procedure

.text:0040139D KeyGenAndCompare endp

 

 

还原的算法源代码如下:

 1 #include
 2 #include
 3 int gacCode[] = {0x0c, 0x0A, 0x13, 0x9, 0x0c, 0x0b, 0x0a, 0x08};
 4
 5 int __cdecl KeyGen(char* lpName, int aiLen)
 6 {
 7     int liTemp = 0; // means esi
 8     int i = 3; //just because using VC6,know 3 from ecx == 3
 9     int j = 0;
10     for(; i
11     {
12         if(j > 7)
13         {
14             j = 0;
15         }
16         liTemp += lpName[i] * gacCode[j];
17     }
18
19     return liTemp;
20 }
21
22
23 int main()
24 {
25     char lszName[] = "abcdefg";
26     int liKey = KeyGen(lszName, strlen(lszName));
27
28     printf("%i/n",liKey);
29     return 0;
30 }
31

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

程序员手中的利器(2)--文本编辑工具


程序员手中的利器(2)--文本编辑工具

write by 九天雁翎(JTianLing) -- www.jtianling.com

 
 

不管有多少人告诉过你vim简直就不是设计给人用的,不管你曾经抱着怀疑的态度试过多少次并且放弃,我还是得说,vim绝对值得花时间去学习!并且,按某些人的原话,当你真的熟悉了vim的思维后,没有vim你甚至都觉得完全不自在!的确是这样!

首先得说一些其他理由,作为程序员,不说做的最多的,但是很经常需要做的可能就是输入代码了,输入代码时一个好的编辑器是非常需要的!这是节省你生命的最佳方式。

并且,Vim还有它随处可用的特性,不仅Windows下有GVim可用,X下也有GVim可用,并且就算是一个Linux服务器,没有安装图形界面,一样有vim可以用,哪怕它是最小安装,你起码都能有个vi可以用!这些都不是EditPlus,UE等工具可比的。还有更夸张的事情!因为Vim是如此的好用,导致没有Vim很多程序员都会觉得不自然,活不下去!所以,很多程序员都试图将Vim变得在任何时候都可以用!比如Visual Studio中有VimEmu插件,Eclipse中有VimPlugin和viPlugin,使得你不脱离你熟悉的IDE开发环境,你一样可以使用Vim。一次学习,随时随地的使用到其强大的功能,除了Vim我不知道还有哪个工具可以!最后Vim的语法着色支持的语言很多,也是的你在进行各种语言的时候都可以依赖它,比如我无论写lua,bash,python,C/C++的时候,我都只是使用vim。

至于Vim的模式编辑方式,和众多好用的快捷键,我这里就不想多讲了,这些讲多了也没有用,只能等学习的人自己去熟悉去体会,我只想说,学习Vim投入的时间将远远少于它为你节省的生命。

很多人对工具很不屑,总是说无论什么工具都能写好的代码,这里我得说,我从来不认为这些工具能够使你写的代码的更好,但是,实实在在的节省的是打字时间,也就是你的生命。学习这些不是为了炫耀什么,仅仅是不想把时间都浪费在打字上面,剩余的时间我可以用来想怎么把代码写好..........

这里引用

http://www.wanglianghome.org/blog/2006/04/emacs-show-motivation.html

一文中的一个观点,虽然此文是支持Emacs的,但是动机是一致的,而且文中对于编辑器应该有的功能的分析我也是比较赞同。但是因为我并不是纯GNU环境的程序员,所以一直没有需要转到Emacs的使用上去。文中提到:

"记得当初关于"软件蓝领"的争论焦点就是写程序是体力活还是脑力活,是否仅仅等于敲敲键盘。虽然到头来双方仍是自说自话,但似乎都同意"敲键盘是体力活"。"

既然如此,为什么我们不用一个好的工具来使这个活更轻松一些呢?

既然谈到了Emacs。。。。。m我想说说两个有意思的事情,这也应该属于我平时比较感兴趣的程序员文化。

  1. 有人说:世界上的程序员分三种,一种使用Emacs,一种使用vim,剩余的是其它。
  2. Emacs使用者说Vim的模式导致编辑的时候都不知道自己在什么模式。Vim使用者说,Emacs的组合快捷键如此复杂,以至于使用者都希望能有个脚踏板。。。。。

其实,一旦在讲Vim的时候谈到了Emacs都会让我觉得很有意思,有人说这是一个伴随着计算机发展的圣战。。。。。。

可以参看:

http://blog.csdn.net/oyd/archive/2007/02/16/1511128.aspx

这里面也有一个很有意思的对白:

Greg Menke发出了第一个置顶帖。使用vi?那该多么麻烦呀?他把自己的步骤贴了出来:
1.获得一条任意品种的鱼,长度大于12英寸就行
2.冰冻一晚上
3.把它放到电脑前,运行vi
4.抓住鱼的尾巴不停的往你头上砸,直到你决定用EMACS。因为鱼鳞在你周围飞溅并且你的头开始痛

Menke,当然,他是在暗示,顽固的不仅是vi,vi的用户更甚,他们顽固到拒绝承认他们虐待自己,就像拿冰冻的鱼砸自己的头一样。

没过多久,vi的拥护者们开始回击,其中一个建议Menke把这一行加到他的步骤中去:在第1步之前键入emacs,这样在第4步结束时emacs才可能加载完成。

他是在说EMACS编辑器慢吗?

最后,Emacs是一个附带有文本编辑功能的操作系统的话也很出名:)

Ultra Edit(UE)也是个不错的文本编辑工具,也有列编辑功能(vim也有),也有16进制编辑功能,一开始我都是用UE来解决很多问题。但是当我习惯了vim以后,UE很多时候仅仅作为一个功能强大点的记事本了。

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

多鼠标的资料少到英国人用Google翻译我的文章看-_-!

多鼠标是博客中发表文章中大家最感兴趣并且联系我最多的一个内容,因为国内的资料相对较少,并且当时的确为了写论文花了一定的时间进行研究,(一年前的事情了,其实现在忘的差不多了)但是今天发现一个很有意思的事情(见下表)。。。。。。呵呵,英国有个兄弟缺少多鼠标的资料。。。。甚至利用google的翻译功能,将我的文章翻译成E文观看。。。。可怜的人哪。。。。不过这方面国内的资料少还可以理解,国外的资料其实还是有一些的,至于这样吗?-_-!呵呵,感觉比较有意思,我们痛苦的读者E文的资料的时候。。。。还有同样痛苦的兄弟啊。。。。

2009-6-12 13:39:36 ( IP 90.197.196.34 )

基本信息

这是此用户第 1 次访问九天雁翎博客, [ 查看他浏览的所有网页 ]
英国, 英语 - 美国 (en-us), 位于1 时区

客户端信息

Windows XP, MSIE 7.0, 1280×800, 232
, 未装 Alexa 工具条

来路

http://translate.google.com/translate?hl=en&sl;=zh-CN&u;=http://blog.csd

入口网址

http://209.85.229.132/translate_c?hl=en&sl;=zh-CN&u;=http://blog.csdn.ne

他查看的详细路径,懂E文的兄弟也可以看看google的机器翻译效果。。。。也可以体会一下此英国兄弟的感受-_-!

http://translate.google.com/translate?hl=en&sl;=zh-CN&u;=http://www.jtianling.com/archive/2008/04/23/2316676.aspx&ei;=g-kxSp2lE4KQjAf92_mXCg&sa;=X&oi;=translate&resnum;=8&ct;=result&prev;=/search%3Fq%3DRegisterRawInputDevices%2Bvb%26hl%3Den%26sa%3DN%26start%3

 

阅读全文....

在VS中debug时,将未初始化变量都赋值为CC的顿悟


在VS中debug时,将未初始化变量都赋值为CC的顿悟

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一直以来,我都不是太理解这种方式,在 C++函数调用原理理解中,我仅仅是简单的认为,那么做,可能是因为CC平时用的少,而且好看:)所以初始化这样一个不怎么常用的变量,可以让人很快发现。。。。事实上,的确有这样的效果,当Debug时,我看一个变量为CC时的确第一时间就能反应过来,我又犯了一个不可饶恕的低级错误,又忘了初始化了,这点在变量为指针类型的时候更加严重。

但是,在学习过反汇编这么久后,今天在看《C缺陷与陷阱》时,突然顿悟了CC的意义。。。。。至于为什么是看这本和这件事完全没有关系的时候突然想到,我也不知道,反正就是那样发生了。

CC在汇编代码中表示为int 3,实际表示一个中断,在与硬件中断(CPU中加入的DR寄存器指示)做区别的时候也叫软中断。。。。几乎所有的调试工具在调试时,都是靠int 3来完成任务的。。。。。。这些我知道时间不短了。。。。但是今天才将其与VS在debug时的初始化联系起来。。。。。这样的话,假如有异常的跳转,程序运行到了不应该运行的地方。。。。那么,就会触发中断,让调试程序获得控制,这样可以更早的发现问题,而不是当运行了一堆莫名其妙的代码后才出现问题。。。。。。

至于VS在debug时的初始化,可以用debug方式编译任何程序,你都能看到

比如在C++函数调用原理理解例子中如下:

0041136C  lea         edi,[ebp-0C0h] ;读入[ebp-0C0h]有效地址,即原esp-0C0h,正好是为该函数留出的临时存储区的最低位

00411372  mov         ecx,30h   ;ecx = 30h(48),30h*4 = 0C0h

00411377  mov         eax,0CCCCCCCCh ;eax = 0CCCCCCCCh;

0041137C  rep stos    dword ptr es:[edi] ;重复在es:[edi]存入30个;0CCCCCCCCh? Debug模式下把Stack上的变量初始化为0xcc,检查未初始化的问题

 

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

在C++中内嵌汇编代码分析


C++中内嵌汇编代码分析

write by 九天雁翎(JTianLing) -- www.jtianling.com

JAVA或者Python的人常常会告诉你,现在的硬件已经太快了,以至于你可以完全不再考虑性能,快速的开发才是最最重要的。这是个速食者的年代,什么都是,甚至是编程。

但是。。。。。。永远有但是,任何C/C++程序员在工作一段时间后都会发现,不关心性能的C/C++程序员是干不下去的。。。。。C语言的程序员犹甚,C++的程序员也许还可以光靠着MFC等类库混口饭吃。。。

C/C++程序员常常会发现自己只有有限的内存,要占用更小的CPU资源(或者仅仅只有速度非常有限的嵌入式CPU可用),但是却得完成非常复杂的任务,这时候能够利用的工具却非常有限。。。唯一可能有用的就是自己的大脑。。。呵呵,改进算法永远是最重要的,但是一个非常优秀的算法也没有办法满足要求的时候,你再剩下的东西可能就是无限的优化原有的C/C++代码,接着就是汇编了。

这里从内嵌汇编将起,然后讲讲用MASM独立编译,然后链接的方法,都以VS2005为例子,其他编译器请参考。

内嵌汇编:

内嵌汇编的使用是很简单并且方便的,在VS中以的形式就可以简单的使用。对于此语法我没有必要再多说了,各类资料都有介绍,可以参考参考《加密与解密》(第3版)附录2.这里想举几个的函数的调用例子来说明一下,因为在函数调用时需要特别注意调用的约定,各类调用约定有不同的规矩需要留意,因为一旦使用了汇编,出现问题没有东西可以保护你,唯一可以看到的就是程序崩溃。

对于各类调用约定也可以参考《加密与解密》(第3版),其中的分类还比较详细。

我在《 反汇编时的函数识别及各函数调用约定的汇编代码分析》,《 C++中通过指针,引用方式做返回值的汇编代码分析》中也有一些实例的分析,但是不能代替完整的说明,仅供参考。

http://www.jtianling.com/archive/2009/01/20/3844768.aspx

http://www.jtianling.com/archive/2009/01/20/3844520.aspx

 

以下形式就是最方便的内嵌汇编用法:

__asm

{

 

}

1

//  仅仅返回参数

int __cdecl GetArgument(int ai)

{

    int li = 0;

    __asm

    {

       mov eax, ai;

       mov li, eax

    }

    return li;

}

 

以上程序就是一个仅仅返回参数的函数,用__cdecl调用约定,需要注意的是,mov li, ai的形式是不允许的。。。。与在普通汇编中不允许从内存mov到内存的限制一致。

其实上面的例子中,程序可以优化为如下形式:

2

//  仅仅返回参数

int __cdecl GetArgument(int ai)

{

    __asm

    {

       mov eax, ai

    }

}

在函数中没有指定返回值时,eax就是返回值了。。。这和普通的汇编代码一致。。。。顺便看看生成的代码:

    printf("%d/n",GetArgument(100));

0041142E  push        64h 

00411430  call        GetArgument (41100Ah)

00411435  add         esp,4

 

足够人性化的,栈由编译器自动的平衡了,不需要我们额外操心,这是很愉快的事情。

再看例三:

//  仅仅返回参数

int __stdcall GetArgument(int ai)

{

    __asm

    {

       mov eax, ai

    }

}

这次改成了__stdcall,那么编译器还会帮我们吗?答案还是肯定的。所以也可以放心用。

调用时:

0041142E  push        64h 

00411430  call        GetArgument (4111DBh)

00411435  mov         esi,esp

原函数:

//  仅仅返回参数

int __stdcall GetArgument(int ai)

{

004113C0  push        ebp 

004113C1  mov         ebp,esp

004113C3  sub         esp,0C0h

004113C9  push        ebx 

004113CA  push        esi 

004113CB  push        edi 

004113CC  lea         edi,[ebp-0C0h]

004113D2  mov         ecx,30h

004113D7  mov         eax,0CCCCCCCCh

004113DC  rep stos    dword ptr es:[edi]

       __asm

       {

              mov eax, ai

004113DE  mov         eax,dword ptr [ai]

       }

}

004113E1  pop         edi 

004113E2  pop         esi 

004113E3  pop         ebx 

004113E4  add         esp,0C0h

004113EA  cmp         ebp,esp

004113EC  call        @ILT+330(__RTC_CheckEsp) (41114Fh)

004113F1  mov         esp,ebp

004113F3  pop         ebp 

004113F4  ret         4   

其实只关心外部调用者没有维持栈平衡,而函数内部最后的ret 4就够了,完全符合__stdcall的定义。

再接下来呢?thiscall?

    int GetArgument(int ai)

    {

00411450  push        ebp 

00411451  mov         ebp,esp

00411453  sub         esp,0CCh

00411459  push        ebx 

0041145A  push        esi 

0041145B  push        edi 

0041145C  push        ecx 

0041145D  lea         edi,[ebp-0CCh]

00411463  mov         ecx,33h

00411468  mov         eax,0CCCCCCCCh

0041146D  rep stos    dword ptr es:[edi]

0041146F  pop         ecx 

00411470  mov         dword ptr [ebp-8],ecx

       __asm

       {

           mov eax, ai

00411473  mov         eax,dword ptr [ai]

           add eax, [ecx]

00411476  add         eax,dword ptr [ecx]

       }

    }

00411478  pop         edi 

00411479  pop         esi 

0041147A  pop         ebx 

0041147B  add         esp,0CCh

00411481  cmp         ebp,esp

00411483  call        @ILT+330(__RTC_CheckEsp) (41114Fh)

00411488  mov         esp,ebp

0041148A  pop         ebp 

0041148B  ret         4 

最后的结果也是正确的,也符合ecxthis指针,并且函数内部维护栈的约定,这个例子也许得将测试程序也贴出来:

int _tmain(int argc, _TCHAR* argv[])

{

    CTestThisCall lo(20);

    printf("%d/n",lo.GetArgument(10));

 

    system("PAUSE");

 

    return 0;

}

得出的结果是30,结果是正确的。

然后呢?fastcall?

见例4

int __fastcall GetArgument(int ai)

{

    __asm

    {

       mov eax, ecx

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

 

    system("PAUSE");

 

    return 0;

}

 

结果也是正确的,fastcall的规则在VC中是已知的,但是在很多编译器中实现并不一样,所以很多书籍推荐最好不要使用fastcall来内嵌汇编,因为你不知道到底fastcall到底会使用哪些寄存器来传递参数。

MSDN中有如下描述:

In general, you should not assume that a register will have a given value when an __asm block begins. Register values are not guaranteed to be preserved across separate __asm blocks. If you end a block of inline code and begin another, you cannot rely on the registers in the second block to retain their values from the first block. An __asm block inherits whatever register values result from the normal flow of control.

If you use the __fastcall calling convention, the compiler passes function arguments in registers instead of on the stack. This can create problems in functions with __asm blocks because a function has no way to tell which parameter is in which register. If the function happens to receive a parameter in EAX and immediately stores something else in EAX, the original parameter is lost. In addition, you must preserve the ECX register in any function declared with __fastcall.

To avoid such register conflicts, don't use the __fastcall convention for functions that contain an __asm block. If you specify the __fastcall convention globally with the /Gr compiler option, declare every function containing an __asm block with __cdecl or __stdcall. (The __cdecl attribute tells the compiler to use the C calling convention for that function.) If you are not compiling with /Gr, avoid declaring the function with the __fastcall attribute.

在《加密与解密》中有适当的翻译。。。。我就不翻译了,应该能看懂吧。其实大意就是最好别用fastcall,原因主要是不知道哪个寄存器用来传递参数,假如一个函数中你需要使用ecx一定要记得将其还原。。。呵呵,这个道理很简单,因为一个参数的时候fastcall必定是由ecx来传递参数的。两个参数就是ecx,edx,起码在VS2005中是这样。虽然个人没有感觉用起来有多大的风险,但是文中甚至提及可能通过eax传递参数。。(应该是在优化的时候),所以假如真的用了fastcall,请看汇编代码确认一下,以防万一。

再见一个更详细的例5

int __fastcall GetArgument(int ai1, int ai2, int ai3)

{

    __asm

    {

       mov eax, ecx

       add eax, edx

       add eax, [ebp+8]

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10, 20, 30));

 

    system("PAUSE");

 

    return 0;

}

 

反汇编:

       printf("%d/n",GetArgument(10, 20, 30));

00411ACE  push        1Eh 

00411AD0  mov         edx,14h

00411AD5  mov         ecx,0Ah

00411ADA  call        GetArgument (4111F4h)

 

int __fastcall GetArgument(int ai1, int ai2, int ai3)

{

004130B0  push        ebp 

004130B1  mov         ebp,esp

004130B3  sub         esp,0D8h

004130B9  push        ebx 

004130BA  push        esi 

004130BB  push        edi 

004130BC  push        ecx 

004130BD  lea         edi,[ebp-0D8h]

004130C3  mov         ecx,36h

004130C8  mov         eax,0CCCCCCCCh

004130CD  rep stos    dword ptr es:[edi]

004130CF  pop         ecx 

004130D0  mov         dword ptr [ebp-14h],edx

004130D3  mov         dword ptr [ebp-8],ecx

       __asm

       {

              mov eax, ecx

004130D6  mov         eax,ecx

              add eax, edx

004130D8  add         eax,edx

              add eax, [ebp+8]

004130DA  add         eax,dword ptr [ai3]

       }

}

004130DD  pop         edi 

004130DE  pop         esi 

004130DF  pop         ebx 

004130E0  add         esp,0D8h

004130E6  cmp         ebp,esp

004130E8  call        @ILT+330(__RTC_CheckEsp) (41114Fh)

004130ED  mov         esp,ebp

004130EF  pop         ebp 

004130F0  ret         4   

 

说实话,ebp+8的计算我原来是搞错了,我当时直接用了esp+8,但是后来才发现在debug没有优化的时候,此处是会采用先保存esp,然后再用ebp的标准用法的。这里没有任何可健壮和移植性可言,仅仅是为了说明thiscall其实也是有规则并且符合约定的,比如上述程序在VS2005最大速度优化时也能正确运行,反汇编代码如下:

int _tmain(int argc, _TCHAR* argv[])

{

       printf("%d/n",GetArgument(10, 20, 30));

00401010  mov         edx,14h

00401015  push        1Eh 

00401017  lea         ecx,[edx-0Ah] ;注意此处,是编译器相当强程序优化的 成果,用此句替代了mov ecx,0Ah,原因说白了就很简单,因为此句才3个字节,而用mov的需要5个字节。。。。。

0040101A  call        GetArgument (401000h)

0040101F  push        eax 

00401020  push        offset string "%d/n" (4020F4h)

00401025  call        dword ptr [__imp__printf (4020A4h)]

 

       system("PAUSE");

0040102B  push        offset string "PAUSE" (4020F8h)

00401030  call        dword ptr [__imp__system (40209Ch)]

00401036  add         esp,0Ch

 

       return 0;

00401039  xor         eax,eax

}

 

int __fastcall GetArgument(int ai1, int ai2, int ai3)

{

00401000 55               push        ebp 

00401001 8B EC            mov         ebp,esp

       __asm

       {

              mov eax, ecx

00401003 8B C1            mov         eax,ecx

              add eax, edx

00401005 03 C2            add         eax,edx

              add eax, [ebp+8]

00401007 03 45 08         add         eax,dword ptr [ai3]

       }

}

0040100A 5D               pop         ebp 

0040100B C2 04 00         ret         4  

 

最后,在VS2005中还有一个专门为内嵌汇编准备的函数调用方式,naked。。。裸露的调用方式?-_-!呵呵,其实应该表示是无保护的。

6

int __declspec(naked) __stdcall GetArgument(int ai)

{

    __asm

    {

       mov eax, [esp+04H]

       ret 4

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

 

    system("PAUSE");

 

    return 0;

}

 

反汇编代码:

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

00401010  push        0Ah 

00401012  call        GetArgument (401000h)

00401017  push        eax 

00401018  push        offset string "%d/n" (4020F4h)

0040101D  call        dword ptr [__imp__printf (4020A4h)]

 

       system("PAUSE");

00401023  push        offset string "PAUSE" (4020F8h)

00401028  call        dword ptr [__imp__system (40209Ch)]

0040102E  add         esp,0Ch

 

       return 0;

00401031  xor         eax,eax

}

 

int __declspec(naked) __stdcall GetArgument(int ai)

{

    __asm

    {

       mov eax, [esp+04H]

00401000  mov         eax,dword ptr [esp+4]

              ret 4

00401004  ret         4

}

}

哈哈,在debugnaked函数都是如此简洁,因为我们对其有完完全全的控制,非常完全!以至于。。。在这里你想用mov eax,ai的形式都是不可以的。。。只能完全遵循普通汇编的规则来走。

但是无论此函数再怎么nakedVS也不是完全不管的,见下例7

int __declspec(naked) __cdecl GetArgument(int ai)

{

    __asm

    {

       mov eax, [esp+04H]

       ret

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

 

    system("PAUSE");

 

    return 0;

}

 

假如VS完全不管GetArgument函数,因为GetArgument函数内部无法维护栈平衡,那么程序崩溃是必然的,还好VS管理了这个问题:

    printf("%d/n",GetArgument(10));

00411ACE  push        0Ah 

00411AD0  call        GetArgument (4111FEh)

00411AD5  add         esp,4

 

这里需要说明的是,当一个函数naked的时候,完全的指挥权都在程序员手中,包括栈的平衡,函数的返回都需要程序员来做,ret是必不可少的,这里想说明的是,一旦你使用了内嵌汇编,你就开始走在了悬崖边上,因为C++强健的编译时期类型检查完全帮助不了你,能够帮助你的仅仅是你的大脑和你对汇编的理解了。呵呵,既然是用汇编写代码,其实都没有反汇编的概念可言,源代码就在哪儿,看仔细了debug比什么都重要,ollydbg,softICE,windbg你爱用什么就用什么吧。

另外,当函数不是naked的时候,从理论上讲你也是可以在汇编中ret的,只不过,你冒的风险也更大了,因为VS好像还不够智能化,它无法预见你的ret,或者从设计上来讲,不是naked的时候你不应该自己处理ret的,所以,即使是你在内嵌汇编中已经有了ret,它还是会原封不动的用于函数返回的代码。。。。

见下例8

int __cdecl GetArgument(int ai)

{

    __asm

    {

       mov eax, ai

       mov esp,ebp

       pop ecx

       ret

    }

}

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

 

    system("PAUSE");

 

    return 0;

}

 

上例在VS2005中最大速度优化,内联选项为仅仅在有inline声明才内联时可以运行正确,见汇编代码:

int _tmain(int argc, _TCHAR* argv[])

{

    printf("%d/n",GetArgument(10));

00401020  call        GetArgument (401000h)

00401025  push        eax 

00401026  push        offset string "%d/n" (4020F4h)

0040102B  call        dword ptr [__imp__printf (4020A4h)]

 

       system("PAUSE");

00401031  push        offset string "PAUSE" (4020F8h)

00401036  call        dword ptr [__imp__system (40209Ch)]

0040103C  add         esp,0Ch

 

       return 0;

0040103F  xor         eax,eax

}

 

int __cdecl GetArgument(int ai)

{

00401000  push        ebp 

00401001  mov         ebp,esp

00401003  push        ecx 

00401004  mov         dword ptr [ai],0Ah

       __asm

       {

              mov eax, ai

0040100B  mov         eax,dword ptr [ai]

              mov esp,ebp

0040100E  mov         esp,ebp

              pop ecx

00401010  pop         ecx 

              ret

00401011  ret             

       }

}

00401012  mov         esp,ebp

00401014  pop         ebp 

00401015  ret        

 

当你在不应该ret的时候ret,带来的往往是灾难性的后果,所以此处你必须的预见(或者先反汇编看到)原程序会做的所有操作,并且完整的重复它们,不然你在一个非naked的函数中用ret,结果几乎肯定是程序的崩溃。无论如何,除非你有特殊原因,还是按规矩办事比较好。

其实这样的代码对于反汇编来说还挺有意思的:IDA中可以看到

.text:00401000                      ; ===========================================================================

.text:00401000

.text:00401000                      ; Segment type: Pure code

.text:00401000                      ; Segment permissions: Read/Execute

.text:00401000                      _text           segment para public 'CODE' use32

.text:00401000                                      assume cs:_text

.text:00401000                                      ;org 401000h

.text:00401000                                      assume es:nothing, ss:nothing, ds:_data, fs:nothing, gs:nothing

.text:00401000

.text:00401000                      ; =============== S U B R O U T I N E =======================================

.text:00401000

.text:00401000                      ; Attributes: bp-based frame

.text:00401000

.text:00401000                      GetArgument     proc near               ; CODE XREF: _mainp

.text:00401000

.text:00401000                      var_4           = dword ptr -4

.text:00401000

.text:00401000 55                                   push    ebp

.text:00401001 8B EC                                mov     ebp, esp

.text:00401003 51                                   push    ecx

.text:00401004 C7 45 FC 0A 00 00 00                 mov     [ebp+var_4], 0Ah

.text:0040100B 8B 45 FC                             mov     eax, [ebp+var_4]

.text:0040100E 8B E5                                mov     esp, ebp

.text:00401010 59                                   pop     ecx

.text:00401011 C3                                   retn

.text:00401011                      GetArgument     endp

.text:00401011

.text:00401012                      ; ---------------------------------------------------------------------------

.text:00401012 8B E5                                mov     esp, ebp

.text:00401014 5D                                   pop     ebp

.text:00401015 C3                                   retn

 

多余的代码IDA人性的分割掉了,这在没有源代码的人那里过去了就过去了,要是真深究的话,可能还会以为因为花指令的影响导致代码反汇编错了。。。呵呵,不然也不会有3行代码无缘无故的被分割在那里,一看也不像数据段。。。。。

另外push ecx,pop ecx的处理也是编译器极端优化的一个例子,这里本来可以用sub,add修改esp的做法的,但是那样的话一条指令就是5个字节。。。这样的话才1个字节!真是极端的优化啊。。。。。

 

 

参考资料:

1.      MSDN

2.      《加密与解密》(第3版)附录2

 

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

反汇编时的函数识别及各函数调用约定的汇编代码分析


 反汇编时的函数识别及各函数调用约定的汇编代码分析

write by 九天雁翎(JTianLing) -- www.jtianling.com

其实在刚开始工作的时候,我就因为工作中需要处理异常处理时的dump,就分析过C++的函数调用原理,也写过一篇文章,现在因为另外的原因(反外挂)而重新提起,回头看以前写的文章,实在是略显稚嫩:)也是,那是9个月前的事情了。

原文《C++函数调用原理理解》http://www.jtianling.com/archive/2008/06/01/2501238.aspx

 

首先看一个很简单的例子(此例子的更简单原型(一个add函数)来自于《加密与解密》一书第三版第4章的第2节),这里列举出了各种函数调用约定的ADD函数(其实还有PASCAL调用约定,但是作为C++程序员我忽略了那种,所以我提到的几种函数调用约定都是参数反向入栈的,此点再下面不再提起)这里的编译器选择了VC6,不是我喜欢仿古。。。但是,因为VS2005(我平时用的)的优化太过于激进。。。。不知道此词妥否。。。。起码一般的小测试程序几乎不能反汇编得到什么信息,一般就是直接在编译器完成了很多操作了。这个我在以前也提到过,

比如在《Inside C++ Object 》 阅读笔记(1), NRV(Named Return Value)一文中:

http://www.jtianling.com/archive/2008/12/09/3486211.aspx

 

示例源代码1:

 

 1 int __cdecl Add1(int x,int y) // default
 2 {
 3         return(x+y);
 4 }
 5
 6 int __stdcall Add2(int x,int y)
 7 {
 8         return(x+y);
 9 }
10
11 int __fastcall Add3(int x,int y)
12 {
13         return(x+y);
14 }
15
16 int inline Add4(int x,int y)
17 {
18         return(x+y);
19 }
20
21 int main( )
22 {
23         int a=5,b=6;
24         Add1(a,b);
25         Add2(a,b);
26         Add3(a,b);
27         Add4(a,b);
28         return 0;
29  }

 

 

Release编译后,反汇编代码及其注释如下:

 

.text:00401030 ; main函数

.text:00401030

.text:00401030 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401030 _main proc near ; CODE XREF: _mainCRTStartup+AFp

.text:00401030 push 6

.text:00401032 push 5

.text:00401034 call Add1 ; 将参数压入栈中,并调用Add1(__cdecl调用约定函数,

.text:00401034 ; 即C语言的调用规范,调用者负责栈的维护)

.text:00401039 add esp, 8 ; 此处由调用者维护调用了Add1后的栈,

.text:00401039 ; esp加8是因为两个参数

.text:0040103C push 6

.text:0040103E push 5

.text:00401040 call Add2 ; 参数入栈,并调用Add2(__stdcall调用规范,windows

.text:00401040 ; API的默认调用规范,由被调用者负责维护栈)所以

.text:00401040 ; 此函数调用完后,main函数中不需要有维护栈的操作

.text:00401045 mov edx, 6

.text:0040104A mov ecx, 5

.text:0040104F call Add3 ; 将参数赋值给寄存器edx,ecx,调用Add3(Fastcall调用约定,

.text:0040104F ; 函数尽量通过寄存器传递,也是由被调用者自己维护栈)

.text:00401054 xor eax, eax ; 此处清空eax作为main函数的返回值返回了,注意到并没有

.text:00401054 ; Add4(inline函数)的调用,并且因为返回值并没有用,

.text:00401054 ; 所以此函数即使在VC6中,也忽略了。

.text:00401056 retn

.text:00401056 _main endp

 

 

 

例2,稍微复杂一点

源码:

 1 #include
 2
 3 int __cdecl Add1(int x,int y) // default
 4 {
 5     int z = 1;
 6     return(x+y+z);
 7 }
 8
 9 int __stdcall Add2(int x,int y)
10 {
11     int z = 1;
12     return(x+y+z);
13 }
14
15 int __fastcall Add3(int x,int y)
16 {
17     int z = 1;
18     return(x+y+z);
19 }
20
21 int inline Add4(int x,int y)
22 {
23     int z = 1;
24     return(x+y+z);
25 }
26
27 int main( )
28 {
29     int a=5,b=6;
30     int c = 0;
31
32     c += Add1(a,b);
33     c += Add2(a,b);
34     c += Add3(a,b);
35     c += Add4(a,b);
36     printf("%d",c);
37     return 0;
38  }
39

 

比前面的例子多了一个变量c来累加返回值并输出,每个函数中再多了一个局部变量。

 

Release编译后,反汇编代码及其注释如下:

.text:00401030 ; main函数

.text:00401030

.text:00401030 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401030 _main proc near ; CODE XREF: _mainCRTStartup+AFp

.text:00401030

.text:00401030 argc = dword ptr 4

.text:00401030 argv = dword ptr 8

.text:00401030 envp = dword ptr 0Ch

.text:00401030

.text:00401030 push esi ; 以下可以看到,esi后来一直用作局部变量c,

.text:00401030 ; 所以此处先保存以前的值

.text:00401031 push 6

.text:00401033 push 5

.text:00401035 call Add1

.text:0040103A add esp, 8

.text:0040103D mov esi, eax ; 默认约定eax是返回值,无论哪种调用约定都是一样的,

.text:0040103D ; 并且因为C/C++函数肯定只能由一个返回值,所以确定

.text:0040103D ; 是eax这一个寄存器也没有关系

.text:0040103F push 6

.text:00401041 push 5

.text:00401043 call Add2

.text:00401048 mov edx, 6

.text:0040104D mov ecx, 5

.text:00401052 add esi, eax

.text:00401054 call Add3

.text:00401059 lea eax, [esi+eax+0Ch] ; 内联的作用,Add4还是没有函数调用,并且用一个lea指令

.text:00401059 ; 实现了c+Add3()+5+6的操作,其中5+6的值在编译器确定

.text:0040105D push eax

.text:0040105E push offset aD ; "%d"

.text:00401063 call _printf

.text:00401068 add esp, 8 ; 可见C语言库函数的调用遵循的是__cdecl约定,所以此处

.text:00401068 ; 由main函数维护栈

.text:0040106B xor eax, eax

.text:0040106D pop esi

.text:0040106E retn

.text:0040106E _main endp

 

与前一个例子重复的内容我注释也就不重复了。

一下具体看看各个Add函数的内容

.text:00401000 Add1 proc near ; CODE XREF: _main+5p

.text:00401000

.text:00401000 arg_0 = dword ptr 4

.text:00401000 arg_4 = dword ptr 8

.text:00401000

.text:00401000 mov eax, [esp+arg_4] ; 因为函数是如此的简单,所以此处并没有将ebp入栈,也

.text:00401000 ; 并没有通过堆栈为z局部变量开辟空间,而是直接用esp

.text:00401000 ; 取参数,用lea指令来完成+1,以下几个函数相同

.text:00401004 mov ecx, [esp+arg_0]

.text:00401008 lea eax, [ecx+eax+1]

.text:0040100C retn ; 这里可以看到Add1函数并没有在内部维护栈,原因也说了

.text:0040100C Add1 endp ; __cdecl调用约定是由调用者来维护栈的

.text:0040100C

.text:0040100C ; ---------------------------------------------------------------------------

.text:0040100D align 10h

.text:00401010

.text:00401010 ; =============== S U B R O U T I N E =======================================

.text:00401010

.text:00401010

.text:00401010 Add2 proc near ; CODE XREF: _main+13p

.text:00401010

.text:00401010 arg_0 = dword ptr 4

.text:00401010 arg_4 = dword ptr 8

.text:00401010

.text:00401010 mov eax, [esp+arg_4]

.text:00401014 mov ecx, [esp+arg_0]

.text:00401018 lea eax, [ecx+eax+1]

.text:0040101C retn 8 ; 此处可以看到Add2自己维护了栈,retn 8相当于

.text:0040101C Add2 endp ; add esp 8

.text:0040101C ; retn

.text:0040101C ; ---------------------------------------------------------------------------

.text:0040101F align 10h

.text:00401020

.text:00401020 ; =============== S U B R O U T I N E =======================================

.text:00401020

.text:00401020

.text:00401020 Add3 proc near ; CODE XREF: _main+24p

.text:00401020 lea eax, [ecx+edx+1] ; 通过寄存器来传递参数,速度自然快,也不破坏栈,所以

.text:00401020 ; 也不用维护,此处的参数较少,所以可以达到完全不用

.text:00401020 ; 栈操作

.text:00401024 retn

.text:00401024 Add3 endp

.text:00401024

 

至此,完全没有源码,看到一个函数的调用,大概也知道参数是什么,返回值是什么,栈维护的操作是在干什么了。

这里再看两个复杂点的例子,一个是局部变量多一点的Add5,一个是参数多一点的fastcall调用的函数Add6

 1 #include
 2
 3 int __cdecl Add5(int x,int y) // default
 4 {
 5     int z1 = 1;
 6     int z2 = ++z1;
 7     int z3 = ++z2;
 8     return(x+y+z1+z2+z3);
 9 }
10
11 int __fastcall Add6(int x,int y,int z)
12 {
13     return(x+y+z);
14 }
15
16
17 int main( )
18 {
19     int a=5,b=6;
20     int c = 0;
21
22     c += Add5(a,b);
23     c += Add6(a,b,c);
24
25     printf("%d",c);
26     return 0;
27  }

 

反汇编:

.text:00401020 ; int __cdecl main(int argc, const char **argv, const char *envp)

.text:00401020 _main proc near ; CODE XREF: _mainCRTStartup+AFp

.text:00401020

.text:00401020 argc = dword ptr 4

.text:00401020 argv = dword ptr 8

.text:00401020 envp = dword ptr 0Ch

.text:00401020

.text:00401020 push esi

.text:00401021 push 6

.text:00401023 push 5

.text:00401025 call Add5

.text:0040102A add esp, 8

.text:0040102D mov esi, eax ; 保存第3个参数(即Add5的返回值)到esi

.text:0040102F mov edx, 6

.text:00401034 mov ecx, 5

.text:00401039 push esi ; 虽然时fastcall,但是edx,ecx不够用的时候,还是使用了栈

.text:0040103A call Add6

.text:0040103F add esi, eax

.text:00401041 push esi

.text:00401042 push offset aD ; "%d"

.text:00401047 call _printf

.text:0040104C add esp, 8

.text:0040104F xor eax, eax

.text:00401051 pop esi

.text:00401052 retn

.text:00401052 _main endp

 

 

Add函数:

.text:00401000 ; =============== S U B R O U T I N E =======================================

.text:00401000

.text:00401000

.text:00401000 Add5 proc near ; CODE XREF: _main+5p

.text:00401000

.text:00401000 arg_0 = dword ptr 4

.text:00401000 arg_4 = dword ptr 8

.text:00401000

.text:00401000 mov eax, [esp+arg_4]

.text:00401004 mov ecx, [esp+arg_0]

.text:00401008 lea eax, [ecx+eax+8] ; 虽然我尽量做了很多无用的操作,但是连VC6都要把这些

.text:00401008 ; 操作优化掉

.text:0040100C retn

.text:0040100C Add5 endp

.text:0040100C

.text:0040100C ; ---------------------------------------------------------------------------

.text:0040100D align 10h

.text:00401010

.text:00401010 ; =============== S U B R O U T I N E =======================================

.text:00401010

.text:00401010

.text:00401010 Add6 proc near ; CODE XREF: _main+1Ap

.text:00401010

.text:00401010 arg_0 = dword ptr 4

.text:00401010

.text:00401010 lea eax, [ecx+edx]

.text:00401013 mov ecx, [esp+arg_0] ; fastcall在VC中只会使用ecx,edx两个寄存器来传递参数,

.text:00401013 ; 当参数超过2个时,还是得通过栈来传递

.text:00401017 add eax, ecx

.text:00401019 retn 4

.text:00401019 Add6 endp

.text:00401019

.text:00401019 ; ---------------------------------------------------------------------------

 

 

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

从最简单的Win32汇编程序,HelloWorld说起


从最简单的Win32汇编程序,HelloWorld说起

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

自从开始弄反外挂以来,我的个人目标很明确,就是借此机会,好好的重新学习Win32汇编,当年买了书,开始学了一下因为C++学习的紧迫性,就放弃了。。。这和Python当年学习的过程几乎是一样的。。。。。呵呵,人生真是戏剧性,当工作后,我因为工作需要学习了lua后,重拾对脚本语言的兴趣,对C++的学习紧迫性也没有那么强了(毕竟完全可以应付工作了,真是完全,我还没有因为C++语法的问题在工作中卡过,剩下需要学习的就是思想了),所以又开始扛着《Python核心编程》啃,最近反外挂的工作竟然也在工作中用了python一把,真是戏剧性啊。。。。。现在要反汇编嘛,乘机好好学好汇编吧。。。。我发现我对于越底层的东西好像兴趣越大。。。。不知道为什么,也许是因为毕竟是学硬件出身吧。。。。。。底层的东西离硬件近点。。。。但是怎么解释我去学习LUA,Python呢?所以结果还是没有办法解释。。。。。。。。。

书中的例子很简单,源代码如下:

.486                                ; create 32 bit code
.model flat, stdcall                ; 32 bit memory model
option casemap :none                ; case sensitive

include windows.inc
include masm32.inc
include user32.inc
include kernel32.inc

includelib masm32.lib
includelib user32.lib
includelib kernel32.lib

.data
    szCaption db "A MessageBox !",0
    szText db "Hello,World !",0

.code

start:
    invoke MessageBox,NULL,offset szText,/
            offset szCaption,MB_OK
    invoke ExitProcess,NULL

end start

 

然后编个makefile:

对了,书中是建议去下载微软的nmakeVS中都有的,但是我不知道是否和GNU make完全兼容,我只学过GNU make,并且好像也不太像去学微软的nmake语法(咋一看好像差不多),所以我下了个windows 版的GNU make来用,幸好还真有这个东西:)

makefile:

1 basename = helloWorld
2 exe = helloWorld.exe
3 obj = helloWorld.obj
4 files = helloWorld.asm
5 $(exe) : $(obj)
6     link /subsystem:windows /map:$(basename).map /out:$(exe) $(obj)
7 $(obj) : $(files)
8     ml /c /coff /Zi $(files)

 

这个makefile写的有点复杂了-_-!呵呵,还好例子简单。。。。。

然后用make命令编译就好了。。。。。这里因为我用了vim。。。所以一切都简单了很多,呵呵,其实大家假如用UE,EditPlus也不会坏到哪去,反正越是接触的语言越多,你也会越希望有个万能的编辑器的。。。。当然,万能的IDE最好。

编译,运行,都没有问题,但是,有的人脑袋就是进水了,因为我没有自己push去传递参数,也因为习惯了C++程序中微软做一大堆手脚,总感觉不踏实,我想看看这个程序中微软是不是也在我背后干了什么。。。。。。(C语言程序员不喜欢C++,其中一个很大的理由就是他们觉得编译器背着他们干了太多,他们不踏实,其实去看看然后多反汇编几次C++程序,要达到基本理解C++编译器干了些什么好像也不是太难。。。。。。但是起码比C语言干的多。。。又说了一大堆没有用的话,我发现我的文中有用的信息越来越少了,都是我东扯西扯。。。)

回到主题,于是我先看了一下生成的exe文件,发现无缘无故多了.rdata区段,虽然我并没有用,所以程序达到了4k(好像是普通不修改PE文件时最小的一个可执行程序的大小),反汇编一下生成的exe文件(还能叫反汇编吗-_-!)

00401000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL

00401002 |. 68 00304000 push 00403000 ; |Title = "A MessageBox !"

00401007 |. 68 0F304000 push 0040300F ; |Text = "Hello,World !"

0040100C |. 6A 00 push 0 ; |hOwner = NULL

0040100E |. E8 07000000 call <_MessageBoxA@16 0040101a f user32:user32.dll> ; /MessageBoxA

00401013 |. 6A 00 push 0 ; /ExitCode = 0

00401015 /. E8 06000000 call <_ExitProcess@4 00401020 f kernel32:kernel32.dll> ; /ExitProcess

0040101A > $- FF25 08204000 jmp dword ptr [<&user32.MessageBoxA;>] ; _MessageBoxA@16 0040101a f user32:user32.dll

00401020 > .- FF25 00204000 jmp dword ptr [<&kernel32.ExitProcess;>] ; _ExitProcess@4 00401020 f kernel32:kernel32.dll

 

我只能说,总算在我们程序背后没有什么在偷偷摸摸进行了,一切就像我们的源代码一样,这正是我们需要的境界。。。。。用汇编这点还是好(其他高级语言使用者基本可以忽视此句)

 

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

PyQT学习 的开始,顺便小谈目前GUI的选择

PyQT学习 的开始,顺便小谈目前GUI的选择

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

以前学习了MFC,工作前自己也做些小应用,工作后除了在工作组呆了2个月,还有在服务器组时客串写点计算md5值,密码,关键字过滤等小工具的时候,基本上用不到MFC。但是,总的来说,MFC的学习还算是在这段时间发挥了一点作用,也不算白费。至于说MFC的设计多么完善,实在不敢恭维,哪怕是自己随便开发一个小工具,都会有别扭的地方,以前学习的时候,更加是为其开发可以动态改变大小的对话框程序绞尽脑汁。。。。。基本上,理解了MFC的原理后,开发一般的东西是没有问题,除了要看看具体控件的函数调用啥以外,但是过程主要还是以痛苦为主,没有畅快的感觉。。。。而,想要用MFC开发出漂亮的界面,那就。。。。更加痛苦,虽然也许控制力更强,可定制的方式更多,但是。。。实在不是太方便。个人最痛苦的经历是在用MFC做一个游戏的时候,那个痛苦啊,基本上需要的功能基本上是从额外的控件库中获得,学习了CButtonBTCImageX等后,才好不容易弄出个雏形。。。。何止一个难字了得。这点。。。虽然怪我准备用MFC做游戏的不当,但是也体现了MFC的确有其复杂性所在。

       个人认为,像一般的程序语言使用时,控制力强,效率高还在某些时候有一定必要,比如公司现在开发网络游戏服务器,客户端的时候用的都是C++,嵌入式开发时用用C,甚至汇编都可,但是开发界面嘛,还有必要弄的非要绞尽脑汁,翻尽资料才能弄的一个控件有点小小的改变吗(比如自绘控件),RAD嘛,那样,速度岂不是慢了点,虽然一个软件的界面很重要,但是指的不是要求开发的多么痛苦,开发的GUI库要多么能自由的控制。。。。。

       综上所述,不喜欢MFC,但是其偏偏是我目前唯一懂的GUI库,我准备改变这一点,准备有空学习QTPyQT,一起学了,相对来说,QT的资料多些,PyQT就比较少,所以我写的时候可能以PyQT为主。

这里顺便谈谈GUI库的选择

本人其实最大的要求就是跨平台,我不希望到时候需要某个Linux工具的时候还需要额外学习其他库,要学习还不一次学完,虽然我现在使用Linux还是纯控制台(putty远程)。

这里基本上有QT,WxWidget,GTK+,等3种选择

这是我在参考了WxWidget,GTK+等相关了资料后做出的选择。。。。GTK+适应面太窄,而且原生为C设计,不喜欢,WxWidgetQT之间倒是抉择了很久,WxWidget也算是比较广泛使用的东西了,记得《梦断代码》一书中描述的Chandler就是使用了WxWidget,并且WxWidgetQT也有很多共同的优点。比如都有Python的绑定版本:)。

从软件架构上来说,按性格我会更喜欢QT这样面向对象使用更加正规的工具而不是WxWidgetMFC这样太依赖宏的工具,虽然说也许速度会慢一点,我也愿意忍受了,就我的经验来说,似乎程序的瓶颈一般不大可能在这些问题上。并且,看资料了解了一下QT的信令槽机制,直观感觉会比可狰的宏操作的消息映射会好得多。

但是WxWidget有另外一个优点,其不与任何一家公司相关,QT毕竟是诺基亚的产品,毕竟受其控制,个人性格又不太喜欢受制于某一家公司的技术,(即便是微软这样的公司我都不愿意,何况诺基亚呢?)所以当年坚决的没有学Object C并买IPhone,而是打算就算是学JAVA也应该支持Android。当年没有学C#JAVA而是学了C++,并且,虽然在Windows下工作的较多,但是还是保持着对Linux的兴趣。这些对自己视野的开阔还是有一定好处的,并且对于知识的持久性也是有保证的。。。。听着骂C#多变的人我就偷笑了,呵呵,C++我盼着变都还不变呢。。。。。但是有利有弊吧,学的东西多了,自然不如专门学一个东西精罗,何况,C#多变,还不是其在发展嘛。。。。。。。这是个人在刚毕业,刚工作时候的取舍,不然一开始就太精,视野容易变得很狭窄,虽然也许这样一开始工资会涨的比较快,呵呵,这是关于GUI以外的题外话了。

但是最后我还是选择了QT,首先,QT虽然其属于诺基亚,但是开源,这点保证了不至于太受制于一家公司,另外,还因为QT有额外的优点:

1.他有原生的图形界面平台,那就是KDE,这点目前我能选择的范围内,就只有MFCGTK+,QT了,WxWidget没有这样的优势。虽然其实我以前用Linux的时候都是在Gnome下。。。(RedhatUbuntuLinux默认都是Gnome,并且因为我一开始用Redhat9Gnome,也习惯了,后来也没有准备改,看来以后可能需要改到KDE了。。。呵呵,不然怎么领悟QT的优势啊。。。。。)

2.QT能跑在很多移动设备上,除了WM,马上也就会有塞班的版本。

3.QT还能更方便的用于嵌入式开发,甚至QT还能开发命令行的图形界面。

因为这些优点。。。。我还是选择了QT,希望这些额外的优点不要是因为QT有一家大公司在宣传的缘故吧-_-!

 

最后的最后,其实一些东西没有提到,主要原因就是其不跨平台了,那么跨平台真的有那么重要吗?这点我说不清楚,个人爱好使然。只是,在同样的开发成本下,假如你的软件有跨平台特性,这有什么不好吗?那么JAVA的宣传口号算啥呢?“一次编写,到处运行”。。。。。呵呵,QT的口号是,“一次编写,到处编译”。。。

这里有个网页,做了一些比较,大家可以参考一下:

GUI programming with Python

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 


 

 

 

 

阅读全文....

OllyDbg,IDA pro强强联合!从OllyDbg中载入IDA Pro输出的map信息文件,带符号信息调试!


OllyDbg,IDA pro强强联合!从OllyDbg中载入IDA Pro输出的map信息文件,带符号信息调试!

write by 九天雁翎(JTianLing) -- www.jtianling.com

再强大的工具有的时候也不能独立解决问题,毕竟各有所长,就算是最最强大的IDA Pro, OllyDbg, SoftIce都不例外,对于静态分析自然是IDA Pro最强大,而静态分析不了的时候就需要SI,OD出马了,以前我找了很久才研究出一种从SoftIce中载入IDA Pro输出的map信息文件的方法,

《SoftIce,IDA pro强强联合!从SOFTICE中打开IDA Pro输出的map信息文件》

http://www.jtianling.com/archive/2009/01/07/3730240.aspx

 

这一次,我又在网上发现了从OllyDbg载入IDA Pro输出的map的信息的方法了:)

这样实现了OllyDbg带符号信息的调试,更加方便了。。。。呵呵

其实主要的问题在于OllyDbg的一个LoadMap的插件,因为看雪没有下载,我一直不知道,特意从国外download下来了:)

放到老地方,有需要的去下载吧。

http://groups.google.com/group/jiutianfile/files

方法就不多说了,用IDA Pro的Product功能,生成map,再用此插件载入就好了,连这都不知道的话估计也不会用这些软件了。

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....