首页
社区
课程
招聘
OllyMachine问答与技巧集
2004-12-1 19:52 21580

OllyMachine问答与技巧集

2004-12-1 19:52
21580
OllyMachine 0.10版于2004年11月15日发布,今天是12月1日,0.19版,在短短的半个月内它已经升级了10次了。从0.10到0.19,我个人认为它已经相对比较成熟了,同时我也看到了开始有人在使用我的这个程序,还有许多热心的朋友给我提出了许多宝贵的意见和建议,真的感到很开心,感谢大家的支持。

由于OllyMachine是由我一个人单独开发并维护的,所以有些细节大家可能会不清楚。在这里,我打算写一个问答与技巧集,算是一个与大家交流的机会。同时,如果在使用OllyMachine的过程中存在任何疑问,或者建议,请在这里跟帖,我将会尽量给出令您满意的答复。

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞1
打赏
分享
最新回复 (64)
雪    币: 892
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
fly 85 2004-12-1 19:57
2
0
老罗真勤劳负责
佩服呀  :)
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-1 20:08
3
0
问:OllyMachine是用什么语言写的?代码量有多少?第一个版本开发了多长时间?
答:OllyMachine是用VC 6.0开发的。0.19版的源代码大约为10000行。它由两个大类(class)组成:Assembler和VM(Virtual Machine)。其中Assembler类大约是2900行,VM类大约是5700行。另外还有大约1000行的调度代码,以及500行的linker和loader。第一个版本,即0.10版开发了大约1个月。别看Assembler类才只有VM类的一半大小,但其实最难的部分是在Assembler,VM是很简单的。

问:已经有一个OllyScript了,为什么还要开发OllyMachine呢?
答:OllyScript是一个比较成熟、已有很多使用者使用的脚本语言,但在我作为编译器学习者的眼光看来,它还主要存在以下可以改进的地方:
1、解释执行。
2、API库不够丰富。
解释执行这一工作方式决定了它的运行速度不可能达到很快,因为语法和词法分析是很耗费时间的。另外,我觉得它的API库还是不够丰富。
还有一个很重要的原因是,我一直以来都很想开发一个编译器,用来提高自己的水平,哪怕这个编译器是很简单的也好。借助OllyMachine,可以让我达成这个心愿。
雪    币: 85179
活跃值: (198515)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
linhanshi 2004-12-1 20:20
4
0
支持!!!
雪    币: 396
活跃值: (1078)
能力值: ( LV9,RANK:970 )
在线值:
发帖
回帖
粉丝
simonzh2000 24 2004-12-1 20:38
5
0
厉害啊.

看来搞破解没有出息, 要转向开发啊.
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-1 20:47
6
0
问:OllyMachine的工作流程是?
答:OllyMachine首先会把脚本源代码进行编译,转换成机器码,然后把机器码提交给虚拟机去执行。因此,编译和运行其实是两个单独的步骤,我们也可以把源代码编译成机器码并保存成OllyMachine的字节码文件,那么以后再运行的时候,就不必重新执行编译步骤了,达到加快速度的目的。(尽管这个速度我们一般是感觉不到的)

问:OllyMachine的字节码文件的格式是?
答:它的字节码文件的格式比较简单,如下:

文件头:
偏移0~3:  Signature,标记,占用4个字节,永远是"OMLC"。
偏移4:     LinkerVersionHi,1个字节,表示版本的高位。
偏移5:     LinkerVersionLo,1个字节,表示版本的低位。
偏移6~9:  SizeOfCode,4个字节,表示代码段的长度。
偏移10~13:SizeOfData,4个字节,表示数据段的长度。
偏移14~17:BaseOfCode,4个字节,表示代码段在文件中的起始偏移。
偏移18~21:BaseOfData,4个字节,表示数据段在文件中的起始偏移。

可见,文件头永远是22个字节,也就是16进制的0x16。
文件头结束后,紧跟着就是代码段的代码,以及数据段的数据。
下面我们来看一个例子:

invoke msg, "Hello World!"


这是一个最简单的Hello World程序,用0.19版的OllyMachine编译,生成的字节码文件内容为:

Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F

00000000 4F 4D 4C 43 00 13 08 00 00 00 0D 00 00 00 16 00 OMLC............
00000010 00 00 1E 00 00 00 1C 00 00 00 00 00 24 1E 48 65 ............$.He
00000020 6C 6C 6F 20 57 6F 72 6C 64 21 00 llo World!.


对照上面的文件头,可以知道:

偏移0~3:  "OMLC"
偏移4:     0,表示版本号的高位是0
偏移5:     0x13,也就是10进制的19,表示版本号的低位是19
偏移6~9:  0x08,表示代码段为8个字节长。
偏移10~13:0x0d,表示数据段为13个字节长。为什么呢?因为"Hello World!"是12个字节,再加上一个'\0'作为字符串的结尾,一共就是13个字节。
偏移14~17:0x16,表示代码段从文件的0x16偏移处开始。
偏移18~21:0x1e,表示数据段从文件的0x1e偏移处开始。

朋友们可以自己编译一个文件,试着对照一下。
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-1 21:11
7
0
问:OllyMachine和OllyScript相比,有哪些不足?
答:在OllyMachine 0.19版中:
1、OllyMachine不能支持自定义变量名,我个人只能很抱歉地对朋友们说声对不起了,要实现这个功能,必须做语义分析(但OllyScript是没有做这个工作的,不严谨,例如对字符串的变量可以inc,我认为就是不合理的),我现在实在是没有时间去完成。为了弥补这个缺陷,我设置了OllyMachine的寄存器为reg00~reg64,一共有65个可供自由使用的寄存器,理论上应该是足够了。
2、OllyMachine不支持间接寻址。例如:mov eax, [ebx] 在OllyMachine里面是不允许的。这并不是我在技术上无法做到,而是我现在没有那么多时间去改动语法分析器。不过,使用者可以通过ReadMemLong和WriteMemLong这两个API达到同样的目的,只是没有那么方便罢了。

问:OllyMachine和OllyScript相比,有哪些超越的地方?
答:我们知道,事物是会不断发展的,OllyScript目前的更新已经比较慢了,但这并不意味着将来它不会再更新,因此,我们现在暂且拿OllyMachine 0.19和OllyScript 0.92进行对比(都是各自的最新版):
1、OllyMachine支持编译。这意味着程序员在发布自己的脚本程序时,可以发布一个编译好的版本,别人很难反编译出它的源代码来。
2、OllyMachine的API库更加丰富。这是根据大家的需求慢慢增加和修正的,感谢大家一直以来的大力支持。

总结来说,其实OllyMachine和OllyScript没有太多本质上的不同,也没有谁是更强大的,脚本只是一种辅助大家分析的工具,进行太多的比较是没有意义的(就如比较VC好还是Delphi好一样,没有任何意义),朋友们应该根据自己的使用习惯,选择一种能够满足自己需求的脚本语言。
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-1 21:23
8
0
问:应该如何调用OllyMachine的API?有哪些方式?
答:由于历史遗留的原因,API的调用方式有一个演化的过程:最开始是必须把每个参数先push进去,最后才写API名字的,后来发展成为可以通过invoke宏来调用。总结一下,举个例子:

最开始的push方式:
push "Hello World!"
msg


演变之后的invoke宏方式:
invoke msg, "Hello World!"


两种方式都是允许的,但也许大部分朋友都不知道,还有第三种“彩蛋”方式:

invoke msg "Hello World!"
invoke PrintNum eip 0x16


也就是,在invoke宏方式中,可以完全不加逗号。

此外,如果某个API是没有参数的,那么既可以省略invoke宏,也可以加上。例如:

run
invoke run


都是允许的。但不推荐使用invoke,因为在Assembler中,要对invoke宏进行一次宏扩展,这样会浪费编译的时间。
雪    币: 207
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
烈火劲风 2004-12-2 00:05
9
0
有中文版吗?
雪    币: 381
活跃值: (786)
能力值: ( LV12,RANK:730 )
在线值:
发帖
回帖
粉丝
askformore 18 2004-12-2 10:22
10
0
两种方式都是允许的,但也许大部分朋友都不知道,还有第三种“彩蛋”方式:

引用:
--------------------------------------------------------------------------------
invoke msg "Hello World!"
invoke PrintNum eip 0x16
--------------------------------------------------------------------------------


invoke PrintNum eip 0x16
似乎是 22 进制

因为我不是程序员,不太会站在你们的角度思考,下面是我的想法和实践:

inputhexlong API 输入控制不够严谨:
1.输入长度大于8个字符,而不是控制在8个字符内(hexlong,或者我理解错)
2.允许输入非(0~9或A~F)字符,不作检测否定
比如:输入了 00423dfi ,i是非法字符,但是一时大意快速按错了键(本应按8或9的),再快速按下确定,就变成了...;你可以进行输入实时检测,也可以在用户点按确定后检测(不合要求重新要求输入)。
3.我看初始值没什么必要吧,会增加书写 API 的长度,初始值你自定为 Null 好了,不竟我们关心的是输入值和标题含义!

如何才能将输入的字符串写入到指定的内存地址?

比如:请输入你的注册名:,注册名要求写入到内存地址为 401000 处

invoke inputtext,"please input regname:"
invoke logText,FreeBufferReg
...跟着该怎么去组织...没有头绪...请验证成功把指令写出来...

另外,可简化一下 API (考虑在 8 个字符内好吗)吗?
如:FreeBufferSizeReg
变:FbufSize :p

老罗帮我看看我的问题
[url]http://bbs.pediy.com/showthread.php?s=&threadid=7779[/ur;]
你会的可以在那里答复就可以了!:)
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-2 11:13
11
0
to askformore:

// invoke PrintNum eip 0x16
// 似乎是 22 进制

呵呵,我并没有说过PrintNum只能显示10和16两种进制啊,实际上任何进制都是允许的,你可以试试2进制、8进制、32进制、874进制……(但太奇怪的进制可能会导致输出结果在显示上出问题)

// inputhexlong API 输入控制不够严谨:
// 1.输入长度大于8个字符,而不是控制在8个字符内(hexlong,或者我理解错)

HexLong的意思是,在计算机的内部用long来表示一个数值,这跟在输入框中输入多少个字符没有什么直接关系的。

InputHexLong的内部实现,我是把接口直接传给OllyDbg的导出API:Getlong()来完成的。这是我的实现代码:

nRetCode = Getlong(
szTitle,
(unsigned long *)&lValue,
4, // datasize
0, // init letter
DIA_HEXONLY
);
if (-1 == nRetCode) // fails or user pressed CANCEL
goto Exit0;

所以实际上是由它来进行处理的,我没有进行更多的处理。用户能做的千奇百怪的操作实在是太多了,我只能保证Getlong()的返回结果是正确的,至于判断合法字符...我们要爱生活,爱猫扑。

InputHexLong的初始值还是保留吧,API的接口是不能变的,否则以前的脚本就会出错了。

// 如何才能将输入的字符串写入到指定的内存地址?

很抱歉,暂时没有办法。。。

// 另外,可简化一下 API (考虑在 8 个字符内好吗)吗?
// 如:FreeBufferSizeReg
// 变:FbufSize

如前面所说,API的接口是不能变的。按照程序员的思维,是不在乎多打几个字符的,毕竟源代码是给人看的,越详细越容易理解就越好,其实刚刚开始看OllyScript的脚本时,我就经常搞不清楚那些2、3个字母长的指令是什么意思...

// 老罗帮我看看我的问题
// http://bbs.pediy.com/showthread.php?s=&threadid=7779
// 你会的可以在那里答复就可以了!

很抱歉啊!界面编程是我的弱项,我几乎是一窍不通。
雪    币: 1223
活跃值: (469)
能力值: (RANK:460 )
在线值:
发帖
回帖
粉丝
monkeycz 11 2004-12-2 11:18
12
0
支持老罗!:D
雪    币: 247
活跃值: (265)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
peansen 6 2004-12-2 12:17
13
0
支持
正在使用中
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-2 13:10
14
0
问:include指令有什么功能?应该如何用好它?
答:include是用来包含另外一个文件的。例如:

文件a.oms:

invoke msg, "这是文件a!"


文件b.oms:

invoke msg, "这是文件b!"


文件c.oms:

include "a.oms"
include "b.oms"


那么,在运行程序c的时候,就会顺次显示出两个对话框:"这是文件a!" 和 "这是文件b!"。

注意事项:

1、include指令在哪里,就会把另外文件include到哪里,因此可以在需要的地方进行include。
2、由于OllyMachine里面没有过程和子函数这两个概念,每个文件中的标号(label)都是全局的,因此,如果使用了include指令来包含别的文件,那么在脚本文件1中的寄存器的值在脚本2、3、4...里面都有效,并且不允许在include的文件中有相同名的label。

根据上面两点,我们可以很容易地想到,其实include指令最大的用处是进行代码的模块化。例如,我们可以把一些经常用到的功能写成一个文件,假设我们经常要用16进制输出一个数值进行调试,那么可以写成:

文件 PrintNum.oms:

invoke PrintNum, reg01, 16


在需要的时候就可以这样了:

// ...
mov reg01, 0x401000
include "PrintNum.oms"
// ...
mov reg01, 0x402000
include "PrintNum.oms"


用模块化编程最大的好处就是代码可以复用,维护很方便,例如假如哪天我们不想用16进制输出了,而想改成10进制输出,那么只需要把PrintNum.oms里面的16改成10就可以了,其他的地方一句都不用再改,一劳永逸。

综上所述,我的建议是:只要没有标号(label)重复,请尽量把可以复用的代码写成模块化的文件。这是一种良好的编程素养。
雪    币: 513
活跃值: (2228)
能力值: ( LV9,RANK:2130 )
在线值:
发帖
回帖
粉丝
loveboom 53 2004-12-2 17:54
15
0
:D
建议加下
@F,@B那样对只有一次label可以省了:D

lbl1:
  inc reg01
  cmp reg01,1
  je @F

@@:
  invoke msg "Hello World!"
  ret
雪    币: 513
活跃值: (2228)
能力值: ( LV9,RANK:2130 )
在线值:
发帖
回帖
粉丝
loveboom 53 2004-12-2 18:16
16
0
start:
  invoke stepintos,2
  mov reg01,esp
  add reg01,0x04
  invoke bphws reg01,2
  run

lbl1:
  invoke bphwc,reg01
  invoke stepintos,2

end:
  invoke comment eip,"oep"
  halt
本想写个OM脚本,结果写个这么简单的也出错
帮我看看错在哪里 .
这是jdpack/jdprotect的脚本
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-2 19:18
17
0
最初由 loveboom 发布
:D
建议加下
@F,@B那样对只有一次label可以省了:D

lbl1:
........


汗...
这个有点难度
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-2 19:22
18
0
最初由 loveboom 发布
start:
invoke stepintos,2
mov reg01,esp
add reg01,0x04
invoke bphws reg01,2
run


lbl1:
invoke bphwc,reg01
invoke stepintos,2

end:
invoke comment eip,"oep"
halt


第5和第14行出错了,应该改成:

// start:
invoke stepintos, 2
mov reg01, esp
add reg01, 0x04
invoke bphws, reg01, 2
run


// lbl1:
invoke bphwc, reg01
invoke stepintos, 2

// end:
invoke comment, eip, "oep"
halt


在API名字的后面必须有一个逗号。另外,如果标号没有被引用,则会在编译的时候出warning,所以建议把没有用过的标号注释掉。;)
雪    币: 282
活跃值: (233)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
wangli_com 5 2004-12-2 21:26
19
0
HideOD
EOB conversion
EOE continue

  invoke GetProcAddress,  "OpenMutexA","kernel32.dll"
  invoke BP,reg00
  run
  jmp  setbreakpoint
conversion:

__asm
{
    PUSHAD
    PUSH EDX
    xor eax,eax
    push eax
    push eax
    CALL kernel32.CreateMutexA
    POPAD
    jmp kernel32.OpenMutexA
}
invoke BC,reg00
RunToUserCode
COB

continue:
ESTO

setbreakpoint:
  pause

为什么在OpenMutexA函数里出不来了,清指教,我哪写错了?
雪    币: 319
活跃值: (2304)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
csjwaman 24 2004-12-3 08:05
20
0
请问laoluo:
FindOpcode和Find有什么区别?
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-3 10:12
21
0
最初由 wangli_com 发布
HideOD
EOB conversion
EOE continue

invoke GetProcAddress, "OpenMutexA","kernel32.dll"
........


我不知道应该怎么解释,这个问题要从内嵌汇编的实现原理开始说起,篇幅会很长,请等待我有时间的时候再说...

至于如何解决,请参考fxyang的一个脚本,在:
http://bbs.pediy.com/showthread.php?s=&threadid=7032&perpage=15&pagenumber=13
第191楼。
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-3 10:13
22
0
最初由 csjwaman 发布
请问laoluo:
FindOpcode和Find有什么区别?


最大的不同在于:FindOpcode不支持通配符,Find支持通配符。但是,如果不需要通配符的话,请尽量考虑优先使用FindOpcode,因为它的速度比较快。
雪    币: 319
活跃值: (2304)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
csjwaman 24 2004-12-3 10:18
23
0
最初由 luocong 发布


最大的不同在于:FindOpcode不支持通配符,Find支持通配符。但是,如果不需要通配符的话,请尽量考虑优先使用FindOpcode,因为它的速度比较快。


谢谢回复。
雪    币: 381
活跃值: (786)
能力值: ( LV12,RANK:730 )
在线值:
发帖
回帖
粉丝
askformore 18 2004-12-3 10:26
24
0
最初由 wangli_com 发布
HideOD
EOB conversion
EOE continue

invoke GetProcAddress, "OpenMutexA","kernel32.dll"
........


或者我能替 老罗 答复你:

__asm
{
PUSHAD
PUSH EDX
xor eax,eax
push eax
push eax
CALL kernel32.CreateMutexA
POPAD
jmp kernel32.OpenMutexA
}

最后一句 jmp kernel32.OpenMutexA

把你删除,看看效果怎么样,逻辑上虚拟机要为你的内嵌代码加入返回 运行前的eip,你上面的最后这句,是自己返回 eip,虚拟机永远在等待自己写的那条返回 eip 的指令(是不会被执行的,因为你自已强行来实现)

to luocong(we talk about):

=========================
- ASProtect 2.0 Unpack - import & scrambled code recovery (only Delphi & Imagebase = 400000) (by Mario555, 30 Sep 2004)

你到http://ollyscript.apsvans.com/下来看看,你就会发现你的  WriteMemLong 的先天不足,即是这样的

OS的 mov 有n种集成功能,其中比方这个 mov patch,#abcdef0123456789# (patch已经是个有值401000的地址变量):

作用前:
00401000   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00

作用后:
00401000   AB CD EF 01  23 45 67 89  00 00 00 00  00 00 00 00

你说用它来写这样的补丁是多么痛苦的事,其中是要通过人脑计算的思维就烦了,特别在hex串特长的情况尤其明显。

不如来个这样的 API :
WriteMemHexs,address,hexsofvalue
用不用返回值由你决定,意思是将任意长度的 hex 串,按原样的顺序写入到指定的内存地址处。

举例:invoke writememhexs, 401000(或者reg01),"33C090"

=================
FreeBufferSizeReg
FreeBufferReg
上面这两个特殊的 寄存器,从字面上的不好理解,但作用还是能明白过来的。

FreeBufferSizeReg 表示 FreeBufferReg 的长度,FreeBufferReg 表示实际内容(相当于&a),如“I am a boy!”,而不是指向 “I am a boy!”的指针(a),若能加个(a)对应的指针就好了,不然就像广州话的“有得睇,母得使”!或者加个相关的写入 API
如:

MovBufferReg, address, FreeBufferReg
意思是将 FreeBufferReg 之值的原样写入到指定的内存地址

======================
希望增个 API 作用如下:

LoadFileInMem, address, filename
意思是将 filename(完整路径)给出的文件以16进制的形式加载到内存指定的地址,如果 address 用 0 ,则表示让虚拟机自动分配空间加载文件,而返回值在 reg00,表示该文件在内存的首地址,返回值为(0或-1选一个吧)表示加载失败!

动机:有时我们在调试中得到的一些有用的“表”,希望在下次调试中加入到(指定或肯定的)内存中进行自己期代的运算,比如 Amr的xxx表修复就会有需求

大家可以讨论一下有没有必要增这个 API 呀

========================================
:D :p
雪    币: 1593
活跃值: (726)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
luocong 9 2004-12-3 10:41
25
0
差不多就是这个意思了,但不完全对,主要是断点设在了OpenMutexA函数的第一条指令处,然后虚拟机开始运行,在后面运行时遇到了这条断点,就立即跳到了嵌入汇编那里去了,接着最后一条 jmp kernel32.OpenMutexA 使得eip又回到了OpenMutexA的第一条指令处,但这时由于断点还没去掉,所以又跳到了嵌入汇编处……于是无限循环产生了。等等,这么一说好像是虚拟机对断点的处理逻辑有问题,我先调试一下看看,说不定是我的bug。

to askformore(we talk about):

类似WriteMemHexs这个API非常有必要,我已经在0.20版中实现了,请等待发布,但可能还要等几天。

至于FreeBufferReg和FreeBufferSizeReg的使用,你的建议很好,我会考虑增加。BTW,你是广州人?是的话来握个手。:D

LoadFileInMem实现起来应该不难,我尽量增加到0.20版中。但我不太清楚你的需求,具体要怎么加载?是不是申请一块跟文件大小相同大的内存,然后把整个文件从文件头4D5A开始一直到文件末尾,所有的字节放到内存中就OK了?
游客
登录 | 注册 方可回帖
返回