黑眼探光明,逆向寻真理!
为了能够更好的学习零基础逆向安全课程,我们决定C++基础开始说起,懂得正向原理,才能事半功倍的取学习逆向知识。
一、进制及其转换
学习C++和逆向安全,为什么要学习进制?难道说我们日常用的数字还不够么?答案是:当然不够
下面我们就先来看一下什么是进制,常用的进制有哪些,不同进制之间的关系,以及他们之间如何的转换。
进制,又称进位计数制,是人为的一种带有进位的计数方式,我们最常用的进制是十进制,
那么我们就用十进制来对进制的概念做一个深入的了解.
什么是十进制?
10进制非常简单,我们现实生活中最常用的就是十进制
10进制是由 0,1,2,3,4,5,6,7,8,9 10个符号组成的逢10进1的一种进制。
10进制中是没有10这个符号的, 哪个符号来代表10呢?其实是没有的
10是由1和 0 2个符号组成的。
还有哪些其他的进制?
知道了10进制的定义,我们引申出n进制定义
N进制定义:由n个符号组成 逢n进位
N进制中是没有N这个符号的
例如:
2进制有2个符号组成,里面有2吗?没有,逢2进位了,只有 0和1这2个符号,电路开关采用的就是二进制
1小时有60分钟,这是60进制,我们在电子表里是看不到60这个数字的
1天有24小时,这是24进制,同样我们手机里也看不到24点这个时间
再比如1打啤酒是12瓶,这是12进制,还有很多很多其他类型的进制,当然这些进制并不是很常用,也对我们后面的学习没有什么帮助
学习C++和逆向安全需要哪些进制?
由于我们要学习编程的原理,而不只是单单的学习C/C++等编程语言,
所以就要去学习计算机底层实现的原理。
但是计算机只认识2进制,也就是0和1.
实际上我们无论写出什么样的代码,最后都是变成2进制被计算机执行的。
也就是说我们要学习进制,其实主要就是要学习2进制。
以及十六进制,后面我们会知道十六进制就是二进制的简写方式.毕竟二进制及其不方便
进制的转换
进制计算其实我们谁都会,只是我们没有把他抽象成文字,或者说并不熟悉
例如
70秒你立刻就知道是1分钟零10秒,想想怎么算的呢?
25瓶你立刻就知道是两打零1个,又是怎么计算的呢?
自己莫名其妙的就会吧? 只是自己没有总结方法吧?
所以啊,我不是教你知识, 只是引导你使用你已经会的东西.
以上两个例子,就是十进制转其他进制
70/进制 = 70/60 = 1 余10 = 1分10秒
我们再来看下常用的进制转换
1.十进制转二进制
那么十进制3转成二进制等于多少?
3/2 = 1 余1 十进制3 = 二进制 11 是不是很简单呢?
需要除多次的十进制转二进制大家可以不掌握,因为我们只要知道原理就可以了
例如 十进制 12 转 二进制 12 = 1100
12/2 = 6余0
6/2=3余0
3/2=1余1
1/2=0余1
将余数从下向上依次抄写,就是1100
一小箱白酒可以装2瓶 ,一个礼盒可以装2小箱,一大箱又可以装二个礼盒
请问 1大箱是多少瓶? 什么感觉自己还是会的? 答案很简单 8瓶
其实就是对应的二进制转十进制
1000 = 8
1表示1大箱 第一个0表示0礼盒 第二个0表示0小箱 第三个0表示0瓶
2.二进制转十进制
一个十进制数123 ,1为什么在百位?2为什么在十位?3为什么在个位?
实际上123 = 1*10*10 + 2*10 +3 = 1*进制*进制 + 2*进制 +3
2在十位的原因是因为他有2个10,是进位而来的,1在百位是因为有10个10进位而来的
刚才的1000 也是 1*2*2*2+0+0+0 =8
一个二进制111
实际上111 = 1*2*2 +1*2+1 = 7
这样2进制就转换成10进制了
二进制 10000000000 = 十进制 1024
3.拓展内容,十进制转其他进制
十进制数12 如何转成二进制
其实很简单,我们把数值都放到个位,看看他怎么进位即可
我们12全放在个位是不行的,二进制只有1和0,那么我们需要进位、
所以
12/2 = 6 余0
代表我们有6个2进位到十位,余0代表个位为0,我们暂且可以写成60,当然2进制不能有6,所以我们继续进位
6/2 = 3 余 0
代表我们有3个2进位到百位 ,余0代表十位为0,我们暂且写成300,但是2进制不能有3,所以我们继续进位
3/2 = 1 余 1
代表我们有1个2进位到千位 ,余1代表百位为1,我们可以写成1100,里面已经没有除了1和0之前其他的数值,那么我们得到了最终结果1100
十进制123 转成 9进制等于多少? 用同样的方法计算一下
146
十六进制
我们学习了2进制和十进制,下面来学习一下16进制
1.为什么要学16进制?
为什么会牵扯出16进制呢?16进制实际上是2进制的简写方式。
为什么要简写呢?
大家试想一下,如果屏幕上面全都是0和1一串数值,让你去看或则记忆,会是一种上面样的情况
例如
11010100100101000101111101001010101
疯掉了吧?
所以我们必须要有一种简写的方式来帮助我们观看
聪明的人类是这样设计的
4位2进制最小是0000,最大是1111,也就是 0 - 15,那么我们就可以用一位的16进制来表示4位的2进制了,16进制的16的符号分别是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
例如 1101 我直接可以用 0xD 表示了
11011111 就是0xDF 是不是看起来舒服多了,看起来不那么累了
以后我们看到的二进制,无论是VS的反汇编窗口还是IDA,OD,CE等工具里,全部都是以16进制的形式体现出来的
2.数据宽度,字节和位
1位在10进制中就是表示 0 -9
而在2进制 也就是计算机中表示 0或则1
这是理论上的最小单位 1位,但是应用起来很麻烦
所以我们真正应用的最小单位是 字节 一个字节 等于8位
1字节8位的最大值 = 11111111 转成16进制就是FF
1字节8位的最小值 = 0000000 转成16进制 就是 00
字节是以后我们应用的最小单位 范围为00-FF
进制符号化_拓展
我们来看下十进制的定义 0123456789 10个符号 逢10进1
注意是说的10个符号,这10个符号可以不是0123456789
如果是)!@#¥%……&*( 这10个符号 你还会计算吗
% + …… = !! 能看出来吗?
打乱这种就可以达到加密的效果
提问:1+1=1 正确吗?
9进制 9个符号分别为543672189 (分别对应012345678 )
现在5已经代表0了...
那么
176+829=?
176 和 829 分别对应 643和 758
643+758= 1512
然后转换回现在的定义 4 2 4 3
二、创建项目,了解main函数
学完了进制的概念和转换,接下来我们进入了正式的C++和逆向安全的学习,这里我们采用的工具是VS2019,大家可以到WX公众:任鸟飞逆向的工具资料中进行下载,或者到VS的官网下载Visual Studio 2022 IDE - 适用于软件开发人员的编程工具
1.C语言和C++的关系
在学习C++之前,我们先来了解一下C语言和C++的关系
C语言是过程性语言,而C++则是在C的基础上"++"也就是添加了面向对象编程,函数模板,泛型编程等很多东西
简单来说C就是C++的一个子集,C++ 是C 的超集,C++ 在C语言的基础上增加更多的内容而已
2.编写软件的过程
软件的编写其实是一个很复杂的过程,而我们可以利用工具讲这个过程变的简单化,下面我们来看看这个过程有哪些步骤
源代码---->编译源代码变成目标代码---->链接(将启动代码,库代码,目标代码组合起来)---->可执行代码,
从这个过程中大家可以看到这里需要编译器,链接器等工具
所以这个过程需要一个集成开发环境又称IDE.
就是集合编辑、编译、调试三大功能为一体的工具,我们选择Visual Studio简称VS。
最新版VS2019.VS2022,选择次新版本永远是最明智的选择,所以我们选择2019版本
3.创建第一个程序
启动VS来到初始界面
初始界面,点击创建新项目
然后选择 控制台应用,点下一步
给项目起个合适的名字和目录,点击创建(虽然新版本的VS支持中文名字和目录,
但是还是建议都使用英文命名,这里用中文的目的是为了让大家看得更加清晰)
这个时候,我们只有一个文件,30天入门C++.cpp
.cpp 就是C++文件,英文就是 c plus plus 的意思 即C++,而C语言创建的则是.c
这样第一个项目就创建完毕了.
4.编辑----编译生成----运行----调试
创建完毕的项目,已经有了初始代码
这里暂时不需要我们自己编辑
我们直接编译
下面显示“成功”,没有“失败”就是编译成功了。
5.main函数
一个程序必须要有一个main函数作为入口函数
我们把 原有的代码注释掉
// 注释单行,
/* ... */ 注释一段,
被注释的代码意思就是相当于没有, 不参与编译,但是又方便我们观看,也可以随时取消注释
点生成,出现错误提示
大概意思就是说一个程序必须有一个main 函数,因为他是入口函数。
没有入口函数,程序不知道从哪里开始执行, 简单说,做任何事,你总要是给我一个开始点,逛个商场我们还要从门口开始呢不是吗?
而程序的这个入口点就是main函数,main函数被启动代码调用,相当于被操作系统调用。
启动代码是操作系统和程序的桥梁,C++规定int main固定写法,而且return 0表示程序正常结束
当然也有其他情况,是和启动代码有关的,不在这里做过多研究。
6.运行生成的程序
生成的程序是可以运行的,CTRL+F5
也可以在项目上右键,打开文件夹
也可以打开项目文件夹找到生成程序直接双击
双击后我们发现,程序没有出来
这并不是程序执行失败,而是因为程序一下就执行结束了,我们还没来得及看到他的输出信息。
所以,我们在代码最下面加一句getchar(); 就好了这句代码的意思是等待用户输入信息,所以他不会立刻被执行完毕
开始先抄写,不要抄错
同时也可以点F5的方式,对程序进行调试执行,我们放到后面详细讲解。
7.注意事项
(1)标点符号用英文,尤其喜欢命名中文变量的同学,要给输入法设置成半角符号
(2)区分大小写
(3)开始先抄袭模仿,再学习每一句代码的真实含义,一步步来
8.其他问题
界面打乱了怎么恢复?
可以点击 视图----解决方案资源管理器
也可以,重置或则保存布局
关掉项目怎么重新打开?
项目文件夹中.sln 是打开项目的启动项
.cpp 是我们写的代码
.exe 是给用户的执行程序
三、printf与cout
我们完成第一个C++程序的编写, 并且成功的输出了一行代码,那么这条输出的代码是如何生成的呢?
1.输入输出, 面向过程和面向对象
cout是一个对象, 他来执行输出的动作,
我们把上述代码替换成下面代码来学习,
这是C语言常用的方式,给你一个输入"Hello World" 让printf帮我们在控制台输出结果,
这是一个过程,为什么要这么做呢?
主要原因有2个:
第一,std::cout 即使用了名字空间,又使用了对象,还使用了符号重载,这些名词听起来都很陌生吧
我觉得这2个知识不适合新手上来就学,这样就可以放到后面点的位置再学习,
第二,printf比 std::cout
当然cout比printf 也有很多优势,后面我们会了解,但是基本上 2者的功能是可以互相替代的。
2.printf的含义
print是打印,f 就是format,格式化的意思,合起来是格式化输出字符串的意思
printf("Hello World \n"); 打印双引号中的内容
\n 换行符,不会被打印出来而是直接换行
这里的 \ 叫转义字符,不会被打印,而是联合后面的n成为一个符号
还有\t,即tab制表符等等
有的同学可能会说,那么想打印\ 岂不是打印不出来了?
打印\ 需要写 \\
3.printf也可以这样使用
printf("a = %d ,b = %d \n",30,70);
printf("a+b = %d \n",100);
printf括号中的内容代表传入的参数列表
printf括号中的逗号将参数列表分割成几个部分,表示传入的每个参数
其中的%d表示用后面的参数30和70来依次替换,其中的d代表十进制有符号整数
F5运行后结果为
%d也可以写成%3d,%03d等等格式
也可以用f来代表十进制浮点型等等
F5运行后结果为
我们还可以用%3d来将打印的结果按3位对齐,表示数值占3个宽度,也可以用%04d来将打印结果按4位对齐,表示数值占4个宽度不足4位的部分用0来代替,
的部分用0来代替
F5运行结果为
printf("a = %f \n",12.3456);
其中的%f表示打印一个浮点型的数值,
我们也可以用%.2f来四舍五入保留小数点后两位。
在printf("a = %d ,b = %f \n",11,22.2);中有3个参数
分别为第一个参数"a = %d ,b = %f \n" (参数的概念我们放到后面来讲,这里只要做个了解即可)
第二个参数11
第三个参数22.2
当然如果有更多的%d %f等等,也可以有第四个第五个等等的参数
这里的第二个第三个参数不止可以用数值来表示,也可以用变量来表示
比如
输出结果为
这里的int a = 11;表示申请一个类型为int的变量,并为它赋值11
double b =22.22;表示申请一个类型为double的变量,并为它赋值22.22
4.简单的了解下变量
在printf的逗号后面,我们可以使用数值,也可以使用变量, 变量可以有很多的类型,
比如int 整数型,float小数,double双精度小数等等
%d 不是固定的
%f 打印小数 float 默认6位
%lf 打印double
%.2f 打印2位
%X 打印16进制(0x 或则 h 代表16进制)
5.小数常量的问题
C++ 中默认小数常量都是double 类型(我们在代码中遇到的直接的数值通常都是常量,常量的概念后面会讲到)
在给a赋值时,常常会因为类型不同,生成如下警告
解决方法,在数值后面加一个小写的f
那么float 和double 有什么区别呢?
float只能表示6位,后面的都是假的了,这就是double存在的意义
四、预处理指令之头文件
我们之前已经说过,代码在进行编译前,需要对源文件进行简单的加工,我门把这个操作叫做预处理。、
当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
C语言提供了多种预处理功能,如宏定义、文件包含、条件编译(兼容不同系统)等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
下面我们着重了解一下预处理指令中的头文件。
我们新建一个类型为.h的头文件,并且在里面加入一条int a = 100;
然后我们回到有main函数的源文件中并且将a进行打印
此时他会提示未定义的标识符“a”。
因为我们并没有在这个文件中定义a这个变量,而是在其他文件中进行的定义,
于是在文件头部添加一条包含头文件的指令#include “hhh123.h”
此时可以编译成功。
包含头文件
有的文件用<>,有的却用"",那么二者到底什么区别呢
<>和"“表示编译器在搜索头文件时的顺序不同,
<>表示从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录,不搜索当前目录
”“是表示从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录。
所以,系统头文件一般用<>,用户自己定义的则可以使用”",加快搜索速度。
一般C++的头文件都是不带.h的,而C的头文件都是带.h的,
头文件的第一句代码都默认,#pragma once 这句 预处理指令,
只要在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次。
防止包含了多次,产生错误。
头文件无需死记硬背,只需要跟着课程学下去即可,如果有疑问可以到WX公众:任鸟飞逆向进行留言和交流学习。
五、编写进制转换器
前面我们讲了一些C++的基本概念和指令,这节课我们来利用这些知识去编写一个简单进制转换工具。
在写代码之前,我们要再了解一个新的指令scanf,当然在新版本的VS中我们需要用到scanf_s这个指令
scanf_s("%d",&a);表示为变量a输入一个无符号整数型的数值
这里的%d后面不要加\n换行符
比如
scanf_s("%d",&a);表示为变量a输入一个无符号整数型的数值
这里的%d后面不要加\n换行符
比如
运行结果为
这里的3和4是需要我们自己输入并按回车键的
我们也可以任意输入其他的整数型数值
再输入小数的时候我们尽量用%lf
printf可以传入多个参数来打印多个值,scanf也同样可以
比如
F5运行后输入两个浮点型,中间用英文的“,”隔开,然后回车,结果如下
注意这里的逗号不要输入全角的,否则结果就会变成
这样是错误的
scanf在后面的应用中几乎是用不到的,所以会用即可
运行结果为
这里的3和4是需要我们自己输入并按回车键的
我们也可以任意输入其他的整数型数值
再输入小数的时候我们尽量用%lf
printf可以传入多个参数来打印多个值,scanf也同样可以
比如
F5运行后输入两个浮点型,中间用英文的“,”隔开,然后回车,结果如下
注意这里的逗号不要输入全角的,否则结果就会变成
接下来我们用scanf_s和printf来做一个十六进制和进制的转换器,代码如下
这样我们在输入一个十进制后,会输出它相应的十六进制
这样一个简单的进制转换工具就完成了,当然这只是逆向安全的开始,我们连第一步还没有迈出,随着后面的课程更新,我们会在C++的学习中逐渐的接触和学习逆向知识。
欢迎大家来到WX公众:任鸟飞逆向进行学习交流。