前言

今日学习计划原本是打算搭建shiro Remeberme 的漏洞来复现然后分析其代码的。

刚开始费劲千辛万苦终于找到了源码,接着开始安装环境,配置maven。

直到我打开了idea,导入项目,问题就出来了。

首先不熟悉软件,开始百度怎么导入,一步一步来,发现idea版本太高了,和网上的文章很多操作都不符合,但是按照第七感,一路next,开始导入maven包了,等了几分钟,看着好像导入结束了,然后一点开代码,一堆红色。

后面的时间,都在漫长的百度和尝试的路上,可惜最后都失败了。

被折磨了半天,最后身心俱疲,打开了0day安全 软件漏洞分析技术第二版看书转移注意力,忘却java带给我的痛苦。


栈溢出

首先栈指的是一种数据结构,是一种先进后出的数据表。栈的常见操作有两种:压栈(push)、弹栈(pop);用于标识栈的属性也有两个:栈顶(top)、栈底(base)

首先了解一点:大多数情况下,局部变量在栈中的分布是相邻的,但也有可能出于编译优化等需要而有所例外。具体情况需要在动态调试中具体对待。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#define PASSWORD "1234567"

int verify_password (char *password)
{
int authenticated;
char buffer[8];// add local buff
//strcmp比较两个字符串并根据比较结果返回整数
//strcmp(str1,str2),若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数
authenticated=strcmp(password,PASSWORD);
//strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*。
strcpy(buffer,password);//over flowed here!
return authenticated;
}


main()
{
int valid_flag=0;
char password[1024];
while(1)
{
printf("please input password: ");

scanf("%s",password);

valid_flag = verify_password(password);

if(valid_flag)
{
printf("incorrect password!\n\n");
}
else
{
printf("Congratulation! You have passed the verification!\n");
break;
}
}
}

如果从代码逻辑来看,通过输入一个值传入password,进入verify_password进行处理,函数对输入的值与常量进行比较,返回的值,如果等于0则验证成功,反之验证失败。

分析代码

没有od,只能用x32dbg进行分析。首先进入到main函数,从键盘输入一个值

然后跳转到verify_password函数

F7跟进函数,然后f8走一遍

一路F8走出函数后,观察寄存器中EAX的值

EAX=FFFFFFFF

根据strcmp的特性,当str1<str2则返回负数,也就是-1,这时内存中的值按照双字-1的补码存放,也就为0xFFFFFFFF,所以EAX得值就等于FFFFFFFF

下一个函数就是strcpy复制函数,但是现在复制也突破不了密码验证,F8跟回去main函数内

这里,将eax的值赋值给[ebp-4],此时eax=0xFFFFFF,接着,让[ebp-4]0进行比较,如果等于就进行跳转,否则就不跳转。所以现在就没跳转,输出了incorrect password!


密码验证突破

一开始被strcmp这个函数卡住,一直在观察到底怎么执行,后面跟大佬一波有力沟通以后,才知道自己关注点错了。

他通过内存来解释,清晰易懂

首先 内存地址 007AF3DCauthenticated申请的空间 4字节

007AF3CCbuffer申请的空间 8字节 理论上只能设7个 因为要\00 收尾截断

由内存地址可知,buffer的内存地址是在authenticated这个变量上面,他只有8字节,现在程序逻辑就是,输入的值要与常量PASSWORD的值一样,然后经过strcmp比较,两个值相同返回0,就能输出Congratulation! You have passed the verification!

所以此时,只需要输入字符串为8字节,并且大于常量1234567就能绕过。

为什么要大于常量的1234657

将程序放入x32dbg走一趟,观察内存地址就清楚了。

首先输入12345678,此时经过strcmp函数比较,也是大于常量,所以会返回1

首先将程序改了改,此时将bufferauthenticated的内存地址打印出来,方便观察内存变化。

程序首先断在了strcmp函数处一路F8走到函数底部,可以看到EAX的值等于1,意思就是经过比较,输入的值大于常量,所以返回1.

内存地址中现在就是01.

接着现在进入strcpy函数

F8走一圈后,当跳到底部时候。观察buffer的内存地址,现在这里还是01

当在走一步,就发生变化了,由于buffer只有8个字节,复制了九个字节的值过来,所以最后一个直接将会被\00转为NULL,所以变为0,因此authenticated的值也从1转为0.此时密码成功突破验证。


可能输入12345678看不太明显。所以我输入了比较长的一个值12345678qaz。按照上面的分析,这个时候如果复制过去buffer内,8就会被转为NULL,就剩下qaz在后面

确实如此。看此时edi

和转储空间中内存地址的变化


为什么不能传小于常量的值

首先strcmp函数比较,按照分析中发现,函数是逐字节进行比较,比如我输入一个8字节的字符串01234567

观察寄存器中EAX的值,此时是FFFFFFFF,其实就是-1,按照双字-1的补码存放,为0xFFFFFFFF

那么此时,即使将其复制到BUFFER中,字节溢出,将最后的两个FF转为00那返回去的值也是FFFFFF00

这样子就不能突破,将会继续循环,输入新的值


分析完了,也终于写完了,诶二进制还是好玩的。

继续去研究web,shiro的漏洞环境一定要搭起来。