首页
社区
课程
招聘
Delphi逆向工程笔记5(续)――关于String局部变量
2004-11-15 00:34 8318

Delphi逆向工程笔记5(续)――关于String局部变量

2004-11-15 00:34
8318
没想到居然写了这么多……写完后打算去blogcn抒发一下高兴的心情,却发现blogcn被黑了……不过那个Hacker算是手下留情的,把主页备份了。

=================================================================================

  为什么我在ItemDraw里用了String后没有达到预想的效果呢?从反汇编的结果
看,问题有点复杂。按道理说,过程/函数的局部简单变量是放在堆栈里的,但是
这里似乎有点例外情况。还是应该跟踪一下。
  用od跟,首先找到GetWindowText的调用,前面有个push 0ah,下个HWBP,运
行。下面是IDA在那里的disasm结果:

CODE:00013D06                 mov     eax, [ebp+var_4]
CODE:00013D09                 call    sub_12DA4
//这里是检查eax是否为0,如是,则eax就被设为var_4的地址
CODE:00013D0E                 mov     esi, eax
CODE:00013D10                 push    esi             ; lpString
CODE:00013D11                 mov     eax, [ebx+14h]
CODE:00013D14                 push    eax             ; hWnd
CODE:00013D15                 call    GetWindowTextA

  继续,在00013D0E处,eax=12da9,但是这好象是CODE段。run。Exception!
这时程序中断在User32里。很明显,Delphi在处理String时有点小小的例外。那么,
来分析一下。

  一、PChar

  PChar是Delphi为和C兼容而引入的数据类型,对应于char*。对程序做如下改动:

Procedure ItemDraw(lpDIS:PDrawItemStruct);
Var
  Str:PChar;
Begin
  ...
  GetWindowText(lpDIS^.hwndItem,Str,10);

  运行结果是,按钮文字乱码,od报告Exception。反汇编结果如下:

CODE:00013CE5                 push    0Ah             ; nMaxCount
CODE:00013CE7                 push    esi             ; lpString
CODE:00013CE8                 mov     eax, [ebx+14h]
CODE:00013CEB                 push    eax             ; hWnd
CODE:00013CEC                 call    GetWindowTextA

  前面没有给esi赋值的地方,这也是意料之中。跟踪发现,esi=00013FB8,
指向LicenseProc。那么,看到的乱码就是LicenseProc的开始部分。大概是
User32里有SEH,直接运行程序没有Exception。
  注意,Str没有在堆栈里分配,哪怕是个指针。但是这个可以理解。
  改改代码:

Procedure ItemDraw(lpDIS:PDrawItemStruct);
Var
  Str:PChar;
Begin
  ...
  GetWindowText(lpDIS^.hwndItem,@Str,10);

  可以正常运行。反汇编:

CODE:00013CA4 String          = byte ptr -4
CODE:00013CA4
CODE:00013CA4                 push    ebx
CODE:00013CA5                 push    ecx
CODE:00013CA6                 mov     ebx, eax
CODE:00013CA8                 mov     eax, ds:hbr
...
CODE:00013CE5                 push    0Ah             ; nMaxCount
CODE:00013CE7                 lea     eax, [esp+8+String]
CODE:00013CEB                 push    eax             ; lpString
CODE:00013CEC                 mov     eax, [ebx+14h]
CODE:00013CEF                 push    eax             ; hWnd
CODE:00013CF0                 call    GetWindowTextA
...
CODE:00013D4B                 pop     edx
CODE:00013D4C                 pop     ebx
CODE:00013D4D                 retn

  有一个局部变量!跟踪到00013CA4,发现esi指向LicenseProc,esp=0013FA7C。
到00013CA6时,esp=0013FA74,没问题。到00013CE7,esp=0013FA70。向下,
eax=0013FA74,指向push的ecx。好了,得到的字符串覆盖了堆栈里的ecx和ebx。
那么,“正常运行”的结果应该是一个巧合。但是这个地方非常有趣,ebx当然要保
存,但是ecx完全不必保存的。这从过程的首尾可以看到。怎么回事呢?很简单,
PChar只是一个Point,push ecx就是分配空间。但是ebx呢?Delphi的HLP里明确说明
必须保护ebx。很巧,程序是SDK写的,没有用VCL。所以ebx坏了没事,而且最长的文
字才6字节。否则肯定出问题了。

  玩个花样:

Var
  Str:PChar;
Begin
  Str:='123';
  ...
  GetWindowText(lpDIS^.hwndItem,Str,10);

  按钮的字全部是123。很容易就想到,123是常量,很可能放在CODE里。异常,写
不进。反汇编也证实了这点:

CODE:00013CA4 sub_13CA4       proc near
CODE:00013CA4                 push    ebx
CODE:00013CA5                 push    esi
CODE:00013CA6                 mov     ebx, eax
CODE:00013CA8                 mov     esi, offset loc_13D48
...
CODE:00013CEA                 push    0Ah             ; nMaxCount
CODE:00013CEC                 push    esi             ; lpString
CODE:00013CED                 mov     eax, [ebx+14h]
CODE:00013CF0                 push    eax             ; hWnd
CODE:00013CF1                 call    GetWindowTextA
...
CODE:00013D46                 retn
CODE:00013D46 sub_13CA4       endp
CODE:00013D46
CODE:00013D47                 align 4
CODE:00013D48
CODE:00013D48 loc_13D48:
CODE:00013D48                 xor     [edx], esi
CODE:00013D4A                 xor     eax, [eax]

  但是用Aspack2.12加壳后,运行程序,可以正常显示文字。很明显,代码段可
写。这个结果很有趣,在加密上会有用的。顺便说一句,此时由于部分代码被破坏,
窗口重画时就异常退出了。

  二、String

  1、直接用String

Var
  Str:String;
Begin
  GetWindowText(lpDIS^.hwndItem,PChar(Str),10);

  没有文字。

CODE:00013CB0 sub_13CB0       proc near
CODE:00013CB0
CODE:00013CB0 var_4           = dword ptr -4
CODE:00013CB0
CODE:00013CB0                 push    ebp
CODE:00013CB1                 mov     ebp, esp
CODE:00013CB3                 push    0
CODE:00013CB5                 push    ebx
CODE:00013CB6                 push    esi
CODE:00013CB7                 mov     ebx, eax
CODE:00013CB9                 xor     eax, eax
CODE:00013CBB                 push    ebp
CODE:00013CBC                 push    offset loc_13D7E
CODE:00013CC1                 push    dword ptr fs:[eax]
CODE:00013CC4                 mov     fs:[eax], esp
...
CODE:00013D04                 push    0Ah             ; nMaxCount
CODE:00013D06                 mov     eax, [ebp+var_4]
CODE:00013D09                 call    sub_12DA4
CODE:00013D0E                 mov     esi, eax
CODE:00013D10                 push    esi             ; lpString
CODE:00013D11                 mov     eax, [ebx+14h]
CODE:00013D14                 push    eax             ; hWnd
CODE:00013D15                 call    GetWindowTextA
...
CODE:00013D68
CODE:00013D68 loc_13D68:
CODE:00013D68                 xor     eax, eax
CODE:00013D6A                 pop     edx
CODE:00013D6B                 pop     ecx
CODE:00013D6C                 pop     ecx
CODE:00013D6D                 mov     fs:[eax], edx
CODE:00013D70                 push    offset loc_13D85
CODE:00013D75
CODE:00013D75 loc_13D75:
CODE:00013D75                 lea     eax, [ebp+var_4]
CODE:00013D78                 call    sub_12D80
CODE:00013D7D                 retn
CODE:00013D7D sub_13CB0       endp ; sp = -14h

CODE:00012DA4 sub_12DA4       proc near
CODE:00012DA4                 test    eax, eax
CODE:00012DA6                 jz      short loc_12DAA
CODE:00012DA8                 retn
CODE:00012DA9 byte_12DA9      db 0
CODE:00012DAA
CODE:00012DAA loc_12DAA:
CODE:00012DAA                 mov     eax, offset byte_12DA9
CODE:00012DAF                 retn
CODE:00012DAF sub_12DA4       endp

  00013CB0处,esp=0013FA7C。00013D06处,eax=0。那么,看来局部的String是
分配一个指针,和PChar一样。这是在{H+}条件下的结果。注意,这里用了SEH。加
上Str:='123';一句,结果和PChar的情况一样。但是这里有个额外的call,上面没有
列出。

  改一下:

Var
  Str:String;
Begin
  ...
  GetWindowText(lpDIS^.hwndItem,@Str,10);

  程序异常了。经过跟踪可以得知,异常点在12D8C:

CODE:00012D80 sub_12D80       proc near
CODE:00012D80                 mov     edx, [eax]
CODE:00012D82                 test    edx, edx
CODE:00012D84                 jz      short locret_12DA2
CODE:00012D86                 mov     dword ptr [eax], 0
CODE:00012D8C                 mov     ecx, [edx-8]
CODE:00012D8F                 dec     ecx
CODE:00012D90                 jl      short locret_12DA2
CODE:00012D92                 lock dec dword ptr [edx-8]
CODE:00012D96                 jnz     short locret_12DA2
CODE:00012D98                 push    eax
CODE:00012D99                 lea     eax, [edx-8]
CODE:00012D9C                 call    sub_1249C
CODE:00012DA1                 pop     eax
CODE:00012DA2
CODE:00012DA2 locret_12DA2:
CODE:00012DA2                 retn
CODE:00012DA2 sub_12D80       endp

  说明一下,00012D80处,eax就指向堆栈里的那个指针。那么,edx应该就是
String的实际地址了,而edx-8很可能是一个引用计数。很明显,对@Str的写入破坏
了指针(正常应该是0)。

  但是这里有个情况,如果对Str初始化一下

Var
  Str:String;
Begin
  Str:='1423423';

  那么,如果使用PChar(Str),按钮的文字显示为1423423。而使用@Str[1]则正
常。
  用PChar转换时,部分反汇编结果如下

CODE:00013CDC sub_13CDC       proc near
CODE:00013CDC
CODE:00013CDC var_4           = dword ptr -4
CODE:00013CDC
...
CODE:00013CF3                 lea     eax, [ebp+var_4]
CODE:00013CF6                 mov     edx, offset a1423423 ; "1423423"
CODE:00013CFB                 call    sub_12DA4

  执行完sub_12DA4后,var_4被放上了位于CODE里的a1423423的地址。

  而用后一种形式时,'1423423'仍然在CODE里,但是在00013DA9的call以后,eax
变了!

CODE:00013DA4                 push    0Ah             ; nMaxCount
CODE:00013DA6                 lea     eax, [ebp+var_4]
CODE:00013DA9                 call    sub_12E0C
CODE:00013DAE                 push    eax             ; lpString
CODE:00013DAF                 mov     eax, [ebx+14h]
CODE:00013DB2                 push    eax             ; hWnd
CODE:00013DB3                 call    GetWindowTextA

  看来是新分配了一段空间。应该是堆空间。

  2、用String[xx]形式:

Var
  Str:String[20];
Begin
  ...
  GetWindowText(lpDIS^.hwndItem,@Str[1],10);

  正常运行。这时局部变量已经变了

CODE:00013CA4 sub_13CA4       proc near
CODE:00013CA4
CODE:00013CA4 String          = byte ptr -17h
CODE:00013CA4
CODE:00013CA4                 push    ebx
CODE:00013CA5                 add     esp, 0FFFFFFE8h
...
CODE:00013CE7                 push    0Ah             ; nMaxCount
CODE:00013CE9                 lea     eax, [esp+1Ch+String]
CODE:00013CED                 push    eax             ; lpString
CODE:00013CEE                 mov     eax, [ebx+14h]
CODE:00013CF1                 push    eax             ; hWnd
CODE:00013CF2                 call    GetWindowTextA
...
CODE:00013D4D
CODE:00013D4D loc_13D4D:
CODE:00013D4D                 add     esp, 18h
CODE:00013D50                 pop     ebx
CODE:00013D51                 retn
CODE:00013D51 sub_13CA4       endp

  直接看分析代码可以看到,对于20字节长的ShortStr,分配了24个堆栈字节。加
一下偏移就看得到,长度标志还是1字节。4个字节的差别是为了对齐。

  结论是:在{H+}条件下,对于String[xx]形式,就是ObjectPascal的String,直
接在堆栈分配。PChar是一个放在堆栈里的指针,内容视情况而定。String则是在堆
栈里分配一个指针,指向一个结构体。限于时间,我没有对这个结构体做进一步探
索。但是,如果用PChar转换,则指针一般是指向CODE的。
  那么,为什么开始的程序没有出问题也就很明白了。也是巧合,我用了PChar强
制转换,所以指针实际是指向CODE的。由于User32很可能有SEH什么的,结果是什么
都显示不出。加上对Message不熟悉,这样我以为按钮的文字可能在WM_PAINT里,否
则肯定早就发现原因了。

================================================================

如果你看到这里觉得我没有讲什么新东西,别打我……

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

收藏
点赞7
打赏
分享
最新回复 (5)
雪    币: 85496
活跃值: (198820)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
linhanshi 2004-11-15 00:38
2
0
支持!!!
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yafeng 2004-11-15 21:25
3
0
delphi的字串一般是
FF FF FF FF +长度+字串+00的
其中最前边只要一个FF即可
雪    币: 390
活跃值: (707)
能力值: ( LV12,RANK:650 )
在线值:
发帖
回帖
粉丝
firstrose 16 2004-11-15 22:21
4
0
最初由 yafeng 发布
delphi的字串一般是
FF FF FF FF +长度+字串+00的
其中最前边只要一个FF即可


那4个ff就是引用计数?详细点如何?

主要是delphi有好几种不同的string
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yafeng 2004-11-16 12:36
5
0
最初由 firstrose 发布


那4个ff就是引用计数?详细点如何?

主要是delphi有好几种不同的string


FF FF FF FF是字串的开始标志吧。delphi的字串一般除了“标准资源”中的字串,还有的就是以FF FF FF FF 开始的定长字串,剩下的我知道的就是使用API的C类型的以00结尾的字串了,UNICODE字串也可能有,不过没分析其格式。
雪    币: 204
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
DoorKey 2004-11-16 18:14
6
0
Delphi就四种字符串:字符数组,短字符串,长字符串,PChar。
这个问题可以用好几种方法都是可行的:
1.用字符数组
Var
  Str: array [0..255] of char;
Begin
  ...
  GetWindowText(lpDIS^.hwndItem, @str, 255);

2.用长字符串
Var
  Str: string;
Begin
  SetLength(str, 255);
  GetWindowText(lpDIS^.hwndItem, @str[1], 255);

3.用PChar
Var
  Str: pchar;
Begin
  str := AllocMem(256)
  GetWindowText(lpDIS^.hwndItem, str, 255);

至于它们之间的区别及在内存中的结构很多帖子和书都有,我就赖得再说了!!
游客
登录 | 注册 方可回帖
返回