最近有点难产了,现在只能炒炒冷饭先了。毕竟老是发感想类的也不太合适,容易被人诟病
因此我也吸取了教训,哪怕是炒个冷饭好歹也得炒个技术相关的文章。很早以前就在公众号发过《web选手如何快速卷入二进制世界》这篇文章,似乎对不少web选手帮助还挺大。说实话我自己到现在也是不懂二进制的,只不过就是把我自己认为的东西给写出来了而已。在那之后其实我在星球很早就发过了其后续的两篇文章只是一直没有发公众号,现在我把那两篇也整合一下发一个下篇出来吧。
创建
创建方式挺多的,可以理解为用第二个参数创建一个string然后赋值给第一个参数(第一个参数是指针,写着int类型的都是指针),参数个数不同可以认为是构造方法不同,可以搜一下string的各种构造方法来对着看。
v25是一个空的char指针,通过basic_string的构造方法构造了一个string,值为"ImportCert",v25指向这个新生成的string对象
删除
可以理解为删除string对象v3,不仅仅是string,任何对象中带有~符号的都可以这样理解。
转化成c类型的字符串
这种结构都差不多,v2是一个string对象,理解为v3 = v2.c_str()
c类型的字符串是以\0作为结尾的,string并不是。 compare
很简单,翻译成 v6.compare("LOGOUT")
比较两个是否相等
split
这段翻译成:
vector<string> v12; //定义一个vector<string>变量
v12 = v9.split(" "); //空格只是一个临时string所以不用管他,v9应该也是一个string不过是用了程序里包装过的split
append
懒得翻译了
assign
assign函数是将原字符串清空然后用新的字符串赋值。上面的翻译为:
a3.assign(a1+1152);//用a1+1152这个指针指向的字符串来赋值a3
xxx::operator,这里搜一下函数看到很多,不仅仅有operator[],还有各种比如operator=,operator!=,聪明的你应该知道了,这就是运算符。比如operator++
operator=
可以看到就是a1=a2,没有太多可以解释了。
翻译成:
v8=v12[0];
(a1+704)=v8;
v7=v12[1];
(a1+728)=v7;
//其中a1是一个指针
这里简单看一下:
webserver::init(this){
.....
webserver::initmsghandlermap(this);
if((agent::initalize(this+240)) == -1)
.....
}
第一个this是webserver的this,这个this再后面用于initmsghandlermap的初始化。接下来将this+240这个指针(就是this指针偏移240个字节)传递给agent作为初始化用。也就是说webserver的对象指针偏移240个字节的位置保存了一个指向agent对象的指针。如果判断agent初始化返回值是否等于-1,如果等于说明初始化失败。下面是汇编,对着看可以看的很清楚。
其中0F0h是16进制的240.
vector
这里展示的是向一个vector向量里添加字符串元素,图里的vector对象是this+1552(同样的理解,当前对象的this偏移1552字节的位置的指针)这个指针指向的,创建了一个string元素,然后通过push_back添加到vector中,再将这个string对象删除。(不用太在意string临时变量创建和删除,只需要理解为这是往vector里加元素就行)
map
这个分为几块来看:
std::map是创建了一个map对象,可以理解为c++里的字典
xxxx::operator[]可以看成使用运算符[],这里第一个就可以看成(this+176)['initservercert'] = webserver::initservercert(),也就是一个字典key对应一个函数;
std::map<std::string,int (*)(Params const&,std::string&)>,这里的模板第一个参数是string,也就是字典的key,第二个参数int (*)(Params const&,std::string&)
描述了一个函数指针,这个函数的参数有两个,第一个是Params对象(不用管他,这是例子里定义的一个参数对象),第二个是一个string。这个函数指针也就是对应到具体的字典值
ida里伪代码有时候会以数组方式对结构体进行寻址,这时候每个下标都是8字节,而结构体里每个成员的大小并不都是8个字节,因此下标会和结构体的成员对不上。这时候我们需要计算一下才知道下标对应到的是哪个成员。32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int:4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int:4个字节
unsigned int : 4个字节
int64: 8个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
这里还要加上apache里的定义
还有一些其他的类型常见的比如apr_off_t是8字节
下面我们以64位的request_rec结构体为例来试着推导出伪代码里的下标代表的具体的成员
这里的a1是request_rec的对象,29为伪代码的下标,我们可以看一下汇编
0e8h转成10进制是232,232除以8等于29,即如前面所说ida以每8字节来翻译位移得到下标。我们看一下源码定义
可以看到大部分都是指针,所有指针都是8字节,所以不用管指针
这里有apr_int64和普通的int,普通int占4字节,apr_int64其实就是long也就是8字节,以此类推。我们最后可以计算出232字节偏移处是
这个和预期语义一致。后续各位遇到类似的东西只需要按照这种逻辑对其进行计算,就可以大致明白这个偏移量指向的内存是存储的什么内容了。
到这里,加上上一篇的内容基本上就是我对二进制的所有认知了。我就是靠这么一点点简单的基本知识加上长期的机械劳动来挖漏洞的,包括443也是如此,所以我觉得大家web选手不用太焦虑,不一定要掌握多么多么深入的知识才能挖到漏洞,很多时候你离二进制的距离真的就是打开IDA,按一下F5就完事了。