note20200604

notes from chapter 4

x86-64函数调用约定

VS编译出的64程序只有寄存器快速调用约定:前4个参数使用寄存器传递,超过的参数放在栈中,入栈顺序从右至左,由函数平衡堆栈。

  • 参数1:RCX;
  • 参数2:RDX;
  • 参数3:R8;
  • 参数4:R9;

    任何大于8字节或者不是1字节、2字节、4字节、8字节的参数必须由引用来传递(地址传递)。所有浮点参数的传递都是使用XMM寄存器完成的,它们在XMM0、XMM1、XMM2和XMM3中传递。

  • 参数1:XMM0;

  • 参数2:XMM1;
  • 参数3:XMM2;
  • 参数4:XMM3;

如果参数既有浮点类型,又有整数类型,例如”void fun(float, int, float,int)”, 那么参数传递顺序为笫1个参数(XMM0)、笫2个参数(RDX)、 笫3个参数(XMM2)、笫4个参数(R9)。

如果参数是结构体且大于8字节,在传递参数时,会先把结构内容复制到栈空间中,再把结构体地址当成函数的参数来传递(引用传递)。

类的函数中,由于rcx寄存器保存了this指针,所以函数的参数调用从rdx开始使用。

虚函数相关

判断一个函数为构造函数或者析构函数:

函数的栈初始化完毕后,使用”lea reg, off_l40007970”和”mov[reg], reg”特征初始化虚表,且返回值为this指针,就可以怀疑这个函数是一个构造函数或者析构函数,这里的off_l40007970就是虚表指针。

多重继承时,一个类中会有多个虚表。

于纯虚函数没有实现代码,编译器默认填充了_purecall函数的地址。_purecall函数的功能就是显示一个错误信息并退出程序。这是识别抽象类的一个依据。

在逆向分析中,如果发现一个类的虚表里面有_purecall虚表项,就可以怀疑这个类是抽象类。

notes from chapter 5

一些验证产品序列号的方法

将用户名等信息作为自变量,通过函数F变换之后得到注册码

使用的变换:

1
序列号=F(用户名)

通过该方法计算出来的序列号是以明文形式在内存中出现的,所以很容易就能在内存中找到它,从而获得注册码。

通过注册码验证用户名的正确性

使用的变换:

1
序列号=F(用户名)

这里要求F是一个可逆变换。而软件在检查注册码的时候,是利用F的逆变换G对用户输入的注册码进行变换的。如果变换的结果和用户名相同,则说明是正确的注册码,即:

1
用户名=G(序列号)

破解这种注册码检查方法时,除了可以采用修改比较指令的办法(爆破),还有如下考虑:

  1. 通过G(x)得出F(x),从而得到注册机;
  2. 给定一个用户名,通过穷举法找到一个G(x);
  3. 给定一个序列号,通过穷举法找到一个G(x);

通过对等函数检查注册码

使用的变换:

1
F1(用户名)=F2(序列号)

同时将用户名和注册码作为自变量(即采用二元函数)

1
特定值=F(用户名,序列号)

总结

注册码的复杂性问题归根到底是一个数学问题。

如何攻击序列号保护机制

数据约束性原则

在序列保护号程序中,正确的序列号会在某一时刻出现在内存中。

“12121212”为输入的序列号,2470为正确的序列号。

hmemecpy函数

即万能断点。

利用提示信息

智能搜索->查找ascii。

注册机制作

keymake

一般断点设置:

  1. 进入注册码生成函数(call xxxxxxxx)断点一次;
  2. 进行比较时断点一次,断在push正确注册码时(push xxxx call strcmp);

点内存方式,寄存器设置为上面push的寄存器。

F1(用户名)=F2(密码)型注册机时

需要分析出F1逆函数G,用用户名=G(F2(密码))的方式制作注册机。

破解nag

找到DialogBoxParam函数,将对话框处理函数指针指向的函数中的第一行指令改为jmp到配合前面打开窗口的关闭窗口函数EndDialog的下一行指令。

菜单功能限制

找到EnableMenultem()或者EnableWindow函数进行修改。

KeyFile

破解思路

破解过程

创建的文件名”KwazyWeb.bit”:

程序根据”KwazyWeb.bit”的内容跑迷宫,碰到”X”就算成功:

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
signed int __usercall problem_401033@<eax>(char a1@<al>)
{
_BYTE *v2; // [esp+4h] [ebp-4h]

v2 = left_403184;
if ( a1 )
{
if ( a1 == 1 )
{
left_403184 = left_403184 + 1; // right
}
else if ( a1 == 2 )
{
left_403184 = left_403184 + 16; // down
}
else
{
left_403184 = left_403184 - 1; // left
}
}
else
{
left_403184 = left_403184 - 16; // down
}
if ( *left_403184 == '*' )
return 0;
if ( *left_403184 == 'X' )
{
MessageBoxA(0, Text, Caption, 0);
SetWindowTextA(hWnd, String);
}
*left_403184 = 67;
*v2 = 32;
return 1;
}

迷宫:

1
2
3
4
5
6
7
8
9
10
11
****************
C*......*...****
.*.****...*....*
.*..**********.*
..*....*...*...*
*.****.*.*...***
*.*....*.*******
..*.***..*.....*
.*..***.**.***.*
...****....*X..*
****************

答案:

1
222122232211010011100333030011111211011211122332330332223221110011112233

16进制:

1
A9 AB A5 10 54 3F 30 55 65 16 56 BE F3 EA E9 50 55 AF

最终答案:

1
04 6d 69 78 69 1e 1c 12 a7 e3 88 87 e2 d2 a1 e1 9 44 5d 5e e7 e2 18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
raw=[0xA9,0xAB,0xA5,0x10,0x54,0x3F,0x30,0x55,0x65,0x16,0x56,0xBE,0xF3,0xEA,0xE9,0x50,0x55,0xAF]

flag=0xB7

output=[]

for i in raw:
output.append(flag^i)

f=open("my.bit","wb+")

o1=[0x04,0x6d,0x69,0x78,0x69]
o1.extend(output)

for i in o1:
f.write(bytes([i]))
print(hex(i)[2:],end=" ")

f.close()

check:

只运行运行一个实例

实现方法

查找窗口法

使用的api:findWindowA

互斥对象法

使用的api:CreateMutexA

使用共享内存块法

题目修改点:

1
CODE:0040101A 74 01                                   jz     short loc_40101D

改为:

1
CODE:0040101A EB 01                                   jmp     short loc_40101D

5.9 常用断点集合