Stack Smashing
当canary被覆盖后,会call到__stack_chk_fail
打印argv[0]这个指针指向的字符串,默认是程序的名字,如果我们把它覆盖为其他的地址时,它就会把其他内存地址的信息给打印出来
Example:wdb2018_guess
Analyze
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
This is GUESS FLAG CHALLENGE!
Please type your guessing flag
123
You should take more effort to get six sence, and one more challenge!!
Please type your guessing flag
以上是程序开启的一些保护和大概的流程
main
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch] BYREF
__int64 v6; // [rsp+20h] [rbp-80h]
__int64 v7; // [rsp+28h] [rbp-78h]
char buf[48]; // [rsp+30h] [rbp-70h] BYREF
char s2[56]; // [rsp+60h] [rbp-40h] BYREF
unsigned __int64 v10; // [rsp+98h] [rbp-8h]
v10 = __readfsqword(0x28u);
v7 = 3LL;
LODWORD(stat_loc.__uptr) = 0;
v6 = 0LL;
sub_4009A6(a1, a2, a3);
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), buf, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr));
puts("This is GUESS FLAG CHALLENGE!");
while ( 1 )
{
if ( v6 >= v7 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
if ( !(unsigned int)sub_400A11() )
break;
++v6;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets(s2);
if ( !strcmp(buf, s2) )
puts("You must have great six sense!!!! :-o ");
else
puts("You should take more effort to get six sence, and one more challenge!!");
return 0LL;
}
可以看到通过gets
来接收变量s2,也就是用户输入,然后与buf(即flag)进行compare,如果相同则输出""You must have great six sense!!! 😮 "
否则让用户继续进行输入
虽然是危险函数gets
,但是由于本题开启了canary保护,所以没法进行stackoverflow或者搭配ROP等攻击
这里的话我们利用stack smashing来泄露程序的内存信息
Principle
前言已经提到过,当canary被覆盖后,会调用__stack_chk_fail
这个函数,打印argv[0]
这个指针指向的字符串,默认为程序的名字,stacksmashing的思想就是通过覆盖argv[0]
这个指针指向的地址,让程序在调用__stack_chk_fail
的时候,输出我们想要的内容,比如可以通过覆盖argv[0]
为puts的got表地址,从而让程序打印出puts函数的真实地址,来泄露libc
Structure attack script
首先我们需要动调找到argv[0]
这个指针距离用户输入的偏移,__libc_argv[0]
里面存储的是程序的名字,找到指向__libc_argv[0]
的指针即可,计算这个指针和指向用户输入的内容的指针的偏移
这里的话,16.04环境下偏移是0x128
,然后就可以往这个地址去覆盖想要泄露的函数地址了,比如这里我们泄露puts函数地址
payload1 = b'a' * 0x128 + p64(puts_got)
输出的地方如图所示,接收了puts地址后就可以拿到libc基址了
接着就涉及到如何通过libc地址来得到栈地址这个知识点了
Obtain the stack address through libc address
在libc中保存了一个函数叫_environ
,存的是当前进程的环境变量,我们可以通过_environ
函数来得到环境变量地址,而环境变量是存在栈上的,然后我们通过偏移就可以计算得到其他函数的地址了
首先得到_environ
函数地址
environ_addr = libc_base + libc.sym['__environ']
然后用_environ
函数地址覆盖__libc_argv[0]
指针地址,让程序通过__stack_chk_fail
报错输出_environ
环境变量地址
payload2 = b'a' * 0x128 + p64(environ_addr)
这样就拿到环境变量地址了,下一步就是去找存flag的变量地址和环境变量地址的偏移
gdb下可以通过x/a _environ
来查看环境变量地址,通过search flag
来查找flag变量地址,相减得到偏移
16.04下偏移为0x168
最后一步将__libc_argv[0]
覆盖成flag的地址即可,通过__stack_chk_fail
来输出flag值