攻防世界高手进阶区——stack2
攻防世界高手进阶区——stack2
看题目啥都没有
一,分析文件
-
发现居然存在栈溢出保护,这是以前做题没看到过的,可能会有新的知识点。
-
貌似是一个输入数字计算平均值的程序。
ida打开
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65unsigned int m; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:"); // 有几个数字
scanf("%d", &v5);
puts("Give me your numbers"); // 输入一个数字
for ( i = 0; i < v5 && (int)i <= 99; ++i )
{
scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");// 选择
scanf("%d", &v6); // 输入选择
if ( v6 != 2 )
break;
puts("Give me your number"); // 加数字
scanf("%d", &v7);
if ( j <= 99 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:"); // 修改数字,存在漏洞!!!没有检查索引值的值,没有检查数组边界,可以输入返回地址的相对于数组首地址的偏移
scanf("%d", &v5);
puts("new number:"); // 返回地址修改为system函数地址,和bash地址
scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( m = 0; m < j; ++m )
v9 += v13[m];
}
return 0;
}这里分析了一下文件,刚开始啥都没看出来,后来是看了网上的其他大佬的wp才知道这个漏洞(未检查数组边界,造成任意地址修改,这就是绕过栈溢出保护的方法吗?)
二,解题思路
解题的关键就是这里,这里未检查数组的索引值,可以让我们修改任意地址的内容。我们可以直接修改函数返回地址的内容。
现在就只需要找到相对于该数组函数返回地址的偏移就可以了。
这里我觉得是最难的一步,看了好几个wp才明白了这是怎么回事。
这里按照以往的惯例去ida直接找返回地址相对于数组的偏移算出来是有问题的。
这里面我算出来的偏移量为0x74,但是根据其他大佬通过动态调试算出来的偏移,这里的偏移应该是0x84。
这是怎么回事呢?
0x84要经过自己去动态调试
过程如下:
- 分析汇编代码
这段汇编代码其实就是对应下面这段C语言代码
这里也可以进一步看出来汇编的复杂。
分析一下汇编代码,这里面通过scanf将获取的值放入栈区,再将该数放入eax寄存器中,再将eax的值放入ecx寄存器,再取一个栈区的地址给edx,将一个值给eax(分析上面的汇编可以知道,这里放入eax的值就是数组的索引值),再将地址加给eax寄存器,最后将cl(ecx寄存器的低八位)的值赋给eax指向的地址。
通过这里的分析可以知道这时候eax里面存的地址便是数组起始地址。
通过这里的main endp可以知道这是main函数的结束地方,就可以得出,这里的retn时esp指向的地址便是返回地址。
在main函数和输入数组和返回retn指令的地方下断点。
输入全填一
run一下,指令执行前这里是0xd0,
指令执行后,这里是0x1,所以这里的oxffffd378就是数组起始地址,这里的0xf7ffd901什么都不代表,只是刚好数组起始地址里面存的是这个地址罢了。我就是在这里疑惑了半天,都是因为gdb不太熟练呀。菜鸡一枚。
运行到ret,esp里面存的就是返回地址。
所以偏移量是0xffffd3fc - oxffffd378 = 0x84
至于这里面为什么不是ida得到的数,是因为最后leave 和lea指令调整了栈帧,将栈底恢复到了之前的位置。栈底就变化了,偏移量自然就变化了
偏移量在这里就能得到了。
- 而且程序中存在后门函数hackhere
通过后门函数可以直接获得shell。
三,exp
1 | from pwn import * |
拿着这个exp去本地运行,发现能够运行成功,但是运行在服务器就出现了问题。
报错,发现服务器没有bash终端,只有sh。(后来才知道原来这里本来是出题人的失误,发现也能做出来就没有改。)
- 于是我们得改变思路,发现程序有system函数,也可以找到sh字符串。
- 就可以通过sh和system来构造exp
1 | from pwn import * |
这样就可以通过服务器了。
四,收获
- 学会了一个新的漏洞(绕过栈溢出保护)。
- 对python的编程了解了更多。
- 学会了gdb的使用。
- 学会了基础汇编语言。
总的来说,这次做题收获还是很大的。虽然难了我好几天。但是收获慢慢就不亏。哈哈哈