前言
首先免责声明:本篇博客并没有摘抄书中所有的知识点,要全面地学习还是要看书:)
废话少说,开始学习吧
x64程序逆向技术
32位程序和64位程序有什么区别呢:Windows上的64位和32位的区别是什么?
x86名称是怎么来的?因为因特尔公司有一个系列的cpu名称都以86结尾,而它们都是32位的,随着时间的推移,慢慢地大家就使用x86的说法指代32位cpu了
x64应用程序的调用规定
上篇文章介绍了在x86架构下的一些函数调用规定,如stdcall,_cdelcl
等等。但是在x64架构中只有一种调用规定:前四个参数放在固定的寄存器中,剩下的参数从右往左入栈,由函数调用者平衡栈。(pwn中也有用到)
虽然前四个参数放在寄存器中,但是编译器依旧为这些参数预留了栈空间(4*8=32字节)。如果函数的参数比较多,寄存器的数量不够,可以使用预留的栈空间。
参数传递规则如下
参数 | 类型 | 浮点类型 |
---|---|---|
第一个参数 | RCX | XMM0 |
第二个参数 | RDX | XMM1 |
第三个参数 | R8 | XMM2 |
第四个参数 | R9 | XMM3 |
XMM寄存器是啥?深入解析计算机系统中的XMM寄存器
参数传递(具体汇编代码看书)
- 当函数参数个数小于等于4时,编译器操作如下:
- rdi寄存器入栈,保存环境
- rsp(栈顶指针)会减少32字节,由于栈是从低地址到高地址增长的,所以栈会开辟32字节的空间。
- 接下来是函数传参:从右往左把对应的参数放入寄存器中
- 调用函数
- rsp增加32字节,用于平衡栈。(在函数外平衡)
- rdi寄存器恢复原值
- 当函数参数个数大于4个时,其他步骤和上面的一致,编译器只改变第三步的做法:
由于参数是从右往左入栈,当剩下的参数个数>4个时,当前的参数放入栈,当参数个数<=4时,参数放到寄存器中
放上代码
// 调用函数
Add(1,2,3,4,5,6);l
//对应汇编语言如下
000000014000107D mov ecx, [rsp+40h] ;
0000000140001081 mov dword ptr [rsp+28h], 6 ;参数6入栈
0000000140001089 mov dword ptr [rsp+20h],5 ;参数5人栈
0000000140001091 mov r9d, 4 ;参数4
0000000140001097 mov r8d, 3 ;参数3
000000014000109D mov edx, 2 ;参数2
00000001400010A2 mov ecx, 1 ;参数1
00000001400010A7 call sub_140001005 ;调用Add函数
控制语句和循环语句
在程序中,控制语句一般会涉及到程序的核心功能,比如验证序列号等等,所以识别各种控制语句的特点就十分重要了。(借用书中的例子,介绍if
, while
, do while
, switch
等等语句的特征)
if语句
if语句的语法如下
if(expression)
{
if_code;
} // if_end
运行规则:
- 计算
expression
的值,如果为true
就执行if_code
,如果为false
就跳转到if_end
因此,if语句可以简化为一个向下的,有条件的跳转。如果用图像表示法,有条件的跳转(jxx
)用虚线表示,无条件的跳转(jmp
)用实线表示。
if-else语句
if-else
语句语法如下
if(expresion){
if_code;
}else {
else_code;
} // if_end
运行规则
- 计算
expression
的值,如果为true
就执行if_code
,忽略else _code
,如果为false
就执行else_code
,忽略if_code
需要两个跳转语句来实现if-else
语句:当if_code
语句执行完毕后,需要无条件跳转到if_end
。当expresion
为false
时,需要直接跳转到else_code
,这是个有条件跳转
特征如下图(借用书中的图片)
switch语句
switch
语句的语法如下
switch(expression):
case constant_expression1:
code1;
case constant_expression2:
code2;
default:
code3;
需要注意break会影响程序流
接下来是编译器怎么实现switch的,这部分内容我没有验证过。。摘抄一下书里的内容
- 当case的个数<=6个时,编译器会使用if-else来实现switch语句
- 当case的个数较多时,编译器会使用if语句实现switch语句
为了减少if条件的判断次数,编译器使用判断树来实现
判断树是一棵平衡二叉排序树,关于平衡二叉排序树的介绍,可以看这一篇博客:平衡二叉排序树
do-while循环
do-while循环的语法如下
do
{
code;
}while(expression)
//do_while_end
运行规则:
- 执行
code
处的代码 - 如果
expression
的值为true
,程序执行步骤1,否则跳转到do_while_end
处
由于do-while循环的code
一定会执行至少一次,所以不需要额外的有条件向下跳转,只需要一个向上跳转循环执行代码
特征如下图:
while循环
while循环语法如下
while(expression) // while_begin
{
code;
}// while_end
运算规则
- 计算
expression
的值 - 如果为真就执行
code
处的代码,并且回到步骤1,如果为假就跳转到while_ends
处
实现这个循环需要两个跳转语句:第一个有条件跳转需要在expression
为false
时跳转到while_end
处,第二个无条件跳转需要在code
执行结束后跳转到while_begin
处,判断要不要开始下一次循环
特征如下
for循环
for循环语法如下
for ( init; condition; increment )
{
code;
} // for_end
由于for循环有点复杂,这里先把特征图放出来,再介绍for循环的流程
运算规则
- 进入for循环前,先计算
init
的值,也就是初始化步长 - 接下来程序跳转到
code
处的代码前面(对应图中for_if向下跳转
) - 判断
condition
的值,如果结果为true
,执行code
语句,执行结束后调用increment
更新步长(对应图中的for_step向上跳转
,然后运行步长代码
)。如果结果为false
,执行jxx
向下跳转,循环结束
休息一下:)
休息结束:)
一些个人觉得不用解释原理的知识点:
- 使用汇编语句访问数组有两种写法
- [数组首元素地址+n]
- [数组首元素地址+n*寄存器]
关于虚函数的部分,看不懂。。。而且目前我也没有遇到过(可能遇到了但没有认出来),先跳过吧
本文结束
后记
接下来的内容与本文无关,都是一些废话:<
感觉这次的文章好水。。主要是跳过了关于虚函数的内容。。而c语言的循环和选择语句又比较简单。。
照例的meme时间
打ctf比赛被零封时,我的心路历程be like↓
阅读题目->信心满满->发现不对劲->vocal一道题都不会->遗憾离场:(
看大佬wp时,心情be like↓
扯一下最近的学习情况
- 在复现2023i春秋冬季赛的re题目,学到好多新的东西,但是有好多内容是我现在看不懂的。。。
顺便膜拜一下官方wp的exp,真的很优雅
def xor_encrypt(input_file_path, output_file_path, key):
try:
with open(input_file_path, 'rb') as input_file:
content = input_file.read()
encrypted_content = bytes([byte ^ key for byte in content])
with open(output_file_path, 'wb') as output_file:
output_file.write(encrypted_content)
except Exception as e:
print(str(e))
input_file_path = 'DATA101.bin'
output_file_path = 'DATA101_out.bin'
encryption_key = 0x33
xor_encrypt(input_file_path, output_file_path, encryption_key)