巴别塔上的雇工


执行放在Stack上的代码
7月 31, 2006, 5:54 上午
Filed under: 技术体会
看《ATL Internals》,发现ATL居然执行放在栈上的代码。把code放在stack上,然后想办法让eip指向stack上的位置,这在Windows上当然是可行的,缓冲区溢出攻击也是利用这一点。有的Linux内核发布让栈上code不能执行,据说这样防止攻击效果不是很好,所以也没有风行开;如果有朝一日Windows也有这样的限制,ATL的实现也需要修改一下了:)

ATL玩这个花招是为了把对消息处理函数的调用转移到对类的成员函数的调用上面去,因为消息处理函数只能是全局普通函数或者类的静态成员函数,这个转换需要获得一个对象的指针。《ATL Internals》没有解释清楚如何获得这个指针,这个网页解释的比较好,大概来说,就是ATL用一个全局链表来记录每个线程上当前正在通过ATL创建的Window的信息,链表上每一项对应一个线程,因为每个线程同一时刻只可能有一个Window正在被创建,所以任何地方都可以通过这个链表找到自己所在线程,通过这个全局信息获得对象的指针;然后修改栈上HWND参数为对象指针,都是32位,所以没什么问题,在消息处理函数里面又强制类型转换回来,这点C/C++可以做到,但是前面把HWND参数覆盖为指针,只好动用汇编语言了。我觉得ATL这么做主要是为了高效,其实完全可以避免这样让人很难理解的方式。

下面的code演示了的确可以运行Stack上的code.

#include "stdio."
#include "atlbase.h"

void print()
{
_tprintf(_T("Code reach here!n"));
}

//#define BYTE unsigned char

#pragma pack(push, 1)
struct Thunk
{
BYTE m_jmp;
DWORD m_relproc;

void Init(void *proc)
{
m_jmp = 0xe9; //jmp
m_relproc = DWORD((INT_PTR)proc -
((INT_PTR)this+sizeof(Thunk)));
FlushInstructionCache(GetCurrentProcess(),
this, sizeof(Thunk));
}
};
#pragma pack(pop)#include "test.h"

int _tmain()
{
Thunk t;
t.Init(print);
void *p = (void *)&t;
__asm call p;

_tprintf(_T("Overn"));

}

运行结果是

Code reach here!
Over



Mozilla Plugin API
7月 29, 2006, 4:56 下午
Filed under: 技术体会
Mozilla Plugin的文档虽然看的很费劲,但是API本身还是有可圈可点之处,值得说一下。

所谓Plugin,就是一个模块可以往Container指定位置一扔,也许做一点注册之类的事情,然后这个Plugin就可以和Container交互,扩展Container的功能了,这样需要有确定的Container和Plugin之间的接口规范。现在很多应用程序都支持Plugin扩展,Mozilla的接口规范相对来说很原始,没有象M$的产品那样借助COM技术,也没有像Eclipse那样需要java语言,Mozilla就是完全靠纯C的函数指针搞定。

先说说历史:Mozilla是脱胎于Netscape,Netscape曾设想构造一个完全基于Java的浏览器,不奇怪他们搞了一个叫LiveConnect的概念,让Java、JavaScript和Native的Plugin程序之间能够通讯,我想这东西弄出来的时候Netscape那帮人还肯定沾沾自喜,以为靠上了Java能够带来一点“Platform-indentent”的好处,但这玩艺纯粹就是把事情搞复杂。之后,Mozilla又有了一个XPCom的概念,纯粹就是抄袭M$的COM,而且还没有抄好。XPCom号称是平台无关的,如BJ评价Java“Java is not platform-independent. Java itself is a platform”,XPCOM所提供的平台无关,基于他自己就是一个平台,所有的XPCOM组建只能在它里头运行,如果说某某人是为了摆脱M$的枷锁抛弃COM投奔XPCOM,那只是戴上新的枷锁而已。说XPCom没有抄好COM,是因为它忘了抄COM的IDispatch接口,这个接口是使得组件Scriptable的关键,没了这样一个对等的接口,Mozilla没法动态的发现一个Plugin的“能力”,所以只好要求每个Plugin提供静态的XPT文件表明自己的“能力”。2004年,Mozilla终于意识到这个问题,提供了新的Plugin借口扩展npruntime,还是回归本源,用原始的回调函数方法解决了Scriptable的问题。

Mozilla的Plugin必须export的函数只有三个:NP_GetEntryPoints、NP_Initialize和NP_ShutDown,前缀NP就是Netscape Plugin的缩写,相当明显的Netscape的痕迹。

Container会先调用Plugin的NP_GetEntryPoints,两者的交互总是由Container发起,这时候Plugin要把自己愿意提供的函数的指针告诉Container。为什么不直接export,而在这里用函数指针呢?我想Netscape/Mozilla是想减少export symbol table的大小,而且和下面的NP_Initialize一致。

Container还会调用Plugin的NP_Initialize,通过这个函数调用,Container会把它愿意提供给Plugin的函数的指针告诉Plugin,Plugin需要把这些指针记下来,这样回头才能使用Container的功能。我在以前雇主那里工作的时候,也开法过需要Plugin的系统,那时候对Plugin需要调用Container功能的解决办法,是再制造一个Utility Lib,形式是一个Shared Object,每个Plugin动态连接这个Utility Lib,当Plugin被load的时候,这个Utility Lib一起被load到Container的进程空间,毕竟,Container和Plugin之间必须是松耦合关系,我们不希望任何一方没有另外一方就编译不下去。相对我那时候的解决方案,Mozilla更加原始直接,一切都用函数指针搞定,overhead比较小。

NP_ShutDown就是Plugin被卸载的时候用上,没什么好说的。

总的说来,现在的NPAPI还是比较简介干净,没有拖泥带水的要求Plugin利用这技术那技术,连面向对象的思想都没用,算是Raw Plugin API的典范。



砧板为什么要用盐泡油浇?
7月 29, 2006, 4:35 下午
Filed under: 城市丛林
新买了砧板,很重,说是什么越南产的木宪(连在一起)木,附带说明书上说,使用前,要先用盐水浸泡两个小时,然后晾干再用沸腾的油浇一遍,但是没有说明为什么这样做。

我找了一下相关资料,应该是这样,这些适合做砧板的木头都需要有水才能够不干裂,但是平时放着肯定会干,用盐水浸泡是为了让盐中微量的硫酸镁进入木头中,硫酸镁有很强的吸湿性,能够保持木头的水分,砧板又会吸收菜汁肉汁,这样就成为细菌的温床,用油浇是为了在表面形成隔绝层,让木头不吸收汁液。但是这两个措施似乎矛盾啊,不懂



ADSL恢复了
7月 27, 2006, 10:50 上午
Filed under: 八卦杂谈
昨天没有写Blog,因为回到家发现ADSL连接不上去,总是提示691错误:用户名和密码不匹配,我也没有重新输入过密码,不能有问题啊。给CNC的服务热线打了电话,说是最近他们在搞配置更新,北京62和82开头的电话号码用ADSL都可能有问题,24小时内能解决。刚刚回家,发现可以上网了,能上网真是一件幸福的事情



文档之惑
7月 27, 2006, 9:32 上午
Filed under: 工作心情

最近研究了一下怎么写Mozilla的plugin,一个感觉——难受,看差劲的文档真难受。

Open Source的软件项目声称所有的人都可以阅读修改源代码,只要遵守附带的licence,这样看似Open Source的项目最好上手,源代码都在那,有什么不明白的看源代码就好了,Open Souce社区有这种说法:“source code就是文档”。真的是这样的吗?我这几天尝试学习写一个Mozilla Firefox的plugin,相关文档相当差劲,不得不怀疑Open Source这样的方式是否真的行的通。

我就想写一个plugin,也就是寄生在Mozilla中的一个模块,总不能因为这个让我去读一遍Mozilla到底如何如何和plugin交互的源代码吧,在软件趋向模块化的时代,弄清楚模块间的接口就行了,但是Mozilla关于接口的文档相当糟糕。首先相关书籍几乎没有,资源只有网站上内容,但是网站上的API介绍结构并不好,看一遍是很难看明白的,有一个段落(Drawing and Event Handling)居然没有写,最要命的是,文档和Sample Code不一致,在官方网站上链接的API网页,现在还是说NP_Initialize没有参数,但是从Sample Code中可以看到,实际上是有一个参数的。Mozilla的开发社区还是有些人,但是似乎绝大部分都是在用XUL做extension,做plugin的很少,所以我算是误入一块鸟不生蛋的地方了

最后只好用写code的方法摸索,才弄明白个大概,应该说API的设计还算比较合理,但是文档没有描述清楚,而且网站上内容也不做即时更新,除非万不得已,以后我是不想再碰它了。



突降暴雨
7月 24, 2006, 2:15 下午
Filed under: 城市丛林
北京下午突然下了暴雨,我又没带伞,打电话给老婆,老婆说她已下班了,然后我俩电话手机都没电了。我又不想等雨停,就去复印室拿了个纸袋子当伞跑回家,知春路这一块排水系统实在太差劲了,积水很深,想起老婆穿皮鞋肯定不能涉水,就拿了一把伞和老婆的凉鞋去接老婆。在地铁站门口等了快一个小时,小风嗖嗖的吹着,我等得实在无聊,就一个人踢一个并不存在的足球,来往过人肯定觉得我是一个神经病或者超级球迷:)


The Old New Thing上关于DLL的介绍
7月 24, 2006, 6:40 上午
Filed under: 技术体会
前几天没看Bloglines上订阅的Blog文章,今天过了一下,发现The Old New Thing上面接连有几篇关于DLL的文章,嘿嘿,我前一整子正好也研究了一下这个东东。

The Old New Thing的作者Raymond Chen是微软Windows team的developer,他的Blog上介绍了很多微软技术的历史趣事(对技术人员来说有趣),一些不合理的东西从历史问题的角度来看,也是合理的



How PLT(Procedure Linkage Table) Works
7月 23, 2006, 7:41 上午
Filed under: 技术体会
前几天写了一下关于DLL和SO的内在区别,因为这段子都在Windows下用电脑,所以关于ELF格式怎么import,,真的就是资料怎么说我就怎么说,感觉还是不痛快,昨天在家在Linux下面实践,感受了一下PLT(Procedure Linkage Table)是怎么工作的。

简单的弄个可执行程序,源代码main.c如下,故意用两个foo调用,看看两次调用有什么不同

#include "test.h"

int main()
{
printf("%dn", xyz);
foo();
foo();
}

再弄一个Shared Object,源代码如下

#include <stdio.h>
#include "test.h"

int xyz = 4;

int foo()
{
printf("OK %dn", xyz);
}

test.h头文件如下

extern int xyz = 4;

int foo();

还需要一个Makefile一次把库和可知性文件build好

all:
gcc -shared -fPIC test.c -o libtest.so
gcc -I. -L. -ltest main.c

Build产生的a.out可执行程序会动态连接libtest.so,我就想看看到底是怎么在a.out里面使用libtest.so里面定义的全局变量xyz和全局函数foo的。

用gdb来看整个过程,先把断点设在main入口处

$ gdb a.out
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) break main
Breakpoint 1 at 0x8048492
(gdb) run
Starting program: /home/morgan/examples/so/a.out

Breakpoint 1, 0x08048492 in main ()

在断点处看看汇编代码

(gdb) disass
Dump of assembler code for function main:
0x0804848c <main+0>: push %ebp
0x0804848d <main+1>: mov %esp,%ebp
0x0804848f <main+3>: sub $0x8,%esp
0x08048492 <main+6>: and $0xfffffff0,%esp
0x08048495 <main+9>: mov $0x0,%eax
0x0804849a <main+14>: sub %eax,%esp
0x0804849c <main+16>: sub $0x8,%esp
0x0804849f <main+19>: pushl 0x8049680
0x080484a5 <main+25>: push $0x804856c
0x080484aa <main+30>: call 0x80483bc <printf>
0x080484af <main+35>: add $0x10,%esp
0x080484b2 <main+38>: call 0x80483cc <foo>
0x080484b7 <main+43>: call 0x80483cc <foo>
0x080484bc <main+48>: leave
0x080484bd <main+49>: ret
0x080484be <main+50>: nop
0x080484bf <main+51>: nop
End of assembler dump.

在调用printf之前,把0x8049680这个位置所指向的内容压栈作为参数,这个位置存放的就是变量xyz,0x8049680就是a.out的GOT上的位置。对foo的调用是call到0x80483cc上,去看看那里有什么。

先在两个foo的调用点上设上断点

(gdb) break *0x080484b2
Breakpoint 2 at 0x80484b2
(gdb) break *0x080484b7
Breakpoint 3 at 0x80484b7
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048492 <main+6>
breakpoint already hit 1 time
2 breakpoint keep y 0x080484b2 <main+38>
3 breakpoint keep y 0x080484b7 <main+43>

跟进去,如书上所说,这就是PLT上的一项,直接jmp到0x8049678所指的位置,0x8049678和上面引用xyz的位置0x8049680很近哦,因为他们都在GOT上,

(gdb) c
Continuing.
4

Breakpoint 2, 0x080484b2 in main ()
(gdb) si
0x080483cc in foo ()
(gdb) disass
Dump of assembler code for function foo:
0x080483cc <foo+0>: jmp *0x8049678
0x080483d2 <foo+6>: push $0x10
0x080483d7 <foo+11>: jmp 0x804839c <_init+24>
End of assembler dump.

跟进去,如书上所说,这就是PLT上的一项,直接jmp到0x8049678所指的位置,0x8049678和上面引用xyz的位置0x8049680很近哦,因为他们都在GOT上,

(gdb) c
Continuing.
4

Breakpoint 2, 0x080484b2 in main ()
(gdb) si
0x080483cc in foo ()
(gdb) disass
Dump of assembler code for function foo:
0x080483cc <foo+0>: jmp *0x8049678
0x080483d2 <foo+6>: push $0x10
0x080483d7 <foo+11>: jmp 0x804839c <_init+24>
End of assembler dump.

跟进去,如书上所说,这就是PLT上的一项,直接jmp到0x8049678所指的位置,0x8049678和上面引用xyz的位置0x8049680很近哦,因为他们都在GOT上,

(gdb) c
Continuing.
4

Breakpoint 2, 0x080484b2 in main ()
(gdb) si
0x080483cc in foo ()
(gdb) disass
Dump of assembler code for function foo:
0x080483cc <foo+0>: jmp *0x8049678
0x080483d2 <foo+6>: push $0x10
0x080483d7 <foo+11>: jmp 0x804839c <_init+24>
End of assembler dump.

看看0x8049678所指的位置在哪里,其实就在jmp命令的下一条指令0x080483d2

(gdb) x/x 0x8049678
0x8049678 <__JCR_LIST__+24>: 0x080483d2

继续,再次步进到foo入口处

(gdb) c
Continuing.
OK 4

Breakpoint 3, 0x080484b7 in main ()
(gdb) disass
Dump of assembler code for function main:
0x0804848c <main+0>: push %ebp
0x0804848d <main+1>: mov %esp,%ebp
0x0804848f <main+3>: sub $0x8,%esp
0x08048492 <main+6>: and $0xfffffff0,%esp
0x08048495 <main+9>: mov $0x0,%eax
0x0804849a <main+14>: sub %eax,%esp
0x0804849c <main+16>: sub $0x8,%esp
0x0804849f <main+19>: pushl 0x8049680
0x080484a5 <main+25>: push $0x804856c
0x080484aa <main+30>: call 0x80483bc <printf>
0x080484af <main+35>: add $0x10,%esp
0x080484b2 <main+38>: call 0x80483cc <foo>
0x080484b7 <main+43>: call 0x80483cc <foo>
0x080484bc <main+48>: leave
0x080484bd <main+49>: ret
0x080484be <main+50>: nop
0x080484bf <main+51>: nop
End of assembler dump.
(gdb) si
0x080483cc in foo ()
(gdb) disass
Dump of assembler code for function foo:
0x080483cc <foo+0>: jmp *0x8049678
0x080483d2 <foo+6>: push $0x10
0x080483d7 <foo+11>: jmp 0x804839c <_init+24>
End of assembler dump.

<foo>的位置汇编代码没什么变化,还是jmp到0x8049678所指向的位置,现在看看0x8049678位置是什么

(gdb) x/x 0x8049678
0x8049678 <__JCR_LIST__+24>: 0x400176cc

哦?现在这个位置上的值变了,变成0x400176cc了,这个位置上是什么呢?

(gdb) x/x 0x8049678
0x8049678 <__JCR_LIST__+24>: 0x400176cc
(gdb) disass 0x400176cc
Dump of assembler code for function foo:
0x400176cc <foo+0>: push %ebp
0x400176cd <foo+1>: mov %esp,%ebp
0x400176cf <foo+3>: push %ebx
0x400176d0 <foo+4>: sub $0x4,%esp
0x400176d3 <foo+7>: call 0x400176d8 <foo+12>
0x400176d8 <foo+12>: pop %ebx
0x400176d9 <foo+13>: add $0x1164,%ebx
0x400176df <foo+19>: sub $0x8,%esp
0x400176e2 <foo+22>: mov 0x18(%ebx),%eax
0x400176e8 <foo+28>: pushl (%eax)
0x400176ea <foo+30>: lea 0xffffef13(%ebx),%eax
0x400176f0 <foo+36>: push %eax
0x400176f1 <foo+37>: call 0x400175d0 <_init+40>
0x400176f6 <foo+42>: add $0x10,%esp
0x400176f9 <foo+45>: mov 0xfffffffc(%ebp),%ebx
0x400176fc <foo+48>: leave
0x400176fd <foo+49>: ret
0x400176fe <foo+50>: nop
0x400176ff <foo+51>: nop
End of assembler dump.

这个位置上原来就是libtest.so中关于foo函数的实现,因为我们编译选项中用到了-fPIC,产生了PIC代码,所以看见这样取得寄存器eip值得代码

0x400176d3 <foo+7>:     call   0x400176d8 <foo+12>
0x400176d8 <foo+12>: pop %ebx

然后根据eip偏移若干得到GOT的entry

0x400176d9 <foo+13>:    add    $0x1164,%ebx

GOTentry再偏移0x18得到对xyz的引用

0x400176e2 <foo+22>:    mov    0x18(%ebx),%eax
0x400176e8 <foo+28>: pushl (%eax)

再main函数中使用xyz没有这么费事,因为可执行文件不是PIC的,也没有必要时PIC。

在main函数中的两次调用foo,可执行代码部分没有改变,不同的是GOT上面的值,这样导致指令流不同,第一调用的时候GOT上的值让指令流回到PLT上面,把一个偏移值压栈,然后跳到0x0804839c,其实就是PLT的入口,这个位置上的指令先压入ebp的值,用来标示现在是哪个ELF module,然后做的事情就是交给dynamic loader,上面有了两个push,所以栈底两个元素就是关于偏移和ELF module的,dynamic loader能够根据这个解析symbol然后修改GOT。看看0x0804839c位置的汇编代码,居然Segmentation Fault了,应该是gdb的bug吧。

(gdb) disass 0x0804839c
Dump of assembler code for function _init:
0x08048384 <_init+0>: push %ebp
0x08048385 <_init+1>: mov %esp,%ebp
0x08048387 <_init+3>: sub $0x8,%esp
0x0804838a <_init+6>: call 0x8048400 <call_gmon_start>
0x0804838f <_init+11>: call 0x8048460 <frame_dummy>
0x08048394 <_init+16>: call 0x8048524 <__do_global_ctors_aux>
0x08048399 <_init+21>: leave
0x0804839a <_init+22>: ret
0x0804839badd %bh,%bh
0x0804839d <???p??+???p??>: xor $0x8049668,%eax
0x080483a2 <_init+30>: jmp *0x804966c
0x080483a8 <???p??+???p??>: add %al,(%eax)
0x080483aa <_init+38>: add %al,(%eax)
0x080483ac <__libc_start_main+0>: jmp *0x8049670
Segmentation fault



《疯狂的石头》中一处可改进之处
7月 22, 2006, 6:36 上午
Filed under: 电影电视
最近,《疯狂的石头》那可是相——当——的——火,在水木BBS电影版,不是说《石头》,帖子都没人回。

我的观点,《石头》是这两年疲软的国产电影生产中难得的好作品,我不懂什么文艺作品应该雅不雅之类的官话,我看了很乐,我的朋友看了也很乐。谁不喜欢就让他不喜欢,也懒得说服他。

经历了这几天激烈的讨论,影片中被发现的bug还是很少,但是看了几遍之后,感觉有一点可以改进的。

小贼黑皮第一次去偷翡翠的时候,在大堂里向关公许愿,随后点了三支香烟,其实不是自己抽,是用来插在一碗米饭里供关公的。

不料另外一个小贼小军弄响了报警器,这个镜头里面可以看见黑皮手上的那碗插了香烟的米饭。

赶紧逃路,那碗饭也扔了,没准就因为这时没把关公照顾好,所以后来才那么倒霉:)

第三次去偷(第二次黑皮的任务是外围打碎花瓶吸引注意力),黑皮从下水道进去,怎么也顶不开罗汉寺的井盖了。

原来上面被一个埃及风格的雕像压住了。


要是不是一个埃及雕像,而是一个关公像压在上面,我想喜剧效果更好,全片中埃及雕像的道具似乎仅此一处,但是关公像很多阿,为什么不用呢?



看电影
7月 20, 2006, 12:16 下午
Filed under: 电影电视
小时候,除了电视上放的,几乎所有的电影都是学校组织看的,唯一一次买票入场看的电影是初中,一次期中考试达到了老爸的预期名次,奖赏我三块钱看的《新龙门客栈》,三块钱,那时候电影票真是便宜啊。在那个一切就是学习的时期,电影带来的愉悦真如荒漠绿洲。看完学校组织的电影回来,还要写观后感,就和现在看完电影写Blog一样,不知道现在的孩子是不是还是要写电影观后感,我建议学校老师让孩子写观后Blog得了,不光要培养孩子们的文字表达能力,还要让孩子们知道分享快乐。

高中在一个农村小镇上,生活极为枯燥,除了学习还是学习,娱乐活动除了足球就是偷摸溜出去看录像。现在都难以想象,我们又不是没有好好学习,出去看看电影(录像)轻松一下有什么不对,但那时候就得偷偷摸摸的,校长老师会偷袭录像厅检察,在这种高压情况下,我也没看上什么电影,最后好歹能够分清谁是刘德华谁是万梓良

大学是看电影的黄金时代,教育制度把我对电影的向往压制了这么多年,终于得以补偿。最后,我对电影的敏感最后达到这个程度:我走进一个寝室,不管屋里的哥们在看什么电影,我往往都不用看屏幕,只要听几秒钟声音,就能说出这个电影的名字出来。从信息学上来说,电影作为信息的载体,冗余信息是很多的,比如我看到阴沉的热带雨林,直升机飞过,这基本上是越战电影了,然后出现一条河,旁白一些颇有哲理似的话,我没看过野猜得出来时《现代启示录》。现在觉得看电影最开心的就是在本科寝室的时候,几个人挤在一台电脑前,边看边点评调侃一下情节或者对白,分享快乐才是最快乐,后来研究生时期每个人都有一台电脑,大家就算同时看同一电影也是分看看自己的屏幕,这种乐趣也就消失了,唉~~

现在工作了,兜里好歹有俩臭钱,有时候还和老婆去电影院看看,看的烂片多好片少。现在资源丰富了,但是最怀念的,还是本科的时候和哥们挤在寝室里边看边评的日子。