X64逆向

寄存器

寄存器1

寄存器2

函数调用约定

四寄存器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, 28h
   
  mov   rcx, NULL
  mov   rdx, offset MSG1
  mov   r8, offset TITLE1
  mov   r9d, MB_OK
  call  MessageBoxA

  mov  ecx, 0
  call ExitProcess
  
  add   rsp, 28h
  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.

1

函数

传参

函数传参1

参数是结构体大小为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

函数传参2

函数传参3

特征

直接使用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

函数传参4

函数传参5

函数传参6

函数传参7

特征

会出现栈拷贝.

返回值

如果返回类型为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

特征

1
2
函数多了一个参数.
函数内返回时,以参数为目标做memcpy,并返回参数1.
注意
对低32位寄存器赋值,高32位自动清零.也就是说,如当我们看到函数传参,mov ecx, 2.不能说明参数为4字节,要看函数内使用的是RCX还是ECX.

注意1

注意2

变量相关

局部变量

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

特征

地址在全局数据区.

数组

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

数组1

Release

数组2

特征

1
2
3
4
一维数组寻址公式: 数组元素的地址 = 数组首地址 + sizeof(数组类型) x 下标
二维数组寻址公式: 数组元素的地址 = 数组首地址 + sizeof(一维数组类型) x 下标1 + sizeof(数组类型) x 下标2
Debug版和Release版的寻址公式,32.
不同的是64Release版,数组初始化使用多媒体指令.

流程控制语句

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

IF单分支1

特征识别

IF单分支2

图形识别

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

IF双分支1

特征识别

IF双分支2

图形识别

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

IF多分支1

特征识别

IF多分支2

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的幂

除数为非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;
}

无符号除数为非2的幂Magic无进位

特征

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;
}

无符号除数为非2的幂Magic有进位

特征

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;
}

有符号除数为正2的幂

特征

1
2
x >= 0:x / 除数 = x >> n
x < 0 :x / 除数 = (x + (2 ^ n - 1)) >> n

还原

1
除数 = 2 ^ 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;
}

有符号除数为正非2的幂Magic为正

特征

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;
}

有符号除数为正非2的幂Magic为负

特征

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的幂

除数为负非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;
}

除数为负非2的幂Magic为正

特征

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;
}

除数为负非2的幂Magic为负

特征

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

无符号模2的幂

特征

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

有符号模2的幂1

优化二:

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+28h+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
0000000C 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_INFO结构体

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;
}
  1. 定位有异常处理函数的RUNTIME_FUNCTION结构.

定位Catch代码块1

  1. 定位UNWIND_INFO结构体.

定位Catch代码块2

  1. 定位FuncInfo结构体.

定位Catch代码块3

定位Catch代码块4

  1. 根据异常关系信息表,定位Catch代码块.

接下来的故事,就跟32位中,定位Catch代码块的步骤一样了.

定位Catch代码块5

注意

__CxxFrameHandler3 兼容之前的SEH异常处理,参数是FuncInfo的结构体指针.

__CxxFrameHandler4 更新版本的SEH异常处理,参数是FuncInfo4的结构体指针,在ehdata4_export.h头文件定义.


相关内容

0%