2020.05.18-2020.05.25

从上周开始不用写周报了
感觉没有监督就懒散了23333

附加在原始程序上,通过windows加载器载入内存后,先于原始程序执行,在执行过程中对原始程序进行解密、还原,还原后把控制权还给原始程序,执行原来的代码。可以防止程序文件被非法修改或者静态反编译
许多木马和病毒都喜欢用壳来保护和隐藏自己。对一些流行的壳,杀毒引擎先对目标软件进行脱壳,再进行病毒检查;对大多数私人壳,杀毒软件不会开发解压引擎,而是直接当成木马或病毒处理,因此,商业软件出于兼容性的考虑,很少使用加壳保护,而在其他方面提高软件保护强度
不同的外壳侧重方面不一样,有的侧重压缩,有的侧重加密,还有一些提供额外的功能,如注册机制、使用次数、时间限制等

压缩引擎

一些加壳软件调用现成的压缩引擎对文件进行压缩,在选择压缩引擎时要保证解压速度快,这样加了壳的文件运行速度才不会受到太大影响

压缩壳

  1. upx
  2. ASPack

加密壳

  1. ASProtect
  2. Armadillo
  3. EXECryptor
  4. Themida

虚拟机保护

许多解释性的语言,如java的jvm。这里讨论的虚拟机(与vmware不同)将一系列指令解释成字节码后放在一个解释引擎中执行,从而对软件进行保护。

虚拟机引擎

一个虚拟机引擎由编译器、解释器和虚拟cpu组成,还会搭配一个或多个指令系统。虚拟机在运行时,现根据自定义的指令系统把已知的指令解释成字节码并放在pe文件中,然后将原始代码删除,改成直接进入虚拟机执行。
挑事者跟踪并进入虚拟机后很难理解原始指令。想要理解程序就必须对虚拟机引擎进行分析
虚拟机技术以效率换取安全,一条指令经过虚拟机处理,体积会膨胀几十几百倍。因此,VM保护通常经过sdk方式,只把较为重要的代码保护起来

c++

输入和输出

可以用scanf和printf,也可用c++中增加的输入输出库
如果要使用输入输出时,需要包含头文件:

1
#include <iostream>

使用cin和count进行输入和输出

1
2
3
4
5
6
7
#include<iostream>
using namespace std;
int main(){
int x;
cin>>x;//多输出:cin>>x>>y;
cout<<"The int number is x= "<<x<<endl;
//endl即end of line 表示结尾进行换行

cin和count这两个运算符可以自行分析处理(强制转换)数据类型,因此无需像使用scanf和printf那样给出格式控制字符串。

new和delete

用来动态分配内存和释放内存

1
2
3
4
5
int *p = new int;  //分配1个int型的内存空间
delete p; //释放内存
//以及
int *p = new int[10]; //分配10个int型的内存空间
delete[] p;

类和对象

类只是一种数据类型,本身并不占用空间

定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student{//关键字class专门用来定义类,Student是类的名称,类名首字母一般大写
public://表示类的成员具有公开的访问权限,其他还有private等
//成员变量
char *name;
int age;
float score;
//成员函数
void say(){
cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}
};
class Student LLL;//创建对象 ,class可不要,LLL也可为数组
// 用.访问成员
//与结构体贼相似

对象指针

1
2
3
4
5
6
7
//在栈上分配内存
Student stu;
Student *pStu = &stu;

//在堆上分配内存
Student *pStu = new Student;
//访问时通过"->"

成员变量和成员函数

在类中直接定义函数时,不需要在函数名前加类名;当成员函数定义在类外时,要在函数名前加类名

1
2
3
4
5
6
7
8
9
10
11
12
class Student{
public:
char *name;
int age;
float score;
void say(); //函数声明
};
//函数定义
void Student::say(){// 如果写成inline void Student::say 就为内联函数
//::是域解析符(作用域运算符、作用域限定符)用来连接类名和函数名
cout<<name<<"的年龄是"<<age<<",成绩是"<<score;
}

在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会
内联函数会将函数调用处用函数体代替,所以尽量在类内部作声明,在类外对函数定义

函数调用是有时间和空间开销的。程序在执行一个函数之前需要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;执行完之后,还要将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两句语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就不容忽视。
为了消除函数调用的时空开销,C++在编译时将函数调用处用函数体替换,即内联函数。
内联函数的缺点:编译后的程序会存在多份相同的函数拷贝。
内联函数的代码在编译后就被消除了

类成员的访问权限和类的封装

控制成员的访问权限:成员访问限定符public、protected、private
在类的内部,成员可以互相访问;在类的外部,只能通过对象访问public属性的成员
约定成员变量以*m_*开头,可以直接看出是成员变量,又可以和成员函数中的形参名字区分

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
#include <iostream>
using namespace std;
class Student{
private: //私有的
char *m_name;
float m_score;
public: //共有的
void setname(char *name);
void setscore(float score);
};
void Student::setname(char *name){
m_name = name;
}
void Student::setscore(float score){
m_score = score;
}
int main(){
//在栈上创建对象
Student stu;
stu.setname("小明");
stu.setscore(92.5f);
//在堆上创建对象
Student *pstu = new Student;
pstu -> setname("李华");
pstu -> setscore(96);
return 0;
}

下面的写法是错误的

1
2
3
stu.m_name = "小明";
stu.m_score = 92.5f;
stu.show();

因为私有的成员变量不能通过对象直接访问,必须借助public属性的成员函数来修改

类的封装

private:作用在于更好的隐藏内部的实现,不希望外部知道或只在内部使用的成员声明为private
public:向外部暴露的接口声明为public//如上面的setname()和setscore()
(实际项目开发中,成员变量都建议声明为private,只将允许通过对象对用的成员函数声明为public)
protected:在类外也不能通过对象访问,但在派生类内部可以访问

给成员变量赋值的函数通常以set开头;读取成员变量的值的函数通常以get开头

private 和 public的使用体现了类的封装性,即:尽量隐藏类的内部实现,只向用户提供有用的成员函数。

构造函数

一种特殊的成员函数,名称与类名完全相同,可用于对某些成员变量设置初始值。创建对象时系统会自动调用构造函数进行初始化工作

1
2
3
4
5
6
7
8
9
10
11

Line::Line( double len,int age, float score): length(len)//构造函数,也可以对多个成员变量进行赋值
{

}
//相当于
Line::Line( double len)
{
length = len;

}

初始化const成员变量

如:对下面m_len的初始化

1
2
3
4
5
6
7
class VLA{
private:
const int m_len;
int *m_arr;
public:
VLA(int len);
};

只能使用

1
2
3
4
5
6
7
8
VLA::VLA(int len):m_len(len){
m_arr=new int[len]
}
//而不能:
VLA::VLA(int len){
m_len = len;
m_arr = new int[len];
}

堆和栈的区别

  1. 内存分配
    堆由程序员分配和释放;栈由编译器自动分配
  2. 申请方式
    堆:用malloc、new申请;栈:如int a
  3. 大小限制
    堆:向高地址扩展,用不连续的内存空间存储。大小受到系统虚拟内存大小的限制,因此获得的空间比较大
    栈:向低地址扩展,一块连续的内存空间。栈的大小是固定的,能申请的空间比较小
  4. 效率
    堆:速度慢,容易产生内存碎片
    栈:系统分配,速度快

攻防世界逆向题

getit

首先放到ida里看伪代码
tSSEUs.png
其中
t存放的是“SharifCTF{???}”
u存放的是“*******************************************”
s存放的是“c61b68366edeb7bdce3c6820314b7498”
并且s的长度和t里的?一样;“SharifCTF{”长度为10
整段伪代码大概就是:
先改变t里每个?的值,然后输出到文件,然后再改变整个t,再输出一次,最后把这个文件删了
tSSAEj.png
在第一次输出时伪代码是:

1
fprintf(stream, "%s\n", u, v5);

只有一个%s,看起来只输出了u,并没有输出v5

在第二次输出时用到了fseek(),其作用是将文件的指针移动:
tSSwrD.png

由于u的内容一直都是"**",那么也就是说存放flag的t被最后一个循环覆盖为了一堆 *
综上,flag应该出现在第一次输出时的t里,得到flag的过程为程序的第一个循环,可以得到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include<string.h>
int main(){
char s[50]="c61b68366edeb7bdce3c6820314b7498";
int v=0;
char v3;
char t[50]="SharifCTF{????????????????????????????????}";
while((signed int )v<strlen(s)){
if(v&1)
v3=1;
else
v3=-1;
*(t+(signed int)v+10)=s[(signed int)v]+v3;
//从{后面开始修改数组t的内容
v=v+1;
}
puts(t);
}