2020.05.18-2020.05.25
从上周开始不用写周报了
感觉没有监督就懒散了23333
壳
附加在原始程序上,通过windows加载器载入内存后,先于原始程序执行,在执行过程中对原始程序进行解密、还原,还原后把控制权还给原始程序,执行原来的代码。可以防止程序文件被非法修改或者静态反编译
许多木马和病毒都喜欢用壳来保护和隐藏自己。对一些流行的壳,杀毒引擎先对目标软件进行脱壳,再进行病毒检查;对大多数私人壳,杀毒软件不会开发解压引擎,而是直接当成木马或病毒处理,因此,商业软件出于兼容性的考虑,很少使用加壳保护,而在其他方面提高软件保护强度
不同的外壳侧重方面不一样,有的侧重压缩,有的侧重加密,还有一些提供额外的功能,如注册机制、使用次数、时间限制等
压缩引擎
一些加壳软件调用现成的压缩引擎对文件进行压缩,在选择压缩引擎时要保证解压速度快,这样加了壳的文件运行速度才不会受到太大影响
压缩壳
- upx
- ASPack
加密壳
- ASProtect
- Armadillo
- EXECryptor
- Themida
虚拟机保护
许多解释性的语言,如java的jvm。这里讨论的虚拟机(与vmware不同)将一系列指令解释成字节码后放在一个解释引擎中执行,从而对软件进行保护。
虚拟机引擎
一个虚拟机引擎由编译器、解释器和虚拟cpu组成,还会搭配一个或多个指令系统。虚拟机在运行时,现根据自定义的指令系统把已知的指令解释成字节码并放在pe文件中,然后将原始代码删除,改成直接进入虚拟机执行。
挑事者跟踪并进入虚拟机后很难理解原始指令。想要理解程序就必须对虚拟机引擎进行分析
虚拟机技术以效率换取安全,一条指令经过虚拟机处理,体积会膨胀几十几百倍。因此,VM保护通常经过sdk方式,只把较为重要的代码保护起来
c++
输入和输出
可以用scanf和printf,也可用c++中增加的输入输出库
如果要使用输入输出时,需要包含头文件:
1 |
使用cin和count进行输入和输出
1 |
|
cin和count这两个运算符可以自行分析处理(强制转换)数据类型,因此无需像使用scanf和printf那样给出格式控制字符串。
new和delete
用来动态分配内存和释放内存
1 | int *p = new int; //分配1个int型的内存空间 |
类和对象
类只是一种数据类型,本身并不占用空间
定义
1 | class Student{//关键字class专门用来定义类,Student是类的名称,类名首字母一般大写 |
对象指针
1 | //在栈上分配内存 |
成员变量和成员函数
在类中直接定义函数时,不需要在函数名前加类名;当成员函数定义在类外时,要在函数名前加类名
1 | class Student{ |
在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会
内联函数会将函数调用处用函数体代替,所以尽量在类内部作声明,在类外对函数定义
函数调用是有时间和空间开销的。程序在执行一个函数之前需要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;执行完之后,还要将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两句语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就不容忽视。
为了消除函数调用的时空开销,C++在编译时将函数调用处用函数体替换,即内联函数。
内联函数的缺点:编译后的程序会存在多份相同的函数拷贝。
内联函数的代码在编译后就被消除了
类成员的访问权限和类的封装
控制成员的访问权限:成员访问限定符public、protected、private
在类的内部,成员可以互相访问;在类的外部,只能通过对象访问public属性的成员
约定成员变量以*m_*开头,可以直接看出是成员变量,又可以和成员函数中的形参名字区分
1 |
|
下面的写法是错误的
1 | stu.m_name = "小明"; |
因为私有的成员变量不能通过对象直接访问,必须借助public属性的成员函数来修改
类的封装
private:作用在于更好的隐藏内部的实现,不希望外部知道或只在内部使用的成员声明为private
public:向外部暴露的接口声明为public//如上面的setname()和setscore()
(实际项目开发中,成员变量都建议声明为private,只将允许通过对象对用的成员函数声明为public)
protected:在类外也不能通过对象访问,但在派生类内部可以访问
给成员变量赋值的函数通常以set开头;读取成员变量的值的函数通常以get开头
private 和 public的使用体现了类的封装性,即:尽量隐藏类的内部实现,只向用户提供有用的成员函数。
构造函数
一种特殊的成员函数,名称与类名完全相同,可用于对某些成员变量设置初始值。创建对象时系统会自动调用构造函数进行初始化工作
1 |
|
初始化const成员变量
如:对下面m_len的初始化
1 | class VLA{ |
只能使用
1 | VLA::VLA(int len):m_len(len){ |
堆和栈的区别
- 内存分配
堆由程序员分配和释放;栈由编译器自动分配 - 申请方式
堆:用malloc、new申请;栈:如int a - 大小限制
堆:向高地址扩展,用不连续的内存空间存储。大小受到系统虚拟内存大小的限制,因此获得的空间比较大
栈:向低地址扩展,一块连续的内存空间。栈的大小是固定的,能申请的空间比较小 - 效率
堆:速度慢,容易产生内存碎片
栈:系统分配,速度快
攻防世界逆向题
getit
首先放到ida里看伪代码
其中
t存放的是“SharifCTF{???}”
u存放的是“*******************************************”
s存放的是“c61b68366edeb7bdce3c6820314b7498”
并且s的长度和t里的?一样;“SharifCTF{”长度为10
整段伪代码大概就是:
先改变t里每个?的值,然后输出到文件,然后再改变整个t,再输出一次,最后把这个文件删了
在第一次输出时伪代码是:
1 | fprintf(stream, "%s\n", u, v5); |
只有一个%s,看起来只输出了u,并没有输出v5
在第二次输出时用到了fseek(),其作用是将文件的指针移动:
由于u的内容一直都是"…**…",那么也就是说存放flag的t被最后一个循环覆盖为了一堆 *
综上,flag应该出现在第一次输出时的t里,得到flag的过程为程序的第一个循环,可以得到代码如下:
1 |
|