Desktop
在 REALbasic 中注册 AppleEvent
Jan 31st
之前为了注册一个自定义协议,需要通过注册 AppleEvent 来实现,在 Objective-C 中,可以很方便的使用 NSAppleEventManager 来注册 AppleEvent 句柄,但是在 REALbaisc 中,是没有办法直接去调用 NSAppleEventManager 的,所以需要通过声明然后调用 C API 来实现相应的功能。
与 NSAppleEventManager 中功能相对应的 C API 有 AEInstallEventHandler, NewAEEventHandlerUPP 等,通过这些 API 我们也可以在 REALbasic 中来注册 AppleEvent 了,再配合 Info.plist 中的 URLScheme 声明,即可实现 URL 自定义协议处理句柄。
#if TargetCarbon
soft declare function AEInstallEventHandler Lib CarbonLib ( _
theAEEventClass as Integer, _
theAEEventID as Integer, _
handler as Integer, _
handlerRefcon as Integer, _
isSysHandler as Boolean) as Integer
Soft Declare Function NewAEEventHandlerUPP Lib CarbonLib (userRoutine as Ptr) as Integer
Static CallbackUPP as Integer = 0
If CallbackUPP = 0 then
dim m as MemoryBlock = AddressOf ForwardCarbonAEEventToObject
If m is nil then
Return
End if
CallbackUPP = NewAEEventHandlerUPP(m)
End if
dim v as Variant = me
dim OSError as Integer = AEInstallEventHandler( _
OSTypeToUInt(kInternetEventClass), _
OSTypeToUInt(kAEGetURL), _
CallbackUPP, _
v.Hash, false)
msgbox str(OSError)
#endif先使用 NewAEEventHandlerUPP 来生成一个 AppleEvent 回调函数的句柄,然后调用 AEInstallEventHandler 来注册一个共享函数 ForwardCarbonAEEventToObject 为 AppleEvent 事件处理句柄。
AEInstallEventHandler 所需的 AEEventClass 和 AEEventID 都是一个 4 字节的整型,但是通常我们在调用的时候,是用的一个 4 字符的字符串,因此需要一个函数来将 4 字符转换为 4 字节的整形。
// code from ToolbarSearchField by The ZAZ Studios // http://www.thezaz.com/opensource/realbasic/macosx/searchfield/ static m as new MemoryBlock(4) m.LittleEndian = false m.StringValue(0, 4) = s return m.UInt32Value(0)
在 ForwardCartonAEEventToObject 里,参数 theEvent 和 replyEvent 都量个整形,为了从这两个参数里拿到数据,还需要使用 AEGetParamPtr 来从 AppleEvent 中拿到数据。
soft declare function AEGetParamPtr lib CarbonLib ( _ theAppleEvent as Integer, theAEKeyword as Integer, _ desiredType as Integer, byref actualType as Integer, _ dataPtr as Ptr, maximumSize as Integer, _ byref actualSize as Integer) as Integer
当然还有一系列的 AEGetDataDesc、AEGetDescSize 等函数可以,具体可以查 Xocde 随带的库文档。
关于注册自定义协议,可以参考这篇文章。
通过 Core Foundation 中的一些 C API,在 REALbasic 也可以完成一些平台相关的工作,虽然麻烦了些:)
REALbasic 中使用结构体作为 Win32 API 的参数及使用 Win32 API 停止服务
Nov 10th
在之前,我使用 ShellExecute 这个 API 来执行命令(《REALbasic 中使用 ShellExecute 执行命令》),然后通过这个方法来停止某个服务,但是今天想加服务运行状态检测,这样就可以在服务没有运行的情况下不再询问用户是否需要停止某个服务。
为了省事,我一开始决定同样使用 cmd 去执行一个命令,将服务状态输出到一个临时文件中,再通过读取这个临时文件,查找特征字符串来判断服务是否运行:
// 伪代码 ShellExecute("cmd.exe /c sc query service_name > tmpfile") dim serviceStatus as string serviceStatus = TextInputStream.Open(tmpfile).ReadAll() if instr(serviceStatus, "STOPPED") < 1 then // 提示用户是否停止服务 end if
但是这样做不太靠谱,因为 sc 这个命令执行是要时间的,而 ShellExecute 是异步的,这就导致了在调用 ShellExecute 执行完 sc query 之后,临时文件 tmpfile 里并不是马上就有服务状态的内容了。
为了防止这个情况,我再加了一个临时文件内容的检测,如果为空的话,sleep 100ms,再继续读取,如果超过 10 次仍没有内容,直接当作服务正在运行来对待。
// 伪代码 ShellExecute("cmd.exe /c sc query service_name > tmpfile") dim serviceStatus as string dim tryCount as integer while tryCount < 10 and serviceStatus = "" tryCount = tryCount + 1 App.SleepCurrentThread(100) serviceStatus = TextInputStream.Open(tmpfile).ReadAll() wend if instr(serviceStatus, "STOPPED") < 1 then // 提示用户是否停止服务 end if
但是这样仍然不能百分百保证正确,于是想到可不可以继续用 win32 api 来做这些,Google 了一下,找到一篇博客[1],看了之后发现如果只需要停止服务的话,还是蛮简单的,决定使用 win32 api 来实现我想要的功能了。
在停止服务这个过程中需要用到的 win32 api 有:OpenSCManagerW、OpenServiceW、QueryServiceStatus、ControlService,还有一个结构体:SERVICE_STATUS。
win32 api 的参数都很好对应,数值、句柄类型的参数直接使用 Integer 即可,字符串类型使用 CString,而 SERVICE_STATUS 这个结构体也可以直接通过工程菜单添加。
在传参的时候需要注意两点:
- 如果是字符串类型的参数并且调用的接口是 Unicode 类型的,那么需要在传入参数之前,将参数值转换为 Unicode 编码。可以通过 ConvertEncoding(text, Encodings.UTF16) 来转换。
- 如果参数是结构体,那么参数前的传参方式就要写 ByRef,也就是按引用传值,字符串和数值类型的参数可以不写或者写 ByVal。
以下是 4 个 win32 api 的 REALbasic 定义:
soft declare function OpenSCManagerW Lib "advapi32.dll" _ (ByVal lpMachineName As CString, ByVal lpDatabaseName As CString, _ ByVal dwDesiredAccess As Integer) As Integer soft declare function OpenServiceW lib "advapi32.dll" _ (ByVal hSCManager As Integer, ByVal lpServiceName As CString, _ ByVal dwDesiredAccess As Integer) As Integer soft declare function QueryServiceStatus lib "advapi32.dll" _ (byval hService as integer, byref lpServiceStatus as SERVICE_STATUS) _ as boolean soft declare function ControlService lib "advapi32.dll" _ (byval hService as integer, byval dwControl as integer, _ byref lpServiceStatus as SERVICE_STATUS) as boolean
有了 api 定义之后,就可以直接根据上面提到的 vc++ 流程来停止某个服务了。
完整的服务操作 api 可以参阅微软的 MSDN。
这样看来,REALbasic 要写出支持 Windows 7 特性的应用程序也不是难事:)
参数资料
- VC++启动和停止服务, huangchonghai
- QueryServiceStatus, MSDN
关于 REALbasic 中使用 AutoDiscovery 时发生错误 40 的问题
Nov 2nd
今天在使用 AutoDiscovery 发送数据时,发生了错误码为 40 的错误,直接在 UDPSocket 的属性列表里找了一下,没有找到对应的错误码。
在网上搜了一下,找到这篇帖子,里面提到在发送大量数据时,UDPSocket 就会报错误,并且这个错误没有在文档中提到,因此这应该是一个系统级别的错误。
MonkeybreadSoftware 提到了 unix 的错误码定义:
#define EMSGSIZE 40 /* Message too long */
联想到在程序中是在今天开始有问题的,而且今天添加了一大堆数据,看来的确是同于 UDP 消息过大造成的错误 40。
在网上找了找,使用 UDP 发送消息时,报文的大小最好不要超过 MTU,否则会就容易丢包。我在测试时是使用的 127.0.0.1,包大小为 12.5K,照理说应该是直接走 loopback 而不需要走路由的,就算超过 MTU 也应该能发送,不清楚是不是因为 OS 内部实现机制的问题。
为了彻底解决这个问题,最后还是采用了 TCP 来传递大量数据,只使用 UDP 来传递一些控制信息。
在 AIR 中突破同源策略访问 iframe 中的内容
Oct 26th
本篇所讲关于 AIR 的内容是基本 HTML+JS,而不是 Flex 或其他。
在 AIR 中,如果当前页面有一个 iframe,并且我们需要获取这个 iframe 的内容,但是如果 iframe 里包含的是远程页面,在默认情况下,浏览器策略并不允许这种情况,这就是同源策略的限制。
在 Titanium 这个类似于 AIR 的平台中,默认情况下是可以直接从应用的页面中去读取 iframe 中的内容而没有任何限制,但是在 AIR 中不可以。在网上搜索了一番,再看了好几遍 AIR 的文档之后,终于找到解决问题的方法了。
在 AIR 中,iframe 标签拥有额外的几个属性:sandboxRoot、documentRoot、allowCrossDomainXHR 及 ondominitialize。这里为了解决读取 iframe 内容所用到的属性就是 sandboxRoot 及 documentRoot。
通过 sandboxRoot 及 documentRoot,可以将本地的页面映射为远程页面,并在沙盒中运行,另外,在沙盒中运行的页面就会拥有映射域名的域。例如,我们可以将本地的 proxy.html 映射为 http://example.com/local/proxy.html,这样,在实际运行时,proxy.html 中如果用 document.domain 获取页面所在的域,就会得到 example.com,这个时候如果在 proxy.html 中添加一个 iframe,这个 iframe 指向我们实际需要获取内容的地址,因为域相同,就可以直接获取 iframe 的内容了。
例如,我们要在 AIR 中获取 http://example.com/member/login.html 的内容,或者操作这个页面的元素,就可以先用一个 proxy.html,用来实现将本地文件运行在沙盒中,再用一个 login.html 来对实际网页进行操作。
<!– proxy.html –>
<iframe src=”http://example.com/local/login.html” id=”frame” width=”100%”
sandboxRoot=”http://example.com/local/”
documentRoot=”app:/” name=”frame” height=”100%”></iframe>
<!– login.html –>
<iframe id=’f' frameborder=”0″ scrolling=”no”
src=”http://example.com/member/login.html”
width=”100%” height=”100%”></iframe>
<script type=”text/javascript”>
alert(document.domain);
var f = document.getElementById(‘f’);
f.onload = function() {
alert(f.contentWindow.document.documentElement.innerHTML);
};
</script>
在 proxy.html 中,虽然 iframe 的 src 属性是 http://example.com/locale/login.html,但是由于这个 iframe 具有 sandboxRoot 属性,所以实际请求会被重定向到相对于 sandboxRoot,并且使用 documentRoot 进行定位的实际地址 app://login.html,但是这个时候 login.html 中具有 document.domain=”example.com” 这个属性,所有可以直接使用 iframe.contentWindow 来访问 iframe 中的内容。
虽然解决方法有些麻烦,但终归可以实现突破同源策略的限制来读取 iframe 的内容了。
当然,如果你有更好的方法,请不吝赐教:)
REALbasic 中使用 ShellExecute 执行命令
Oct 16th
在 REALbasic 中,如果需要执行 cmd 命令,可以直接使用 Shell 类,但是这样的话,编译成 Windows 程序时会额外需要一个 Shell.dll 的动态链接库,这对于我这样的 1exe 爱好者是不能忍受的。但是对于 Mac OS X 和 Linux 的生成目标来说,是不存在这个问题的,因为 Mac OS X 的应用程序本身就是一个文件夹,而 Linux 的目标不会生成额外的链接库。因此,需要针对 Windows 进行特殊处理。于是在网上搜索解决方案,找到了 VB 中执行程序的几种方法:
1. 使用 CreateProcess
通过 CreateProcess 以及使用管道,可以执行外部程序并获取输出,但是这个方法过于烦琐,并且我也不需要外部进程执行完毕后的输出结果,因此不采用。
2. 使用 Shell 方法
VB 里有一个 Shell 方法,但是在 RB 中并没有,所以此路不通。
3. 使用 ShellExecute
这个方法同样是一个系统 API,可以直接通过 RB 的 declare 声明并调用它,在测试之后,使用 declare 来使用系统 API 不会生成额外的 dll,正是我需要的。
首先在 RB 的某个模块中添加一个方法 ShellExecute,用来封装对系统 API 的请求:
function ShellExecute(hWnd as Integer, lpOperation as String, _
lpFile as String, lpParameters as String, lpDirectory as String, nShowCmd as Integer)
注意,在 RB 中添加方法时,参数列表中的 _ 需要去掉,这里是为了排版的需要而加上的。
这个 API 是定义在 shell32.dll 中的,在 ShellExecute 方法中先需要声明:
soft declare function ShellExecuteA Lib ”shell32.dll” _
(ByVal hWnd As Integer, ByVal lpOperation As CString, _
ByVal lpFile As CString, ByVal lpParameters As CString, _
ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integersoft declare function ShellExecuteW Lib ”shell32.dll” _
(ByVal hWnd As Integer, ByVal lpOperation As CString, _
ByVal lpFile As CString, ByVal lpParameters As CString, _
ByVal lpDirectory As CString, ByVal nShowCmd As Integer) As Integer
然后来调用这个 API:
dim ret as Integer Try ret = ShellExecuteW(hWnd, ConvertEncoding(lpOperation, Encodings.UTF16), _ ConvertEncoding(lpFile, Encodings.UTF16), _ ConvertEncoding(lpParameters, Encodings.UTF16), _ ConvertEncoding(lpDirectory, Encodings.UTF16), nShowCmd) Catch ret = ShellExecuteA(hWnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd) End return ret
把 ShellExecuteW 放在 Try … Catch 中是为了兼容的需要,因为像 Windows 98 这样比较老的系统中,系统内部编码还不是 Unicode,还没有 ShellExecuteW 这样结尾是 W 的 API。
另外,因为 RB 内部是使用的 UTF-8 编码,而系统 API ShellExecuteW 使用的编码是 Unicode,所以参数在传递前需要进行转码,将字符串参数值转换为 Unicode 编码,否则在执行一个明明正确的命令时,会出现“找不到文件”的错误。
好了,这样就可以使用 ShellExecute 方法来执行外部程序了,例如:
#if TargetWin32
dim ret as Integer
ret = Win32Helper.ShellExecute(hWnd, "open", "cmd.exe", _
"/c shutdown -t 10", "", 0)
#endif上面的代码就调用 cmd.exe 执行了命令 shutdown -s -t 10,也就是 10 秒后关机。
其实这个方法也可以用来打开文档或者网址,具体的用法可以去 MSDN 找一找。
当然,如果不在乎生成的 Windows 目标还有额外的 dll 的话,完全可以使用 RB 自带的 Shell 类,功能也强上不少。
在VB中用PictureBox实现图片的缩略图浏览
Dec 9th
要写一个程序,涉及到缩略图的问题,原先准备用StretchBlt来实现的,后来找了下发现PictureBox的PaintPicture方法 也可以把图像按规定大小进行缩放,试了一下感觉PaintPicture的速度比StretchBlt快一些。
PaintPicture的 函数定义为:
Sub PaintPicture(Picture As IPictureDisp, X1 As Single, Y1 As Single, [Width1], [Height1], [X2], [Y2], [Width2], [Height2], [Opcode])
X1 和Y1是目标位置,Width1和Height1是目标大小,默认是PictureBox的大小,X2和Y2是源位置,Width2和Height2是源 大小,Opcode是操作方式,默认应该是vbSrcCopy,如果Width1、Height1和Width2、Height2的大小不一样则进行缩 放。
新建一个工程,设置Form1的ScaleMode为“3 - Pixel”,往窗体上放1个FileListBox控件名称为 File1,1个ImageList控件名称为ImageList1,2个PictureBox控件,一个名称为Picture1,AutoResize 为True,Visible为False,另外一个名称为Picture2,AutoRedraw为True,Visible为False,Width和 Height为128,1个ListView控件名称为ListView1,将图像列表中普通设置为ImageList1。
在窗体里添加如 下代码:
‘ 窗体载入时载入图像列表Private Sub Form_Load()Dim i As LongFor i = 0 To File1.ListCount – 1‘ 载入图像Picture1.Picture = LoadPicture(File1.Path & “\” & File1.List(i))‘ 生成缩略图Picture2.PaintPicture Picture1.Picture, 0, 0‘ 添加缩略图到ImageListImageList1.ListImages.Add , , Picture2.Image‘ 添加缩略图到ListViewListView1.ListItems.Add , , File1.List(i), i + 1DoEventsNextEnd Sub‘ 窗体改变大小时改变ListView的大小Private Sub Form_Resize()With ListView1.Top = 0.Left = 0.Width = Me.ScaleWidth.Height = Me.ScaleHeightEnd WithEnd Sub
用VB写一个你自己的Flash播放器
Jul 10th
前天在PConline下了一个孙鑫的《Java从入门到精通》的视频教程,Flash格式,在看的时候感觉很不爽,每一课开始的时候有一大段广 告,而且前面的一部分颇为啰嗦,讲了乱七八糟的东西,不知道是不是因为“入门”的关系,然后就把以前做的一个Flash播放器找出来,直接跳到自己要看的 地方~
要想用VB做一个自己的Flash播放器还是比较方便的,因为Macromedia已经提供了Shockwave Flash控件, 可以方便来的播放Flash影片。要想使用这个控件,首先要在工程里添加这个控件,选中菜单“工程->部件”,找到 Shockwave Flash,打上勾,确定就可以了。另外,因为要加载Flash影片,所以我们还需要加入对话框控件 Microsoft Common Dialog Control。
做这个Flash播放器之前,先用对象浏览器来看一下 Shockwave Flash控件有哪些我们需要的事件、方法和属性。
因为我们做的Flash播放器比较简单,所以没有可以利用的控件事 件。
方法:
Sub Back()
跳 到前一帧,相当于Flash右键菜单中的快退
Function CurrentFrame() As Long
获 取当前播放的为第几帧
Sub Forward()
跳到后一帧,相当于Flash 右键菜单中的快进
Sub GotoFrame(FrameNum As Long)
跳 到指定帧,这是一个相当有用的方法,也是要做这个Flash播放器的理由之一了
Sub Play()
播 放,相当于Flash中的播放
Sub Stop()
停止,需要注意的是这个是停 止,而不是暂停,停止后再开始播放将从第1帧开始
Sub StopPlay()
暂 停,暂停后再播放是继续暂停之前的状态
属性:
Property Movie As String
影 片路径,用来加载要播放的Flash影片
Property Playing As Boolean
是 否正在播放
TotalFrames
Flash影片的总帧数
好了, 有了这些资料就可以开始写自己的Flash播放了~
打开窗体编辑器,先在窗体添加5个按钮,名称和Caption分别 为:cmdOpen(“打开”),cmdPlay(“播放”),cmdStop(“停止”),cmdPrev(“前一帧”),cmdNext(“后一 帧”)。为了能快整跳转,再添加一个水平滚动条,名称为hslFrame。当然,最重要的,Shockwave Flash不能少了,也要添加,名称为 swf。再者要打开文件,所以添加一个Microsoft Common Dialog Control,名称为dlg。
完成窗体的设计,开始编写代码。
‘ 先给打开按钮添加过程
Private Sub cmdOpen_Click()
dlg.Filter = ”Flash(*.swf)|*.swf” ’ 设 置文件名过滤器,只显示Flash文件
dlg.ShowOpen
If dlg.FileName = ”" Then Exit Sub ’ 如 果用户点了取消则退出处理
swf.Movie = dlg.FileName ’ 加载影片
hslFrame.Max = swf.TotalFrames ’ 获 取总帧数,并赋值给滚动条的最大值
End Sub
‘ 接着是播放按钮
Private Sub cmdPlay_Click()
If swf.Playing = True Then ’ 如 果正在播放则暂停
swf.StopPlay
cmdPlay.Caption = ”播 放” ’ 按钮文本设置为“播放”
Else
swf.Play ’ 否 则就继续播放
cmdPlay.Caption = ”暂停” ’ 按钮文本设置为“暂停”
End If
End Sub
‘ 停 止按钮
Private Sub cmdStop_Click()
swf.Stop ’ 停止
End Sub
‘ 前 一帧按钮
Private Sub cmdPrev_Click()
swf.Back
End Sub
‘ 后 一帧按钮
Private Sub cmdNext_Click()
swf.Forward
End Sub
‘ 为 了可以拖动滚动条来进行播放,下面处理hslFrame的OnChange事件
Private Sub hslFrame_Change()
swf.GotoFrame hslFrame.Value ’ 跳 到滚动条值所在的帧
End Sub
好了,一个简单的Flahs播放器就做好了,当然你也可以在这个此基础上加上更多的功能。
[ASM] 是男人就下100层,交出序列号来
Jun 17th
某天(前天:),看了两个小时高数(具体不详:)之后,想放松一下,找到《是男人就下100层》,打开,玩了两把,突然发现今天怎么看那个“注册”的按钮特别不爽,OK,Crack it!
常规步骤,用PEid侦壳,用VC4.x写的,这样方便了,脱壳都不用。接着祭出用户级调试法宝——Ollydbg,加载《是男人就下100层》,万事俱备,踏上征途!
按F9运行程序,等程序窗口出来,点一下任务栏上按钮,结果竟然中断了,CPU窗口一看,原来是一条INT3指令,麻烦的东西,NOP掉。继续F9运行,好,这下可以把程序窗口调到前面了。切换到程序窗口,点注册,出现填写用户名和序列号的窗口。用户名填HotHeart,序列号填一个123456,确定,当然不会成功,要不我就可以去买彩票了^_^。弹出一个错误提示框,内容是日文的……意思大概是序列号无效。既然有提示框,那就好办,回到Ollydbg,下断点bp MessageBoxA,再次换到程序窗口点确定。YES!顺利中断,切回Ollydbg看看代码:
; 获取对话框中控件文本
00407A78 PUSH 100
00407A7D LEA EAX,DWORD PTR SS:[EBP-204]
00407A83 PUSH EAX
00407A84 PUSH 3EB
00407A89 MOV EAX,DWORD PTR SS:[EBP+8]
00407A8C PUSH EAX
00407A8D CALL DWORD PTR DS:[<&USER32.GetDlgItemText>]
00407A93 LEA EAX,DWORD PTR SS:[EBP-204]
00407A99 PUSH EAX
00407A9A CALL 是男人就.00407C5F
00407A9F ADD ESP,4
00407AA2 TEST EAX,EAX
00407AA4 JNZ 是男人就.00407AE1
; 从资源中载入字符串,是注册失败的提示信息
00407AAA PUSH 100
00407AAF LEA EAX,DWORD PTR SS:[EBP-104]
00407AB5 PUSH EAX
00407AB6 PUSH 4
00407AB8 MOV EAX,DWORD PTR DS:[40E200]
00407ABD PUSH EAX
00407ABE CALL DWORD PTR DS:[<&USER32.LoadStringA>]
; 下面就是弹出出错提示框的代码了
00407AC4 PUSH 10
00407AC6 PUSH 是男人就.0040D270
00407ACB LEA EAX,DWORD PTR SS:[EBP-104]
00407AD1 PUSH EAX
00407AD2 MOV EAX,DWORD PTR SS:[EBP+8]
00407AD5 PUSH EAX
00407AD6 CALL DWORD PTR DS:[<&USER32.MessageBoxA>]
00407ADC JMP 是男人就.00407B22
看到407AD6这里调用了MessageBoxA,往上看,很舒服地看到这一句:
00407AA4 JNZ 是男人就.00407AE1
一个很值得注意的跳转,是不是关键跳转呢,把JNZ改成JZ,再试试,点确定,出错提示是没有了,不过那个注册按钮还是在的,也就是说这句不是关键跳转,没有涉及到序列号的部分。再往上看,发现看在这个跳转之前有获取对话框中数据:
00407A8D CALL DWORD PTR DS:[<&USER32.GetDlgItemText>]
那么在这里中断看看它得到了什么。F2下断点,切到程序窗口,输入用户名和序列号,确定,预料之中断下来。接着F8单步运行,再来一个YES!它得到的是123456,也就是我填的序列号,接着把序列号所在的地址存到EAX中,再将EAX压入堆栈,紧接着又一个call,嗯,这个call很值得怀疑,跟进!下面就是整个call的函数的代码了,有点长:)
00407C5F PUSH EBP ; C函数标准开头
00407C60 MOV EBP,ESP
00407C62 SUB ESP,4 ; 局部变量,分析后面的代码可知是用来作计数器的
00407C65 PUSH EBX ; 保护寄存器
00407C66 PUSH ESI
00407C67 PUSH EDI
从代码开始可明显看出这是一个标准的C函数,接着往下看。碰到堆栈操作,这值得注意,因为call过来时有压入过一个参数,指向序列号起始地址:
00407C68 MOV EAX,DWORD PTR SS:[EBP+8] ; 这句之后EAX指向序列号
00407C6B XOR ECX,ECX
00407C6D MOV CL,BYTE PTR DS:[EAX+7] ; 得到序列号第8位
00407C70 TEST ECX,ECX ; 判断是否为0
00407C72 JE 是男人就.00407C7F ; 是刚跳转
00407C78 XOR EAX,EAX ; EAX清零
00407C7A JMP 是男人就.00407DC4 ; 这里跳到函数结尾
00407C7F MOV DWORD PTR SS:[EBP-4],0 ; 计数器清零
00407C86 JMP 是男人就.00407C8E
上面的代码的作用是得到序列号第8位,并判断是否为0,学过C的人都知道在C里字符串是以0表示结束的,那么就知道这里三句是用来判断用户输入的序列号是否大于7位的了,如果大于7位,就会将EAX清零并跳到函数结束的地方,而前面已经知道,在调用这个函数之后,如果返回值为0则序列号验证失败,所以序列号是7位或7位以内的。我填的123456,是6位,所以没有跳到函数结束,接着往下分析:
00407C8B INC DWORD PTR SS:[EBP-4] ; 计数器加1
00407C8E CMP DWORD PTR SS:[EBP-4],7 ; 判断计数器是否大于7
00407C92 JGE 是男人就.00407CC3 ; 大于等于就
00407C98 MOV EAX,DWORD PTR SS:[EBP-4] ; 取计数器值到EAX
00407C9B MOV ECX,DWORD PTR SS:[EBP+8] ; 取序列号地址到ECX
00407C9E MOV AL,BYTE PTR DS:[EAX+ECX] ; 取序列号第N位到AL,N为计数器值
00407CA1 PUSH EAX ; 压入堆栈,调用处理函数
00407CA2 CALL 是男人就.00407DC9 ; 将1位序列号从ASCII转换成二进制值
00407CA7 ADD ESP,4 ; 恢复堆栈
00407CAA XOR ECX,ECX ; ECX清零
00407CAC MOV CL,AL ; 将处理过的1位序列号存到CL
00407CAE CMP ECX,24 ; 判断是否大于0×24
00407CB1 JLE 是男人就.00407CBE ; 大于就出错了
00407CB7 XOR EAX,EAX ; 大于0×24,跳到函数结束,返回0
00407CB9 JMP 是男人就.00407DC4
00407CBE JMP 是男人就.00407C8B ; 继续处理下一位
很清楚的,上面这段代码是用来判断序列号中每一位是否符合要求,是什么要求呢,看到在每取得1位序列号之后有一句call:
00407CA2 CALL 是男人就.00407DC9
这个调用得看看,F7跟进:
00407DC9 PUSH EBP
00407DCA MOV EBP,ESP
00407DCC PUSH EBX
00407DCD PUSH ESI
00407DCE PUSH EDI
00407DCF XOR EAX,EAX
00407DD1 MOV AL,BYTE PTR SS:[EBP+8] ; 取参数,即1位序列号
00407DD4 CMP EAX,61
00407DD7 JL 是男人就.00407DE8
; 判断是否大于0×61,如果对ASCII码表熟悉的话可以知道0×61对应的是a
; 小于则跳转,好,上面那个判断应该不是用来判断这个字符是否为小写字母了
; 不是则跳到后面继续处理,是则进行下面的处理过程
00407DDD XOR EAX,EAX ; EAX清零
00407DDF MOV AL,BYTE PTR SS:[EBP+8] ; 取字符
00407DE2 SUB EAX,20 ; 减0×20,变大写字母
00407DE5 MOV BYTE PTR SS:[EBP+8],AL ; 保存
; 不管是否为小字字母,经过上面的判断和处理都会变成大写字母,继续进行处理
00407DE8 XOR EAX,EAX ; EAX清零
00407DEA MOV AL,BYTE PTR SS:[EBP+8] ; 取字符
00407DED CMP EAX,41
00407DF0 JL 是男人就.00407E01
; 判断是否大于,0×41对应的ASCII字符为A
; 小于则跳转,说明这里是判断字符是否为大写字母
; 不是则跳到后面继续处理,是则进行下面的处理过程
00407DF6 XOR EAX,EAX ; EAX清零
00407DF8 MOV AL,BYTE PTR SS:[EBP+8] ; 取字符
00407DFB SUB EAX,7 ; 减去7
00407DFE MOV BYTE PTR SS:[EBP+8],AL ; 保存
; 如果不是大写字母就跳到这里了,当然如果是大写字母的话
; 经过上面的处理过程同样要进行下面的处理过程
00407E01 XOR EAX,EAX ; EAX清零
00407E03 MOV AL,BYTE PTR SS:[EBP+8] ; 取字符
00407E06 SUB EAX,30 ; 减去0×30
00407E09 JMP 是男人就.00407E0E ; 函数结束
00407E0E POP EDI
00407E0F POP ESI
00407E10 POP EBX
00407E11 LEAVE
00407E12 RETN
连贯起来看可以知道这个处理函数的过程是这样的:
char是否为小写字母 -> 是则减去0×20变成大写字母,不是则保持不变 -> char是否为大写字母 -> 是则减去7,不是则保持不变 -> char减0×30
再联系数字和字母的ASCII码值,9为0×39,A为0×41,0×41-0×39=7,OK,可以知道这个函数是把1位序列号变成二进制值,字符0到9对应数值0到9,字母全部转换成大写字母,并且字母A对应10,B对应11,后面依次类推。
弄明白了字符到数字的函数,继续看序列号验证的代码,可以看到在得到二进制值之后,又来了一句:
00407CB1 JLE 是男人就.00407CBE ; 大于就出错了
00407CB7 XOR EAX,EAX ; 大于0×24,跳到函数结束,返回0
00407CB9 JMP 是男人就.00407DC4
00407CBE JMP 是男人就.00407C8B ; 继续处理下一位
0×24转换成十进制就是36,刚好是10个数字加26个字母,也就是说序列号只允许数字与字母,并且要7位,否则就通不过这个验证了。弄明白了这个,继续往下看:
00407CC3 MOV EAX,DWORD PTR SS:[EBP+8]
00407CC6 MOV AL,BYTE PTR DS:[EAX+5] ; 取序列号第6位
00407CC9 PUSH EAX
00407CCA CALL 是男人就.00407DC9 ; 转换成数值
00407CCF ADD ESP,4
00407CD2 XOR EBX,EBX
00407CD4 MOV BL,AL ; 序列号第6位值保存到EBX
00407CD6 MOV EAX,DWORD PTR SS:[EBP+8]
00407CD9 MOV AL,BYTE PTR DS:[EAX+2] ; 取序列号第3位
00407CDC PUSH EAX
00407CDD CALL 是男人就.00407DC9 ; 转换成数值
00407CE2 ADD ESP,4
00407CE5 XOR ECX,ECX
00407CE7 MOV CL,AL ; 序列号第3位值保存到ECX
00407CE9 MOV ESI,24 ; ESI=0×24
00407CEE LEA EAX,DWORD PTR DS:[ECX+EBX*2+1D] ; EAX=ECX+EBX*2+1D
00407CF2 CDQ ; 扩展成64位
00407CF3 IDIV ESI ; 除以0×24
00407CF5 MOV EBX,EDX ; EDX为余数,存到EBX
00407CF7 MOV EAX,DWORD PTR SS:[EBP+8]
00407CFA MOV AL,BYTE PTR DS:[EAX] ; 取序列号第1位
00407CFC PUSH EAX
00407CFD CALL 是男人就.00407DC9 ; 转换成数值
00407D02 ADD ESP,4
00407D05 XOR ECX,ECX
00407D07 MOV CL,AL ; 序列号第1位值存到ECX
00407D09 CMP EBX,ECX ; EBX=ECX?
00407D0B JNZ 是男人就.00407DBD ; 不等,验证失败,跳到函数结束
; 头有些大了,用心看发现这里在判断序列号是否符合规则
; (第3位+第6位*2+0×1D)%0×24==第1位
; 跟着又是两段代码类似的,不难发现也是判断,不过规则有小小的不同
; 下面这段的判断规则是
; (第2位+第5位*2+0×1D)%0×24==第7位
00407D11 MOV EAX,DWORD PTR SS:[EBP+8]
00407D14 MOV AL,BYTE PTR DS:[EAX+4]
00407D17 PUSH EAX
00407D18 CALL 是男人就.00407DC9
00407D1D ADD ESP,4
00407D20 XOR EBX,EBX
00407D22 MOV BL,AL
00407D24 MOV EAX,DWORD PTR SS:[EBP+8]
00407D27 MOV AL,BYTE PTR DS:[EAX+1]
00407D2A PUSH EAX
00407D2B CALL 是男人就.00407DC9
00407D30 ADD ESP,4
00407D33 XOR ECX,ECX
00407D35 MOV CL,AL
00407D37 MOV ESI,24
00407D3C LEA EAX,DWORD PTR DS:[ECX+EBX*2+1D]
00407D40 CDQ
00407D41 IDIV ESI
00407D43 MOV EBX,EDX
00407D45 MOV EAX,DWORD PTR SS:[EBP+8]
00407D48 MOV AL,BYTE PTR DS:[EAX+6]
00407D4B PUSH EAX
00407D4C CALL 是男人就.00407DC9
00407D51 ADD ESP,4
00407D54 XOR ECX,ECX
00407D56 MOV CL,AL
00407D58 CMP EBX,ECX
00407D5A JNZ 是男人就.00407DBD
; 下面这段的判断规则是
; (第1位+第7位*2+0×1D)%0×24==第4位
00407D60 MOV EAX,DWORD PTR SS:[EBP+8]
00407D63 MOV AL,BYTE PTR DS:[EAX+6]
00407D66 PUSH EAX
00407D67 CALL 是男人就.00407DC9
00407D6C ADD ESP,4
00407D6F XOR EBX,EBX
00407D71 MOV BL,AL
00407D73 MOV EAX,DWORD PTR SS:[EBP+8]
00407D76 MOV AL,BYTE PTR DS:[EAX]
00407D78 PUSH EAX
00407D79 CALL 是男人就.00407DC9
00407D7E ADD ESP,4
00407D81 XOR ECX,ECX
00407D83 MOV CL,AL
00407D85 MOV ESI,24
00407D8A LEA EAX,DWORD PTR DS:[ECX+EBX*2+1D]
00407D8E CDQ
00407D8F IDIV ESI
00407D91 MOV EBX,EDX
00407D93 MOV EAX,DWORD PTR SS:[EBP+8]
00407D96 MOV AL,BYTE PTR DS:[EAX+3]
00407D99 PUSH EAX
00407D9A CALL 是男人就.00407DC9
00407D9F ADD ESP,4
00407DA2 XOR ECX,ECX
00407DA4 MOV CL,AL
00407DA6 CMP EBX,ECX
00407DA8 JNZ 是男人就.00407DBD
上面这三段就是序列号验证的主要部分了,写个示意表达式,用b1~b7表示序列号第1至7位转换后的数值,T为临时变量:
T = b3 + b6*2 + 1D
b1 = T mod 24 ?
T = b2 + b5*2 + 1D
b7 = T mod 24 ?
T = b1 + b7*2 + 1D
b4 = T mod 24 ?
如果三次判断均通过,那么整个序列号验证函数就回返回1,表示验证通过:
00407DAE MOV EAX,1 ; EAX=1
00407DB3 JMP 是男人就.00407DC4 ; 跳到返回
00407DB8 JMP 是男人就.00407DC4
00407DBD XOR EAX,EAX ; 任何验证的情况下均返回0
00407DBF JMP 是男人就.00407DC4
00407DC4 POP EDI
00407DC5 POP ESI
00407DC6 POP EBX
00407DC7 LEAVE
00407DC8 RETN
到这里就差不多了,序列号验证函数已经分析完成,它的验证规则也知道了,接着是写注册机了,不过我一开始没想到怎么随机产生一个序列号,只好把正确的序列号全列出来了,看了一下,有4万多个……我用C写了个列序列号的程序,用的穷举法,列出每一个序列号,判断是否可用,如果可用就打印出来,程序清单如下:
#include <stdio.h>
int main()
{
char bin2char(int bin);
int tmp,b1,b2,b3,b4,b5,b6,b7;
printf(“register codes:\n”);
for(b1=0;b1<36;b1++)
for(b2=0;b2<36;b2++)
for(b3=0;b3<36;b3++)
for(b4=0;b4<36;b4++)
for(b5=0;b5<36;b5++)
for(b6=0;b6<36;b6++)
for(b7=0;b7<36;b7++)
{
tmp = b3 + b6*2 + 0×1D;
if (b1!=tmp%0×24) break;
tmp = b2 + b5*2 + 0×1D;
if (b7!=tmp%0×24) break;
tmp = b1 + b7*2 + 0×1D;
if (b4!=tmp%0×24) break;
printf(“%c%c%c%c%c%c%c\n”,
bin2char(b1),bin2char(b2),bin2char(b3),bin2char(b4),bin2char(b5),bin2char(b6),bin2char(b7));
}
}
char bin2char(int bin)
{
if(bin<10) return(bin+0×30);
else return(bin-10+0×41);
}
函数bin2char是序列号验证过程中字符转数值的逆过程。
是男人就下100层的破解就到此结束了,后来我又看了一下序列号的规律,发现第7位总是为0,而第4位决定第1位,而第3位与第6位,第2位与第5位分别为一组。这样,随机产生b2,b3,b4就可以生成一个序列号了。另外,也可用暴力破解的方式,在序列号验证函数中直接将EXA=1并返回,这样不论什么序列号都能通过验证了:)
OK,大功告成。写的有些啰嗦,第一次写破解的文章,有错误也是难免的,欢迎指正^_^,vipxjw#163.com。
[VB] 一个操作多线程的类
May 10th
根据点睛工作室的多线程类改写的,不过没有经过严格测试,好像工作不是很稳定,如果使用不当会出错的说。
Option Explicit
Private Type SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Declare Function CreateThread Lib “kernel32″ (lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, ByVal lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long
Private Declare Function CreateThreadL Lib “kernel32″ Alias “CreateThread” (ByVal lpThreadAttributes As Long, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, ByVal lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long
Private Declare Function TerminateThread Lib “kernel32″ (ByVal hThread As Long, ByVal dwExitCode As Long) As Long
Private Declare Function SetThreadPriority Lib “kernel32″ (ByVal hThread As Long, ByVal nPriority As Long) As Long
Private Declare Function GetThreadPriority Lib “kernel32″ (ByVal hThread As Long) As Long
Private Declare Function ResumeThread Lib “kernel32″ (ByVal hThread As Long) As Long
Private Declare Function SuspendThread Lib “kernel32″ (ByVal hThread As Long) As Long
Private Declare Function GetCurrentThread Lib “kernel32″ () As Long
Private Declare Function GetCurrentThreadId Lib “kernel32″ () As Long
Private Declare Sub ExitThread Lib “kernel32″ (Optional ByVal dwExitCode As Long = 0)
Private Declare Function GetLastError Lib “kernel32″ () As Long
‘ 常数常量
Private Const MAXLONG = &H7FFFFFFF
‘ 线程优先级常量
Private Const THREAD_BASE_PRIORITY_IDLE = -15
Private Const THREAD_BASE_PRIORITY_LOWRT = 15
Private Const THREAD_BASE_PRIORITY_MAX = 2
Private Const THREAD_BASE_PRIORITY_MIN = -2
Private Const THREAD_PRIORITY_HIGHEST = THREAD_BASE_PRIORITY_MAX
Private Const THREAD_PRIORITY_IDLE = THREAD_BASE_PRIORITY_IDLE
Private Const THREAD_PRIORITY_LOWEST = THREAD_BASE_PRIORITY_MIN
Private Const THREAD_PRIORITY_NORMAL = 0
Private Const THREAD_PRIORITY_TIME_CRITICAL = THREAD_BASE_PRIORITY_LOWRT
Private Const THREAD_PRIORITY_ABOVE_NORMAL = (THREAD_PRIORITY_HIGHEST – 1)
Private Const THREAD_PRIORITY_BELOW_NORMAL = (THREAD_PRIORITY_LOWEST + 1)
Private Const THREAD_PRIORITY_ERROR_RETURN = (MAXLONG)
‘ 线程创建标志
Private Const CREATE_ALWAYS = 2
Private Const CREATE_NEW = 1
Private Const CREATE_NEW_CONSOLE = &H10
Private Const CREATE_NEW_PROCESS_GROUP = &H200
Private Const CREATE_NO_WINDOW = &H8000000
Private Const CREATE_PROCESS_DEBUG_EVENT = 3
Private Const CREATE_SUSPENDED = &H4
Private Const CREATE_THREAD_DEBUG_EVENT = 2
‘ 线程优先级结构
Public Enum ThreadPriority
Lowest = THREAD_PRIORITY_LOWEST
BelowNormal = THREAD_PRIORITY_BELOW_NORMAL
Normal = THREAD_PRIORITY_NORMAL
AboveNormal = THREAD_PRIORITY_ABOVE_NORMAL
Highest = THREAD_PRIORITY_HIGHEST
End Enum
Private mAttrib As SECURITY_ATTRIBUTES
Private mEnabled As Boolean
Private mThreadHandle As Long
Private mThreadID As Long
Private mTerminate As Boolean
Public Property Get ThreadID() As Long
ThreadID = mThreadID
End Property
Public Property Get ThreadHandle() As Long
ThreadHandle = mThreadHandle
End Property
Public Function Create(ByVal cFunction As Long, Optional ByVal cPriority As ThreadPriority, Optional ByVal cEnabled As Boolean = True) As Long
Dim CreateFlag As Long
If mThreadHandle <> 0 Then TerminateMe
mEnabled = cEnabled
If mEnabled Then
CreateFlag = CREATE_NEW
Else
CreateFlag = CREATE_SUSPENDED
End If
mAttrib.nLength = Len(mAttrib)
mThreadHandle = CreateThreadL(0, 0, cFunction, 0&, CreateFlag, mThreadID)
If mThreadHandle = 0 Then
MsgBox “Create thread failed!Error code:” & GetLastError, vbOKOnly Or vbCritical, “Error”
End If
End Function
Public Sub SuspendMe()
If mThreadHandle = 0 Or mEnabled = False Then Exit Sub
If SuspendThread(mThreadHandle) <> -1 Then
mEnabled = False
Else
MsgBox “Suspend thread failed!Error code:” & GetLastError, vbOKOnly Or vbCritical, “Error”
End If
End Sub
Public Sub ResumeMe()
If mThreadHandle = 0 Or mEnabled = True Then Exit Sub
If ResumeThread(mThreadHandle) <> -1 Then
mEnabled = True
Else
MsgBox “Resume thread failed!Error code:” & GetLastError, vbOKOnly Or vbCritical, “Error”
End If
End Sub
Public Sub TerminateMe()
If mThreadHandle = 0 Then Exit Sub
TerminateThread mThreadHandle, 0
mThreadHandle = 0
‘mTerminate = True
‘Do
‘ DoEvents
‘Loop Until mTerminate
End Sub
Private Sub Class_Terminate()
‘TerminateThread mThreadHandle, 0
End Sub
用汇编实现符串操作函数
Aug 30th
不管是在系统开发还是在平时的编程当中,字符串操作都是很重要的一部分。在C中,已经有库提供了strcpy、strcmp、strcat等函数, 而在开发用汇编开发自己的系统时,并没有现在的库可用,这就要求我们自己来实现字符串操作了。以下如果没有特别说明,字符串均以0为结束标志。
strcpy 字 符串复制
在字符串复制当中,为了简便,可以像在C中一样,不考虑边界问题,把这个问题交调用者,不过这样就有可能产生缓冲区溢出 了:)字符串复制还是比较容易实现的,只要在复制每一个字节之前判断是不是0,如果是就结束,不是则继续复制下一个字节。我给出一个简单的例子,当然,你 可以把它优化以产生更好的性能。
strcpy:
; in si 源字符串起始地址
; di 目标地 址
; out 无
push si
push di ; 保护寄存器
next:
lodsb ; 载 入一个字节
or al,al ; 是0吗?
je end ; 是则结束
stosb ; 不 是则放入目标中
jmp next ; 继续下一个字节
end:
mov [di],byte 0 ; 结 束标志
pop di ; 恢复寄存器
pop si
ret
strlen 取 字符串长度
应该说,这一个比上一个容易,因为这个只需要考虑什么结束,而不需要去复制字节。
strlen:
; in si 源 字符串
; out ax 字符串长度
push si
push cx ; 保护寄存器
xor cx,cx ; 计 数器清零
next:
lodsb ; 载入一个字节
or al,al ; 是0?
je end ; 是 则结束
inc cx ; 字符串长度+1
jmp next ; 继续下一个字节
end:
mov ax,cx ; 将 字符串长度放到AX中作为返回值
pop bx ; 恢复寄存器
pop si
ret
strcat 字 符串连接
就个人来说,字符串连接用得并不是很多,但既然在C中有这个函数,就必然有它存在的理由,所以我们还是有必要来实现它的。 同样的,在字符串连接时不考虑目标缓冲区是否足够的问题,把这个交给调用者。
要将一个字符串连接到另一个字符之后,就需要先找出目 标字符串的结尾,即0的地址,然后就可以把这个地址做为目标,把需要连接的字符串的首地址做为源,调用strcpy即可完成。而找出0的地址,原理和取字 符串长度一样,载入一个字节然后判断是否为0。这个留大家自己去实现。
上面的例子使用的寄存器都是16位的,所以只能用在16位的 程序当中,当然,如果是你自己写的,那么自己懂得它实现的原理,移植到32位就是件很容易的事。
今天这篇就到这里喽~~难得写教 程之类的文章,今天还算顺手^-^~~
