寄存器
函数调用约定
四寄存器fast-call调用约定,调用方平栈,一般是函数开头申请栈空间,函数结尾平栈.
整型参数:
前4个参数传入指定的寄存器RCX, RDX, R8, R9,其余参数传递到堆栈.
浮点参数:
前4个参数将传入XMM0到XMM3的寄存器,其余参数传递到堆栈.
栈空间
会有0x20字节的预留空间,一般用来存放4个寄存器RCX,RDX,R8,R9中的值.
汇编版HelloWorld
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#hello.asm
includelib user32.lib
includelib kernel32.lib
extrn MessageBoxA : proc
extrn ExitProcess : proc
NULL EQU 0
MB_OK EQU 0
.const
MSG1 db " Hello World ! " , 0
TITLE1 db " Luo " , 0
.code
START proc
;栈地址必须保证模16, 申请栈空间保证模8
;栈预留空间32(20h)个字节
sub rsp , 28 h
mov rcx , NULL
mov rdx , offset MSG1
mov r8 , offset TITLE1
mov r9d , MB_OK
call MessageBoxA
mov ecx , 0
call ExitProcess
add rsp , 28 h
RET
START endp
end
编译
1
2
ml64 / c hello.asm
link / subsystem : windows / entry : START hello.obj
注意
栈地址必须模16,因为你函数内调用API时,API内会用到多媒体指令,极大可能会使用movaps这个指令,这个指令要求栈地址必须模16,否则程序会崩.故申请的栈空间大小要求模8.其实在系统断点处,栈地址是模16的,但由于经过了CAll指令才到达入口断点,此时栈会抬高8字节,变成模8.
定位入口函数
Main函数走完,会退进程,调用exit,故离exit最近的一个三参数函数就是Main函数.
iob数组
1
2
3
#define stdin (__acrt_iob_func(0))
#define stdout (__acrt_iob_func(1))
#define stderr (__acrt_iob_func(2))
iob参数:
为1的话,可以命名该函数为printf.
为0的话,可以命名该函数为scanf.
函数
传参
参数是结构体大小为8字节:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct tagPoint {
int x1 ;
int y1 ;
};
void fun ( tagPoint pt ) {
printf ( "x=%d y=%d \r\n " , pt . x1 , pt . y1 );
}
int main ( int argc , char * argv [])
{
tagPoint pt = { 1 , 2 };
fun ( pt );
system ( "pause" );
return 0 ;
}
Debug
特征
直接使用RCX传参,函数内拆开.
参数是结构体大小超过8字节:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct tagPoint {
int x1 ;
int y1 ;
int x2 ;
int y2 ;
};
void fun ( tagPoint pt ) {
printf ( "x1=%d y1=%d x2=%d y2=%d \r\n " , pt . x1 , pt . y1 , pt . x2 , pt . y2 );
}
int main ( int argc , char * argv [])
{
tagPoint pt = { 1 , 2 , 3 , 4 };
fun ( pt );
system ( "pause" );
return 0 ;
}
Debug
特征
会出现栈拷贝.
返回值
如果返回类型为int或long long, 则放在RAX.
如果返回类型为float或double,则放在xmm0.
返回结构体大小为8字节:
放在RAX中.
返回结构体大小超过8字节:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct tagPoint {
int x1 ;
int y1 ;
int x2 ;
int y2 ;
};
tagPoint fun ()
{
tagPoint pt = { 1 , 2 , 3 , 4 };
return pt ;
}
int main ( int argc , char * argv [])
{
tagPoint pt = fun ();
system ( "pause" );
return 0 ;
}
Debug
特征
1
2
函数多了一个参数.
函数内返回时,以参数为目标做memcpy,并返回参数1.
注意
对低32位寄存器赋值,高32位自动清零.也就是说,如当我们看到函数传参,mov ecx, 2.不能说明参数为4字节,要看函数内使用的是RCX还是ECX.
变量相关
局部变量
1
2
3
4
5
6
7
8
9
int main ( int argc , char * argv [])
{
int nNum1 = 1 ;
int nNum2 = 2 ;
printf ( "%d \r\n " , nNum1 + nNum2 );
system ( "pause" );
return 0 ;
}
Debug
特征
先定义的在低地址,后定义的在高地址,且不相邻.
全局变量
1
2
3
4
5
6
7
8
9
int g_nNum1 = 1 ;
int g_nNum2 = 2 ;
int main ( int argc , char * argv [])
{
printf ( "%d \r\n " , g_nNum1 + g_nNum2 );
system ( "pause" );
return 0 ;
}
Debug
特征
地址在全局数据区.
数组
1
2
3
4
5
6
7
8
int main ( int argc , char * argv [])
{
int ary [] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
printf ( "%d %d \r\n " , ary [ 2 ], ary [ argc ]);
system ( "pause" );
return 0 ;
}
Debug
Release
特征
1
2
3
4
一维数组寻址公式 : 数组元素的地址 = 数组首地址 + sizeof ( 数组类型 ) x 下标
二维数组寻址公式 : 数组元素的地址 = 数组首地址 + sizeof ( 一维数组类型 ) x 下标 1 + sizeof ( 数组类型 ) x 下标 2
Debug版和Release版的寻址公式 , 同 32 位 .
不同的是 64 位 Release版 , 数组初始化使用多媒体指令 .
流程控制语句
IF
单分支
1
2
3
4
5
6
7
8
9
10
int main ( int argc , char * argv [])
{
if ( argc > 1 )
{
printf ( "argc > 1 \r\n " );
}
system ( "pause" );
return 0 ;
}
Release
特征识别
图形识别
1
2
3
4
5
6
虚线 : 条件跳转 .
实线 : 无条件跳转 .
每一个点代表一行汇编代码 .
if语句中有一个jxx跳转 , 因此会有一个向下的虚线箭头 .
看到上图中标红区域的图形 , 即可判断其为 if语句 .
双分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main ( int argc , char * argv [])
{
if ( argc == 1 )
{
printf ( "argc == 1 \r\n " );
}
else
{
printf ( "argc != 1 \r\n " );
}
system ( "pause" );
return 0 ;
}
Debug
特征识别
图形识别
1
2
3
4
因为 if语句中有一个jxx指令用于向下跳转 , 所以会有一个向下的虚线箭头 .
又因为 else语句中有jmp跳转 , 所以虚线箭头中会有一个向下的实线箭头 .
看到上图中标红区域的图形 , 即可判断为 if ... else语句 ,
虚线箭头之间的代码为 if代码 , 实线箭头之间的代码为 else代码 .
多分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main ( int argc , char * argv [])
{
if ( argc > 2 )
{
printf ( "argc > 2 \r\n " );
}
else if ( argc == 2 )
{
printf ( "argc == 2 \r\n " );
}
else
{
printf ( "argc < 2 \r\n " );
}
system ( "pause" );
return 0 ;
}
Debug
特征识别
SwitchCase
同32位.
循坏语句
同32位.
除法
无符号除法
除数为2的幂
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 16 = %u" , ( unsigned ) argc / 16 );
system ( "pause" );
return 0 ;
}
除数为非2的幂
MagicNumber无进位
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 3 = %I64u" , ( unsigned long long ) argc / 3 );
system ( "pause" );
return 0 ;
}
特征
1
x / 除数 = x * M64 >> ( 64 + n )
还原
1
除数 = 2 ^ ( 64 + n ) / M64 , 向上取整
MagicNumber有进位
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 7 = %u" , ( unsigned ) argc / 7 );
system ( "pause" );
return 0 ;
}
特征
1
2
乘减移加移
x / 除数 = ((( x - ( x * M >> 32 )) >> n1 ) + ( x * M >> 32 )) >> n2
还原
1
除数 = 2 ^ ( 32 + n ) / ( 2 ^ 32 + M ), 向上取整
有符号除法
除数为正2的幂
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 8 = %d" , argc / 8 );
system ( "pause" );
return 0 ;
}
特征
1
2
x >= 0 : x / 除数 = x >> n
x < 0 : x / 除数 = ( x + ( 2 ^ n - 1 )) >> n
还原
除数为正非2的幂
MagicNumber为正
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 9 = %lld" , ( long long ) argc / 9 );
system ( "pause" );
return 0 ;
}
特征
1
2
x >= 0 : x / 除数 = x * M64 >> ( 64 + n )
x < 0 : x / 除数 = (( x * M64 ) >> ( 64 + n )) + 1 )
还原
1
除数 = 2 ^ ( 64 + n ) / M64 , 向上取整
MagicNumber为负
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / 7 = %d" , argc / 7 );
system ( "pause" );
return 0 ;
}
特征
1
2
x >= 0 : x / 除数 = ( x * M + x ) >> ( 32 + n )
x < 0 : x / 除数 = (( x * M + x ) >> ( 32 + n )) + 1 )
还原
1
除数 = 2 ^ ( 32 + n ) / M , 向上取整
除数为负2的幂
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / -16 = %lld" , ( long long ) argc / - 16 );
system ( "pause" );
return 0 ;
}
除数为负非2的幂
MagicNumber为正
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / -7 = %d" , argc / - 7 );
system ( "pause" );
return 0 ;
}
特征
1
2
x >= 0 : x / 除数 = ( x * M - x ) >> ( 32 + n )
x < 0 : x / 除数 = (( x * M - x ) >> ( 32 + n )) + 1 )
还原
1
除数 = 2 ^ ( 32 + n ) / ( 2 ^ 32 - M ), 向上取整
MagicNumber为负
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc / -10 = %lld" , ( long long ) argc / - 10 );
system ( "pause" );
return 0 ;
}
特征
1
2
x >= 0 : x / 除数 = x * M64 >> ( 64 + n )
x < 0 : x / 除数 = (( x * M64 ) >> ( 64 + n )) + 1 )
总结
1
除数 = 2 ^ ( 64 + n ) / ( 2 ^ 64 - M64 ), 向上取整
总结
1
2
3
4
5
6
7
8
9
10
11
12
x / 除数 = x * M >> ( 32 + n )
结论 :
M > 0 : x / 正除数 , 除数 = 2 ^ ( 32 + n ) / M , 向上取整
M < 0 : x / 负除数 , 除数 = 2 ^ ( 32 + n ) / ( 2 ^ 32 - M ), 向上取整
x / 除数 = ( x * M + x ) >> ( 32 + n )
结论 :
M < 0 : x / 正除数 , 除数 = 2 ^ ( 32 + n ) / M , 向上取整
x / 除数 = ( x * M - x ) >> ( 32 + n )
结论 :
M > 0 : x / 负除数 , 除数 = 2 ^ ( 32 + n ) / ( 2 ^ 32 - M ), 向上取整
取模
余数的符号与被除数相同.
基本公式
1
2
商 = 被除数 / 除数
余数 = 被除数 - 商 * 除数
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "argc % 3 = %d" , argc % 3 );
system ( "pause" );
return 0 ;
}
Release
无符号模2的幂
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "%d" , ( unsigned ) argc % 4 );
system ( "pause" );
return 0 ;
}
Release
特征
1
x % ( 2 ^ n ) = x & ( 2 ^ n - 1 )
有符号模2的幂
优化1:
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "%d" , argc % 4 );
system ( "pause" );
return 0 ;
}
Release
优化二:
1
2
3
4
5
6
7
int main ( int argc , char * argv [])
{
printf ( "%lld \n " , argc % 512 );
system ( "pause" );
return 0 ;
}
Release
1
2
3
4
5
6
7
8
9
mov rax , [ rsp + 28 h + arg_8 ]
lea rcx , aLld_0 ; "%lld\n"
cqo
and edx , 511
add rax , rdx
and eax , 511
sub rax , rdx
mov rdx , rax
call sub_1400011F0
特征
1
2
x > 0 : x % ( 2 ^ n ) = x & ( 2 ^ n - 1 )
x < 0 : x % ( 2 ^ n ) = (( x + ( 2 ^ n - 1 ) & ( 2 ^ n - 1 )) - ( 2 ^ n - 1 )
C++逆向
64位程序,只能通过函数内是否有虚表,判断是否是构造函数.
判断继承:
构造函数中,调父类构造,且同一位置,写两次虚表.
异常
为了解决频繁注册注销SEH,直接将异常表做好,当发生异常时,系统就会去.pdata节,找异常表..pdata节中的数据按RUNTIME_FUNCTION这个结构体进行解析(里面的地址是RVA).
1
2
3
4
5
00000000 RUNTIME_FUNCTION struc ; (sizeof = 0xC, mappedto_5)
00000000 FunctionStart dd ? ; offset rva
00000004 FunctionEnd dd ? ; offset rva pastend
00000008 UnwindInfo dd ? ; offset rva
0000000 C RUNTIME_FUNCTION ends
UNWIND_INFO结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _UNWIND_INFO {
UBYTE Version : 3 ;
UBYTE Flags : 5 ;
UBYTE SizeOfProlog ;
UBYTE CountOfCodes ;
UBYTE FrameRegister : 4 ;
UBYTE FrameOffset : 4 ;
UNWIND_CODE UnwindCode [ 1 ];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
* union {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry; //异常处理函数RVA, 可有可无,看Flag
* };
* OPTIONAL ULONG ExceptionData[]; */ //FunctionInfo的RVA
} UNWIND_INFO , * PUNWIND_INFO ;
1
2
3
4
5
//Flag
#define UNW_FLAG_NHANDLER 0x0
#define UNW_FLAG_EHANDLER 0x1 //置位, 说明有一个C++异常回调函数
#define UNW_FLAG_UHANDLER 0x2 //置位, 说明有一个C异常回调函数
#define UNW_FLAG_CHAININFO 0x4
1
2
3
4
5
6
00000000 UNWIND_INFO_HDR struc ; (sizeof = 0x4, mappedto_6)
00000000 Ver3_Flags db ? ; base 16
00000001 PrologSize db ? ; base 16
00000002 CntUnwindCodes db ? ; base 16
00000003 FrReg_FrRegOff db ? ; base 16
00000004 UNWIND_INFO_HDR ends
UNWIND_CODE结构体
1
2
3
4
5
6
7
8
typedef union _UNWIND_CODE {
struct {
UBYTE CodeOffset ;
UBYTE UnwindOp : 4 ;
UBYTE OpInfo : 4 ;
};
USHORT FrameOffset ;
} UNWIND_CODE , * PUNWIND_CODE ;
1
2
3
4
00000000 UNWIND_CODE struc ; (sizeof = 0x2, mappedto_7)
00000000 PrologOff db ? ; base 16
00000001 OpCode_OpInfo db ? ; base 16
00000002 UNWIND_CODE ends
定位Catch代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main ( int argc , char * argv [])
{
try
{
if ( argc == 1 )
{
throw 1 ;
}
else
{
throw 3.14 ;
}
}
catch ( int nVal )
{
printf ( "catch (int nVal) %d \r\n " , nVal );
}
catch ( double dbl )
{
printf ( "catch (double dbl) %lf \r\n " , dbl );
}
system ( "pause" );
return 0 ;
}
定位有异常处理函数的RUNTIME_FUNCTION结构.
定位UNWIND_INFO结构体.
定位FuncInfo结构体.
根据异常关系信息表,定位Catch代码块.
接下来的故事,就跟32位中,定位Catch代码块的步骤一样了.
注意
__CxxFrameHandler3 兼容之前的SEH异常处理,参数是FuncInfo的结构体指针.
__CxxFrameHandler4 更新版本的SEH异常处理,参数是FuncInfo4的结构体指针,在ehdata4_export.h头文件定义.