本文是写给经常写ShellCode或者给PE文件添加代码的人的.
题目这样写有点不精确.不过Borland的编译器确实是算得上最适合编写ShellCode工具.
编写ShellCode的朋友们知道.字符串常量应该是处理起来非常麻烦.
例如:
调用GetProcAddress函数吧.参数有一个是字符串.很多编译器字符串常量是在数据段中.只读的属性.
那么编写ShellCode的时候GetProcAddress(hm,”Function”);就不行了.因为”Function”实际上是在数据段中,传递的只是指针.如果是DLL的话可能还要重定位.
所以见到有很多人编写ShellCode自己实现GetProcAddress.用的是函数名的CRC.
Borland的编译器有一个特点.正好可以利用.它编译的时候字符串常量不是放在数据段里面.而是放到所在函数的后面.以Delphi为例.
Function A():PChar; begin Result := 'abc'; end;
实际上在编译后变成
00453BF0 B8F83B4500 mov eax,$00453bf8 //把字符串abc的地址付给eax,eax是返回值
00453BF5 C3 ret //返回
00453BF6 0000 //实际上是没用.作用是对齐代码
00453BF8 61626300 字符串abc
但是现在你还不能把这份代码作为ShellCode拷贝到任意地方使用.应为 mov eax,$00453bf8 的参数是绝对地址.我们怎么把它转化成相对的呢?
这需要一个技巧.
Function FixPChar(Value : PChar):PChar;forward; Function A():PChar; begin Result := FixPChar('abc'); end; Function FixPChar(Value : PChar):PChar; register;//register;加不加都无所谓.默认就是register; asm call @next // @next: pop ecx //ecx里面装的就是@next的相对地址,当前执行时 mov ebx, offset @next //ebx里面装的就是@next的绝对地址,编译时生成的 add eax, ecx //返回Value的地址+(相对地址-绝对地址) sub eax, ebx end; procedure _end(); begin end;
现在我们可以把A函数和FixChar拷贝到任何地方使用了.如何拷贝呢?
我们只要拷贝A函数的地址为起始,_end函数的地址为终点的数据块就可以用了.
例如
Type TFunc = Function():PChar; var M : String; begin // SetLength(M, Integer(@_end) - Integer(@A)); //为M分配长度 CopyMemory(PChar(M), @A, Length(M)); //把A函数和_end之间的代码拷贝到M ShowMessage(TFunc(PChar(M))()); //把M强制转换为TFunc类型执行 end;
网上流传很广的一个API搜索就是用CRC来避开字符串的.如果用这样的技术完全可以省略计算CRC的步骤.
原函数如下:
FUNCTION GetProcAddress(Module:Cardinal;ProcessCRC:DWORD) : Pointer; VAR ExportName : pChar; Address : Cardinal; J : Cardinal; ImageDosHeader : PImageDosHeader; ImageNTHeaders : PImageNTHeaders; ImageExportDirectory : PImageExportDirectory; BEGIN ImageDosHeader:=Pointer(Module); ImageNTHeaders:=Pointer(Module+ImageDosHeader._lfanew); ImageExportDirectory:=Pointer(ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress+Module); J:=0; Address:=0; REPEAT ExportName:=Pointer(Cardinal(Pointer(Cardinal(ImageExportDirectory.AddressOfNames)+Module+J*4)^)+Module); IF CalculateCRC32(ExportName^,StrLen(ExportName))=ProcessCRC THEN Address:=Cardinal(Pointer(Word(Pointer(J SHL 1+Cardinal( ImageExportDirectory.AddressOfNameOrdinals)+Module)^) AND $0000FFFF SHL 2+Cardinal(ImageExportDirectory.AddressOfFunctions) +Module)^)+Module; Inc(J); UNTIL (Address<>0)OR(J=ImageExportDirectory.NumberOfNames); Result:=Pointer(Address); END;
改良后如下:
function FixPChar(Value: PAnsiChar): PChar; forward; function strcmp(p1, p2: PAnsiChar): boolean; forward; function GetProcAddress(Module: Cardinal; ProcessName: PAnsiChar): Pointer; var ExportName : pAnsiChar; Address : Cardinal; J : Cardinal; ImageDosHeader : PImageDosHeader; ImageNTHeaders : PImageNTHeaders; ImageExportDirectory: PImageExportDirectory; begin ImageDosHeader := Pointer(Module); ImageNTHeaders := Pointer(Module + ImageDosHeader._lfanew); ImageExportDirectory := Pointer(ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + Module); J := 0; Address := 0; repeat ExportName := Pointer(Cardinal(Pointer(Cardinal(ImageExportDirectory.AddressOfNames) + Module + J * 4)^) + Module); if strcmp(ExportName, ProcessName) then Address := Cardinal(Pointer(Word(Pointer(J shl 1 + Cardinal( ImageExportDirectory.AddressOfNameOrdinals) + Module)^) and $0000FFFF shl 2 + Cardinal(ImageExportDirectory.AddressOfFunctions) + Module)^) + Module; Inc(J); until (Address <> 0) or (J = ImageExportDirectory.NumberOfNames); Result := Pointer(Address); end; function strcmp(p1, p2: PAnsiChar): boolean; begin Result := False; while (p1^ = p2^) do begin if (P1^ = #0) or (P2^ = #0) then begin Result := True; Exit; end; Inc(P1); Inc(P2); end; end; function FixPChar(Value: PAnsiChar): PAnsiChar; register; //register;加不加都无所谓.默认就是register; asm call @next // @next: pop ecx //ecx里面装的就是@next的相对地址,当前执行时 mov ebx, offset @next //ebx里面装的就是@next的绝对地址,编译时生成的 add eax, ecx //返回Value的地址+(相对地址-绝对地址) sub eax, ebx end; procedure _end(); begin end;
调用的时候直接 GetProcAddress(hm,FixPChar(‘函数名’));就可以了.免了事先计算函数名CRC的麻烦.