lyyyuna 的小花园

动静中之动, by

RSS

shellcode学习-绕过条件判断

发表于 2015-08

shellcode学习第一个例子。

以下有一段c语言编写的命令行程序,检验用户输入的数字,并判断是否合法。这里用户的输入被放在了函数的缓冲区里,但程序没有对缓冲区长度做检查,留下了漏洞。这里可以利用该漏洞绕过数字检察,使得任意输入都会被判定为正确。 在 validate_serial 中,do_valid_stuff 的地址溢出到函数的返回值上,就可实现。

源程序

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

int valid_serial(char * psz)
{
    size_t len = strlen(psz);
    unsigned total = 0;
    size_t i;

    if (len<10)
        return 0;

    for (i = 0; i < len; i++)
    {
        if ((psz[i]<'0') || (psz[i]>'z'))
            return 0;
        total += psz[i];
    }

    if (total % 853 == 83)
        return 1;

    return 0;    
}

int valildate_serial()
{
    char serial[24];

    fscanf(stdin, "%s", serial);

    if (valid_serial(serial))
        return 1;
    else
        return 0;
}

int do_valid_stuff()
{
    printf("the serial number is valid!\n");
    exit(0);
}

int do_invalid_stuff()
{
    printf("invalid serial number!\nexiting\n");
    exit(1);
}

int main(int argc, char * argv[])
{
    if (valildate_serial())
        do_valid_stuff();
    else
        do_invalid_stuff();

    return 0;
}

反汇编main

(gdb) disass main
Dump of assembler code for function main:
   0x0804861a <+0>:     push   %ebp
   0x0804861b <+1>:     mov    %esp,%ebp
   0x0804861d <+3>:     call   0x804859f <valildate_serial>
   0x08048622 <+8>:     test   %eax,%eax
   0x08048624 <+10>:    je     0x804862d <main+19>
   0x08048626 <+12>:    call   0x80485de <do_valid_stuff>
   0x0804862b <+17>:    jmp    0x8048632 <main+24>
   0x0804862d <+19>:    call   0x80485fc <do_invalid_stuff>
   0x08048632 <+24>:    mov    $0x0,%eax
   0x08048637 <+29>:    pop    %ebp
   0x08048638 <+30>:    ret
End of assembler dump.

可得到 do_valid_stuff 的地址为 0x08048626。validate_serial 的返回地址为 0x08048622。下面就通过溢出修改返回地址。

缓冲区溢出

源码中,缓冲区长度为24,理论上只要覆盖24+2处的数据就可以了。我们需要检验一下,在fscanf处打断点,观察堆栈内容。

Breakpoint 1, valildate_serial () at serial.c:31
31          fscanf(stdin, "%s", serial);
(gdb) x/20x $esp
0xbffff6bc:     0x0804869b      0x00000001      0xbffff794      0xbffff79c
0xbffff6cc:     0xbffff6e8      0xb7e987f5      0xb7ff0590      0x0804865b
0xbffff6dc:     0xb7fc7ff4      0xbffff6e8      0x08048622      0xbffff768
0xbffff6ec:     0xb7e7fe46      0x00000001      0xbffff794      0xbffff79c
0xbffff6fc:     0xb7fe0860      0xb7ff6821      0xffffffff      0xb7ffeff4
(gdb)c
AAAAAAAAAABBBBBBBBBBCCCCCCCC1234

Breakpoint 2, valildate_serial () at serial.c:33
33          if (valid_serial(serial))
(gdb) x/20x $esp
0xbffff6bc:     0xb7fc8440      0x080486d0      0xbffff6c8      0x41414141
0xbffff6cc:     0x41414141      0x42424141      0x42424242      0x42424242
0xbffff6dc:     0x43434343      0x43434343      0x34333231      0xbffff700
0xbffff6ec:     0xb7e7fe46      0x00000001      0xbffff794      0xbffff79c
0xbffff6fc:     0xb7fe0860      0xb7ff6821      0xffffffff      0xb7ffeff4
(gdb)

可以看到“1234”对应的ascii码“0x34333231”已经被写入了返回值"0x08048622"原来所在的地方。 接下来把“1234”换成我们需要的返回地址。

我们回到shell中实验一下

lyyyuna@yan:~/Desktop/shellcode/validate_serial$ printf "AAAAAAAAAABBBBBBBBBBCCCCCCCC\x26\x86\x04\x08" | ./serial
the serial number is valid!

成功绕过了程序的检验机制。