Category Archives: Desktop

REALbasic 中使用结构体作为 Win32 API 的参数及使用 Win32 API 停止服务

在之前,我使用 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 这个结构体也可以直接通过工程菜单添加。

在传参的时候需要注意两点:

  1. 如果是字符串类型的参数并且调用的接口是 Unicode 类型的,那么需要在传入参数之前,将参数值转换为 Unicode 编码。可以通过 ConvertEncoding(text, Encodings.UTF16) 来转换。
  2. 如果参数是结构体,那么参数前的传参方式就要写 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 特性的应用程序也不是难事:)

参数资料

  1. VC++启动和停止服务, huangchonghai
  2. QueryServiceStatus, MSDN

关于 REALbasic 中使用 AutoDiscovery 时发生错误 40 的问题

今天在使用 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 中的内容

本篇所讲关于 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 执行命令

    在 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 Integer soft 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实现图片的缩略图浏览

    要写一个程序,涉及到缩略图的问题,原先准备用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 Long
    For i = 0 To File1.ListCount – 1
    ‘ 载入图像
    Picture1.Picture = LoadPicture(File1.Path & “\” & File1.List(i))
    ‘ 生成缩略图
    Picture2.PaintPicture Picture1.Picture, 0, 0
    ‘ 添加缩略图到ImageList
    ImageList1.ListImages.Add , , Picture2.Image
    ‘ 添加缩略图到ListView
    ListView1.ListItems.Add , , File1.List(i), i + 1
    DoEvents
    Next
    End Sub
    ‘ 窗体改变大小时改变ListView的大小
    Private Sub Form_Resize()
    With ListView1
    .Top = 0
    .Left = 0
    .Width = Me.ScaleWidth
    .Height = Me.ScaleHeight
    End With
    End Sub
    这样就差不多可以实现图片的缩略图浏览了。

    用VB写一个你自己的Flash播放器

    前天在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层,交出序列号来

    某天(前天:),看了两个小时高数(具体不详:)之后,想放松一下,找到《是男人就下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+0x1D)%0×24==第1位 ; 跟着又是两段代码类似的,不难发现也是判断,不过规则有小小的不同 ; 下面这段的判断规则是 ; (第2位+第5位*2+0x1D)%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+0x1D)%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 + 0x1D; if (b1!=tmp%0×24) break; tmp = b2 + b5*2 + 0x1D; if (b7!=tmp%0×24) break; tmp = b1 + b7*2 + 0x1D; 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] 一个操作多线程的类

    根据点睛工作室的多线程类改写的,不过没有经过严格测试,好像工作不是很稳定,如果使用不当会出错的说。

    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

    用汇编实现符串操作函数

    不管是在系统开发还是在平时的编程当中,字符串操作都是很重要的一部分。在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位就是件很容易的事。

    今天这篇就到这里喽~~难得写教 程之类的文章,今天还算顺手^-^~~

    BMP 文件格式学习

    由于改进IMAGELIS的需要,我对BMP文件的格式进行了一些研究,写在这里做为学习笔记,也给需要的朋友做为参考。如果没有特别说明,我所用的位图文件都是由WinowXP自带的画图产生。

    BMP文件格式在网上已经很多教程之类的,不过我这里讲的是一些它们没提到的。

    1.数据区起始位置

    我在网上找到许多有关BMP文件格式的说明都说数据区起始位置在相对文件开头51个字节处,但我却发现数据区起始在相关文件开头55个字节处,不知是网上的错了还是我错了,不过我这样用了好像也没出错,有空再研究研究。

    2.像素保存顺序

    位图保存时扫描顺序是从下至上,从左至右,即在位图数据区,位图第一行保存在位图数据区的最后,而位图最后一行保存在位图数据区的开始,例有一个4×4的位图,那么它有4行,4列,像素保存在数据区的顺序是这样的:

    line3 pixel 0 1 2 3

    line2 pixel 0 1 2 3

    line1 pixel 0 1 2 3

    line0 pixel 0 1 2 3

    因此,如果我们要自己产生一个位图文件,那么保存时就应该从最后一行,第一个像素开始保存,保存好一行后就往上一行直到第一行保存完毕,当然,我们必须在位图数据之前加上位图文件头,文件头的格式可以在网上找资料。

    3.行字节对齐

    可能是为了数据区的读取更快速,BMP格式规定一行的字节数必须是4的倍数,如果不足,就用0补足。例如有一幅5×5的24位色位图,那么本来每一行的字节数是24/8*5=15个字节,但15不是4的倍数,还需要1个字节来补足,于是在数据区这一行的数据就是:

    pixel 0 1 2 3 4 0×00

    同理如果宽度是7那么就需要补3个字节等等。如果是12×12单色位图,那么一行有12/2=6个字节,就需要在每一行的数据的最后补2个字节。

    这个补0的问题很多朋友在处理位图的时候没有注意,以致于程序在处理诸如32×32之类的位图时能很好的运行,而处理如31×32这样的就不行了。

    4.颜色数少于256时的数据位

    在颜色数少于256时一个字节里就可能保存1个或者更多个像素,这时就要注意像素在字节里的保存顺序了。像素在字节内保存的顺序原则是前面的像素在高位,后面的像素在低位。例:

    在16色位图里,每一个像素需要4个bit来保存,因此1个字节里就保存了两个像素,假如有一个2×1的16色位图,第一个像素是黑色,数据是0×0,第二个像素白色,数据是0xF,那么根据上面的原则,这两个像素在字节里是这样保存的:

    pixel 0      1

    bits  0000 1111

    同理,如果是单色位图,也是前面的像素在高位,后面的像素在低位。

    以上呢就是我在研究BMP格式时得到的一些经验,当然,如果有错或者有什么问题可以给我写信:)

    .