LINUX内核调试相关--oops信息的定位

一个简单的linux crackme的逆向 

前言 

    最不喜欢的就是写破解教程,酒后一时冲动,老夫卿发少年狂,许下将写一篇linux平台逆向的文章的诺言,作此文实非颇不得已。
    在此申明:本文在技术上非常初级,并没有用到什么高深的技术,本人水平亦有限,如有差错,还请见谅! 
    
开始之前的准备 

    正如C语言教程从 hello world 开始,我们也由一个 crackme 说开去。本文的例子程序你可以到这来下载:
http://www.crackmes.de/users/veneta/crackmes/linux_crackme_v2 。古人云“工欲善其事,必先利其器”,本文中所用到的工具及操作平台罗列如下:
  操作平台: gentoo 2004.3 #  kernel 2.6.9
  逆向工具: 
    反汇编 -- objdump (这个工具基本上每个LINUX上都有)、lida( http://lida.sourceforge.net/ )
    调试器 -- gdb
    十六进制编辑器 -- hexedit
    文本编辑器 -- vim
    压缩工具 -- upx  (http://upx.sourceforge.net)
    计算器 -- gcalctool(gnome计算器)
    
开始逆向之旅 

首先我们看看程序基本信息: 
    打开控制台,切换到程序所在目录。运行“ objdump -x cm2 ”,显示如下:

代码:
    

[ncc2008@localhost]$ objdump -x cm2



cm2:     file format elf32-i386

cm2

architecture: i386, flags 0x00000102:

EXEC_P, D_PAGED

start address 0x08048080



程序头:

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

         filesz 0x000005b8 memsz 0x000005b8 flags r-x

    LOAD off    0x000005b8 vaddr 0x080495b8 paddr 0x080495b8 align 2**12

         filesz 0x0000002c memsz 0x0000002c flags rw-



Sections:

Idx Name          Size      VMA       LMA       File off  Algn

SYMBOL TABLE:

no symbols

  
    我们可以看到start address是0x08048080,但有一个问题是Sections下面却什么都没有。这不是一个正常的程序?
    
    接下来,使用十六进制工具 hexedit 查看程序信息。运行命令:hexedit cm2 ,显示如下:

代码:
00000000   7F 45 4C 46  01 01 01 00  4C 69 6E 75  78 00 00 00  .ELF....Linux...



00000010   02 00 03 00  01 00 00 00  80 80 04 08  34 00 00 00  ............4...



00000020   00 00 00 00  00 00 00 00  34 00 20 00  02 00 00 00  ........4. .....



00000030   00 00 00 00  01 00 00 00  00 00 00 00  00 80 04 08  ................



00000040   00 80 04 08  B8 05 00 00  B8 05 00 00  05 00 00 00  ................



00000050   00 10 00 00  01 00 00 00  B8 05 00 00  B8 95 04 08  ................



00000060   B8 95 04 08  2C 00 00 00  2C 00 00 00  06 00 00 00  ....,...,.......



00000070   00 10 00 00  77 6C EA 93  7F 55 50 58  E4 05 0B 0A  ....wl...UPX....



00000080   31 ED 58 89  E1 8D 54 81  04 50 83 E4  F8 52 51 E8  1.X...T..P...RQ.



00000090   FE 01 00 00  F4 0A 00 24  49 6E 66 6F  3A 20 54 68  .......$Info: Th



000000A0   69 73 20 66  69 6C 65 20  69 73 20 70  61 63 6B 65  is file is packe



000000B0   64 20 77 69  74 68 20 74  68 65 20 55  50 58 20 65  d with the UPX e



000000C0   78 65 63 75  74 61 62 6C  65 20 70 61  63 6B 65 72  xecutable packer



000000D0   20 68 74 74  70 3A 2F 2F  75 70 78 2E  73 66 2E 6E   http://upx.sf.n



000000E0   65 74 20 24  0A 00 24 49  64 3A 20 55  50 58 20 31  et $..$Id: UPX 1



000000F0   2E 32 34 20  43 6F 70 79  72 69 67 68  74 20 28 43  .24 Copyright (C



00000100   29 20 31 39  39 36 2D 32  30 30 32 20  74 68 65 20  ) 1996-2002 the



00000110   55 50 58 20  54 65 61 6D  2E 20 41 6C  6C 20 52 69  UPX Team. All Ri



00000120   67 68 74 73  20 52 65 73  65 72 76 65  64 2E 20 24  ghts Reserved. 
$

    从上面的信息中可以看到程序被UPX压缩了,接下来请确信你系统中已有UPX,如果没有请到上面给出的程序链接中下载。
    运行命令“ upx -d cm2 ”把程序解压缩,显示如下:

代码:
[ncc2008@localhost crack]$ upx -d cm2

                     Ultimate Packer for eXecutables

   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004

UPX 1.25         Markus F.X.J. Oberhumer & Laszlo Molnar        Jun 29th 2004



        File size         Ratio      Format      Name

   --------------------   ------   -----------   -----------

     10584 <-      4870   46.01%    linux/386    cm2



Unpacked 1 file.

    接下来,我们再使用命令“ objdump -x cm2 ”来查看程序信息。如下:

代码:
[ncc2008@localhost crack]$ objdump -x cm2



cm2:     file format elf32-i386

cm2

architecture: i386, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x080488b0



程序头:

    PHDR off    0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2

         filesz 0x000000c0 memsz 0x000000c0 flags r-x

  INTERP off    0x000000f4 vaddr 0x080480f4 paddr 0x080480f4 align 2**0

         filesz 0x00000013 memsz 0x00000013 flags r--

    LOAD off    0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

         filesz 0x0000109c memsz 0x0000109c flags r-x

    LOAD off    0x00002000 vaddr 0x0804a000 paddr 0x0804a000 align 2**12

         filesz 0x00000398 memsz 0x00000ff0 flags rw-

 DYNAMIC off    0x00002248 vaddr 0x0804a248 paddr 0x0804a248 align 2**2

         filesz 0x000000e0 memsz 0x000000e0 flags rw-

    NOTE off    0x00000108 vaddr 0x08048108 paddr 0x08048108 align 2**2

         filesz 0x00000020 memsz 0x00000020 flags r--



动态节:

  NEEDED      libgtk-1.2.so.0

  NEEDED      libgdk-1.2.so.0

  NEEDED      libglib-1.2.so.0

  NEEDED      libc.so.6

  INIT        0x8048758

  FINI        0x8049074

  HASH        0x8048128

  STRTAB      0x804841c

  SYMTAB      0x80481fc

  STRSZ       0x225

  SYMENT      0x10

  DEBUG       0x0

  PLTGOT      0x804a33c

  PLTRELSZ    0x98

  PLTREL      0x11

  JMPREL      0x80486c0

  REL         0x80486b8

  RELSZ       0x8

  RELENT      0x8

  VERNEED     0x8048688

  VERNEEDNUM  0x1

  VERSYM      0x8048642



版本引用:

  required from libc.so.6:

    0x0d696911 0x00 03 GLIBC_2.1

    0x0d696910 0x00 02 GLIBC_2.0



Sections:

Idx Name          Size      VMA       LMA       File off  Algn

  0 .interp       00000013  080480f4  080480f4  000000f4  2**0

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  1 .note.ABI-tag 00000020  08048108  08048108  00000108  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  2 .hash         000000d4  08048128  08048128  00000128  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  3 .dynsym       00000220  080481fc  080481fc  000001fc  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  4 .dynstr       00000225  0804841c  0804841c  0000041c  2**0

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  5 .gnu.version  00000044  08048642  08048642  00000642  2**1

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  6 .gnu.version_r 00000030  08048688  08048688  00000688  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  7 .rel.dyn      00000008  080486b8  080486b8  000006b8  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  8 .rel.plt      00000098  080486c0  080486c0  000006c0  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  9 .init         00000017  08048758  08048758  00000758  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 10 .plt          00000140  08048770  08048770  00000770  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 11 .text         000007c4  080488b0  080488b0  000008b0  2**4

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 12 .fini         0000001b  08049074  08049074  00001074  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

 13 .rodata       00000008  08049090  08049090  00001090  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

 14 .eh_frame     00000004  08049098  08049098  00001098  2**2

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

 15 .data         00000248  0804a000  0804a000  00002000  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 16 .dynamic      000000e0  0804a248  0804a248  00002248  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 17 .ctors        00000008  0804a328  0804a328  00002328  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 18 .dtors        00000008  0804a330  0804a330  00002330  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 19 .jcr          00000004  0804a338  0804a338  00002338  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 20 .got          0000005c  0804a33c  0804a33c  0000233c  2**2

                  CONTENTS, ALLOC, LOAD, DATA

 21 .bss          00000c58  0804a398  0804a398  00002398  2**2

                  ALLOC

 22 .comment      00000119  00000000  00000000  00002398  2**0

                  CONTENTS, READONLY

SYMBOL TABLE:

no symbols

    从上面的信息中可以看出,程序使用gtk编写,程序入口start address=0x080488b0。
    我们再进一步的查看程序信息,运行命令“objdump -T cm2 >iat.txt”,下面的信息将会保存到iat.txt文件中。

代码:
[ncc2008@localhost crack]$ objdump -T cm2



cm2:     file format elf32-i386



DYNAMIC SYMBOL TABLE:

00000000      DF *UND*  0000013d              gtk_widget_show

0804a248 g    DO *ABS*  00000000  Base        _DYNAMIC

08048780      DF *UND*  000000e4              gtk_widget_show_all

00000000      DF *UND*  00000027              gtk_dialog_new

08048790      DF *UND*  000001db              gtk_container_add

08048758 g    DF .init  00000000  Base        _init

080487a0      DF *UND*  000000d1              gtk_window_set_default_size

080487b0      DF *UND*  00000056              gtk_label_new

080487c0      DF *UND*  00000085              gtk_window_new

080487d0      DF *UND*  00000213              gtk_widget_set_uposition

080487e0      DF *UND*  0000010d              gtk_window_set_title

080487f0      DF *UND*  0000010c              gtk_entry_get_text

08048800      DF *UND*  00000242              gtk_box_pack_start

08048810      DF *UND*  00000067              gtk_init

08048820      DF *UND*  0000010d              g_print

08048830      DF *UND*  0000007c              gtk_main_quit

08048840      DF *UND*  000001d3              gtk_main

0804a398 g    D  *ABS*  00000000  Base        __bss_start

08048850      DF *UND*  000000e0  GLIBC_2.0   __libc_start_main

08049074 g    DF .fini  00000000  Base        _fini

08048860      DF *UND*  000000c7  GLIBC_2.0   exit

0804a398 g    D  *ABS*  00000000  Base        _edata

0804a33c g    DO *ABS*  00000000  Base        _GLOBAL_OFFSET_TABLE_

0804aff0 g    D  *ABS*  00000000  Base        _end

00000000      DF *UND*  00000028  GLIBC_2.1   fopen

08049094 g    DO .rodata        00000004  Base        _IO_stdin_used

00000000      DF *UND*  0000012e  GLIBC_2.0   fwrite

08048870      DF *UND*  00000027              gtk_entry_new

00000000  w   D  *UND*  00000000              _Jv_RegisterClasses

08048880      DF *UND*  00000157              gtk_signal_connect

08048890      DF *UND*  0000006d              gtk_button_new_with_label

080488a0      DF *UND*  00000044              gtk_vbox_new

00000000  w   D  *UND*  00000000              __gmon_start__

    我们再一次运行命令“objdump -d cm2 >disasm.txt”,对程序进行反汇编,并把结果保存到disasm.txt中。
接下来,用VI打开disasm.txt文件。显示如下:
http://bbs.pediy.com/upload/2005/4/image/disasm.jpg 
    
相对WINDOW平台的反汇编器,objdump 的反汇编结果在没有调试符号的情况下,不显示API的名字。下一步API调用手工的加上。
我们打开iat.txt文件,把disasm.txt文件中的地址全部转换成函数名,例如:

代码:
 8048979:       68 00 00 00 00          push   $0x0

 804897e:       68 03 00 00 00          push   $0x3

 8048983:       e8 88 fe ff ff          call   0x8048810

转换成

http://bbs.pediy.com/upload/2005/4/image/vidisasm.jpg 
这样将好看多了。这一点你可以写个脚本自动完成,而不用手工一个一个的替换。或者你也可以使用更强大的反汇编工器lida来自动显示API调用名。为了方便初学者,下面使用LIDA来作为反汇编工具。用lida 载入程序,如下图:
http://bbs.pediy.com/upload/2005/4/image/lida.jpg 
    好了,前期的工作都做了。下面让我们来运行一下程序,看看有没有什么值得参考的。运行后,出现一个窗口。如下:
http://bbs.pediy.com/upload/2005/4/image/killme.jpg  
    如图,提示我们的CRACK任务之一就是NOP掉这个窗口。点击关闭按钮,出现第二个窗口,如下图
http://bbs.pediy.com/upload/2005/4/image/crackme.jpg 
    这是注册的主窗口。
    对照LIDA中的反汇编代码加上GTK的一些基本知识,我们可以知道程序运行过程是这样的:main->跳出killme窗口->注 册destroy事件处理函数->在关闭 killme 窗口时将引发destroy事件->运行回调处理函数0x80489bd-> 跳出crackme窗口。
    我们CRACK的任务就是nop掉killme窗口,以及找出注册码。
    注:GTK的相关文档你可以到这来获取 http://www.gtk.org/tutorial/


第一个任务:去除killme窗口。 
    用lida反汇编cm2,点击菜单 view -> Functions 找到main函数,点击进入,如下所示:

代码:
main():

08048970  55                                   push      ebp 

08048971  89 E5                                mov      ebp, esp 

08048973  81 EC 08 00 00 00                    sub      esp, 0x8 

08048979  68 00 00 00 00                       push      0x0 

0804897E  68 03 00 00 00                       push      0x3 

08048983  E8 88 FE FF FF                       call      gtk_init (08048810) ; (near - 0x178)

08048988  90                                   nop  -/

08048989  90                                   nop    /

0804898A  90                                   nop     |->怎么会有nop指令出现?

0804898B  90                                   nop    /   我们在这加入call Function___080489BD

0804898C  90                                   nop  -/    直接显示crackme窗口,跳过killme窗口的调用。因为在

0804898D  BE D2 A0 04 08                       mov      esi, " Kill this window (p" (0804A0D2)

08048992  E8 5C 04 00 00                       call      Function___08048DF3 (08048DF3) ; 建立killme窗口


08048997  68 00 00 00 00                       push      0x0 

0804899C  68 BD 89 04 08                       push      Function___080489BD (80489BD)  ; 回调函数,显示crackme窗口


080489A1  68 28 A2 04 08                       push      "destroy" (0804A228)

080489A6  FF 35 A0 A3 04 08                    push      0804A3A0 

080489AC  E8 CF FE FF FF                       call      gtk_signal_connect (08048880) ; 建立destroy事件回调函数0x080489BD


080489B1  81 C4 10 00 00 00                    add      esp, 0x10 

080489B7  E8 84 FE FF FF                       call      gtk_main (08048840) ; 显示killme窗口


080489BC  C3                                   ret

    我们跟入Function___08048DF3函数,如下:

代码:
Function___08048DF3:

08048DF3  55                                   push      ebp  ; xref ( 08048992 08048C9A 08048CA6 08048DAC )

08048DF4  89 E5                                mov      ebp, esp 

08048DF6  81 EC 08 00 00 00                    sub      esp, 0x8 

08048DFC  68 01 00 00 00                       push      0x1 

08048E01  E8 BA F9 FF FF                       call      gtk_window_new (080487C0) ; (near - 0x646)

08048E06  A3 A0 A3 04 08                       mov      0x0804A3A0, eax 

08048E0B  68 2C A0 04 08                       push      "CrackMe v13 (2 linux" (0804A02C)

08048E10  FF 35 A0 A3 04 08                    push      0804A3A0 

08048E16  E8 C5 F9 FF FF                       call      gtk_window_set_title (080487E0) ; (near - 0x63B)

08048E1B  56                                   push      esi 

08048E1C  E8 8F F9 FF FF                       call      gtk_label_new (080487B0) ; (near - 0x671)

08048E21  50                                   push      eax 

08048E22  FF 35 A0 A3 04 08                    push      0804A3A0 

08048E28  E8 63 F9 FF FF                       call      gtk_container_add (08048790) ; (near - 0x69D)

08048E2D  68 64 00 00 00                       push      0x64 

08048E32  68 2C 01 00 00                       push      0x12C 

08048E37  FF 35 A0 A3 04 08                    push      0804A3A0 

08048E3D  E8 5E F9 FF FF                       call      gtk_window_set_default_size (080487A0) ; (near - 0x6A2)

08048E42  FF 35 A0 A3 04 08                    push      0804A3A0 

08048E48  E8 33 F9 FF FF                       call      gtk_widget_show_all (08048780) ; (near - 0x6CD)

08048E4D  C9                                   leave   

08048E4E  C3                                   ret   

    上面这个函数建立killme窗口。
    为了去除这个killme窗口。我们可以在

代码:
08048988  90                                   nop  -/

08048989  90                                   nop    /

0804898A  90                                   nop     |->怎么会有nop指令出现?

0804898B  90                                   nop    /   我们在这加入call Function___080489BD

0804898C  90                                   nop  -/    直接显示crackme窗口,跳过killme窗口的调用。

0804898D  BE D2 A0 04 08                       mov      esi, " Kill this window (p" (0804A0D2)

08048992  E8 5C 04 00 00                       call      Function___08048DF3 (08048DF3)

    上面的nop正好5个,我怀疑作者写crackme在这有一个对crackme函数的调用,生成程序后,他手工把这个代码NOP掉了,他是故意留出空间来做提示。因为在crackme 窗口函数中有完整的窗口初始化和退出处理,如下:

代码:
08048C22  68 3D A2 04 08                       push      "clicked" (0804A23D)

08048C27  FF 35 AC A3 04 08                    push      0804A3AC 

08048C2D  E8 4E FC FF FF                       call      gtk_signal_connect (08048880) ; (near - 0x3B2)

08048C32  81 C4 10 00 00 00                    add      esp, 0x10 

08048C38  E8 03 FC FF FF                       call      gtk_main (08048840) ; (near - 0x3FD)

08048C3D  68 2C A0 04 08                       push      "CrackMe v13 (2 linux" (0804A02C)

08048C42  E8 D9 FB FF FF                       call      g_print (08048820) ; (near - 0x427)

08048C47  81 C4 04 00 00 00                    add      esp, 0x4 

08048C4D  68 00 00 00 00                       push      0x0 

08048C52  E8 09 FC FF FF                       call      exit (08048860) ; 当点击crackme窗口上的quit按钮后程序直接退出




Function___08048C57:

08048C57  55                                   push      ebp 

08048C58  89 E5                                mov      ebp, esp 

08048C5A  81 EC 08 00 00 00                    sub      esp, 0x8 

08048C60  E8 17 00 00 00                       call      Function___08048C7C (08048C7C) ; (near + 0x17)

08048C65  B8 00 00 00 00                       mov      eax, 0x0 

08048C6A  C9                                   leave   

08048C6B  C3                                   ret  

    从上面的代码可以看到,如果先调用crackme 窗口的函数,那么 killme 窗口就不会出现了。
    好的,下面我们就改08048988处的指令为 call    080489BD  ,用计算器算一下:80489BD-08048988-5=00000030,得到代码为 e8 30 00 00 00 。嘿嘿!
    好的,用hexedit 打开 cm2 程序。看看前面objdump -x cm2列出的基地址为0x8048000,所以对应的 file offset应该是0x8048988-0x8048000=0x988,我们按 return 键,打开 go to 窗口,输 入 0x988 ,定位到08048988处,改代码为e830000000。改完后,“ ./cm2 ”运行程序,OK。出现 crackme 窗口 了。

第二个任务,找出正确的注册码。 
为保持本文一个苗条的身材及加大本文所包含的信息量,我就不讲怎么根据按钮名找对应的点击事件处理函数了,讲一种简单省事的方法,找注册码最省事的方法当然是动态跟踪啦。搞不好还可以看到明码哟。费话不多说,运行命令: gdb cm2  。提示没有符号文件,不用管它。
下断点:

代码:
br gtk_entry_get_text 注(类似于WIN平台下的 getdlgitemtexta )

再键入命令 “ run ”运行程序。出现了界面。
输入名字和注册码,我这用的是:
名字:ncc2008
注册码:78787878
按crackme按钮,中断到gdb中。

代码:
Breakpoint 1, 0x40096eb0 in gtk_entry_get_text () from /usr/lib/libgtk-1.2.so.0

让我们看看是哪个CALL调用的,下命令"info frame",显示如下:

代码:
(gdb) info frame

Stack level 0, frame at 0xbfffe250:

 eip = 0x40096eb0 in gtk_entry_get_text; saved eip 0x8048cc1  ;从这CALL来的

 called by frame at 0xbfffe254

 Arglist at 0xbfffe248, args:

 Locals at 0xbfffe248, Previous frame's sp is 0xbfffe250

 Saved registers:

  eip at 0xbfffe24c

呵呵,清除全部断点,下命令“d”。在0x8048cc1下一个断点:

代码:
br *0x8048cc1

,然后键“C”继续运行程序。
程序中断到我们所下的断点0x8048cc1处。让我们看看汇编代码:
下命令:x /10i $eip
显示如下:

代码:
(gdb) x /10i $eip

0x8048cc1:      mov    %eax,%edx

0x8048cc3:      call   0x8048fc5

0x8048cc8:      cmp    $0x20,%eax

0x8048ccd:      jne    0x8048ca1

0x8048ccf:      mov    %edx,0x804a3e8

0x8048cd5:      call   0x8048f50

0x8048cda:      pushl  0x804a3c0

0x8048ce0:      call   0x80487f0

0x8048ce5:      mov    %eax,%edx

0x8048ce7:      call   0x8048fc5

看不懂?我们再看lida 中的 0x8048cc1处的代码。

代码:
Function___08048CAD:

08048CAD  55                                   push      ebp 

08048CAE  89 E5                                mov      ebp, esp 

08048CB0  81 EC 08 00 00 00                    sub      esp, 0x8 

08048CB6  FF 35 C4 A3 04 08                    push      0804A3C4 

08048CBC  E8 2F FB FF FF                       call      gtk_entry_get_text (080487F0) ; (near - 0x4D1)

08048CC1  89 C2                                mov      edx, eax 

08048CC3  E8 FD 02 00 00                       call      Function___08048FC5 (08048FC5) ; 注册码长

08048CC8  3D 20 00 00 00                       cmp      eax, 0x20 

08048CCD  75 D2                                jnz      Label_08048CA1 (08048CA1) ; 注册码必需是32个字符

08048CCF  89 15 E8 A3 04 08                    mov      0804A3E8, edx 

08048CD5  E8 76 02 00 00                       call      Function___08048F50 (08048F50) ; a2i,字符串到 hex转换

08048CDA  FF 35 C0 A3 04 08                    push      0804A3C0 

08048CE0  E8 0B FB FF FF                       call      gtk_entry_get_text (080487F0) ; (near - 0x4F5)

08048CE5  89 C2                                mov      edx, eax 

08048CE7  E8 D9 02 00 00                       call      Function___08048FC5 (08048FC5) ; (near + 0x2D9)

08048CEC  BE F0 A7 04 08                       mov      esi, 0x804A7F0 

08048CF1  50                                   push      eax 

08048CF2  3D 03 00 00 00                       cmp      eax, 0x3 

08048CF7  72 A8                                jc      Label_08048CA1 (08048CA1) ; 用户名必需大于3个字符

08048CF9  89 C1                                mov      ecx, eax 

08048CFB  8A 02                                mov      al, [edx]  ; xref ( 08048D01 )

08048CFD  88 06                                mov      [esi], al 

08048CFF  42                                   inc      edx 

08048D00  46                                   inc      esi 

08048D01  E2 F8                                loop      08048CFB ; (near  0xF8)

08048D03  C7 05 C8 A3 04 08 10 A2 04 08        mov      0804A3C8, "MBECrew" (0804A210)

08048D0D  C7 05 CC A3 04 08 07 00 00 00        mov      0804A3CC, 0x7 

08048D17  E8 33 01 00 00                       call      Function___08048E4F (08048E4F) ; (near + 0x133)

08048D1C  C7 05 D0 A3 04 08 F0 A3 04 08        mov      0804A3D0, 0x804A3F0 

08048D26  C7 05 D4 A3 04 08 10 00 00 00        mov      0804A3D4, 0x10 

08048D30  E8 87 01 00 00                       call      Function___08048EBC (08048EBC) ; 注册码每字节与F0相异或

08048D35  C7 05 C8 A3 04 08 10 A2 04 08        mov      0804A3C8, "MBECrew" (0804A210)

08048D3F  C7 05 CC A3 04 08 07 00 00 00        mov      0804A3CC, 0x7 

08048D49  E8 01 01 00 00                       call      Function___08048E4F (08048E4F) ; (near + 0x101)

08048D4E  58                                   pop      eax 

08048D4F  C7 05 D0 A3 04 08 F0 A7 04 08        mov      0804A3D0, 0x804A7F0 

08048D59  A3 D4 A3 04 08                       mov      0x0804A3D4, eax 

08048D5E  E8 59 01 00 00                       call      Function___08048EBC (08048EBC) ; (near + 0x159)

08048D63  BE F0 A3 04 08                       mov      esi, 0x804A3F0 

08048D68  BA F0 A7 04 08                       mov      edx, 0x804A7F0 

08048D6D  B9 10 00 00 00                       mov      ecx, 0x10 



Label_08048D72:

08048D72  8A 02                                mov      al, [edx]  ; xref ( 08048D7C 08048DB8 )

08048D74  3C 00                                cmp      al, 0x0 

08048D76  74 3B                                jz      Label_08048DB3 (08048DB3) ; (near + 0x3B)

08048D78  04 0A                                add      al, 0xA 

08048D7A  30 06                                xor      [esi], al 

08048D7C  E2 F4                                loop      Label_08048D72 (08048D72) ; (near  0xF4)

08048D7E  BA 18 A2 04 08                       mov      edx, 0x804A218 

08048D83  BE F0 A3 04 08                       mov      esi, 0x804A3F0 

08048D88  B9 04 00 00 00                       mov      ecx, 0x4 

08048D8D  8B 02                                mov      eax, [edx]  ; xref ( 08048DA5 )

08048D8F  33 06                                xor      eax, [esi]  ; 下断点在这

08048D91  85 C0                                test      eax, eax 

08048D93  0F 85 08 FF FF FF                    jnz      Label_08048CA1 (08048CA1) ; 跳就死定了

08048D99  81 C2 04 00 00 00                    add      edx, 0x4 

08048D9F  81 C6 04 00 00 00                    add      esi, 0x4 

08048DA5  E2 E6                                loop      08048D8D ; (near  0xE6)

08048DA7  BE 8F A0 04 08                       mov      esi, " cracked ohh yeah !!" (0804A08F)

08048DAC  E8 42 00 00 00                       call      Function___08048DF3 (08048DF3) ; (near + 0x42)

08048DB1  C9                                   leave   

08048DB2  C3                                   ret   



Label_08048DB3:

08048DB3  BA F0 A7 04 08                       mov      edx, 0x804A7F0  ; xref ( 08048D76 )

08048DB8  EB B8                                jmp      Label_08048D72 (08048D72) ; (near  0xB8)

     
接下来,我们就一步一步跟吧!整个过程单调无趣,不多说了。注册码必需是32位,我使用“12345678abcdef0123456789abcdef0”
跟踪其流程:
转换注册码“12345678abcdef0123456789abcdef0”为:

代码:
0x804a3f0:      0x12    0x34    0x56    0x78    0x9a    0xbc    0xde    0xf0

0x804a3f8:      0x12    0x34    0x56    0x78    0x9a    0xbc    0xde    0xf0

然后每一字节与"0xf0"相异或,生成:

代码:
0x804a3f0:      0xe2    0xc4    0xa6    0x88    0x6a    0x4c    0x2e    0x00

0x804a3f8:      0xe2    0xc4    0xa6    0x88    0x6a    0x4c    0x2e    0x00

我们在08048D8F下个断点,中断后可以看到正确的值,edx中的值为:

代码:
(gdb) x /20xb $edx

0x804a218:      0xc4    0x84    0x0b    0xb5    0xa1    0x02    0x0d    0x97

0x804a220:      0x5c    0xb2    0xe1    0xc9    0xa4    0x91    0x52    0xcc

0x804a218处的值与0x804a3f0处的值相比较,相等就注册成功。结合前面注册码的变换,可以把上面的每字节与F0异或就是正确的注册码了,掏出计算器,计算如下:

代码:
3474fb4551f2fd67ac4211395461a23c

注册码与名字无关,名字可随便写,在程序中输入上面的注册码,成功!
http://bbs.pediy.com/upload/2005/4/image/crack.jpg 
补丁和注册机代码就不发作者了,古得白

Ø  实验目的与意义

1 、掌握printk 的使用、设置及实现原理,理解分级别进行打印log 信息的实现方法

2 、掌握如何分析oops 的方法

3 、掌握strace 工具的移植和使用方法

 

Ø  基本原理和方法

1 、请回顾栈的工作原理,尤其是栈帧的作用

2 、请对照printk 的源代码来进行printk 相关实验,并在实验中进一步理解源代码

 

Ø  实验内容及步骤 

一. Printk 实验 

1 、在内核中编写自己的printk 代码,可利用上次系统调用实验中已有的代码,也可利用之前驱动实验中的模块。

2 、在根文件系统中增加/proc 目录,用来挂载proc 文件系统

3 、重新烧录uImage (如果有所改动的话)和根文件系统,进入控制台之后,输入命令挂载proc 文件系统:mount – t proc none /proc 。

如果挂载成功, /proc 目录应该可以看到文件,比如下面的结果:

# ls proc

1              74             devices        kpagecount     slabinfo

100            765             diskstats      kpageflags     stat

101            773            driver         loadavg        swaps

102            783            execdomains    locks          sys

103            784            fb             meminfo        sysrq-trigger

2               786            filesystems    misc           sysvipc

3              790            fs             modules        timer_list

4              804            ide            mounts         tty

5              806            interrupts     mtd            uptime

6              buddyinfo      iomem          net            version

60             bus            ioports        pagetypeinfo   vmstat

65             cmdline        irq            partitions     yaffs

71             cpu            kallsyms       sched_debug    zoneinfo

737            cpuinfo        kmsg           self

4 、检查并修改printk 的log 级别,比如下面的命令:

# cat /proc/sys/kernel/printk
7       4       1       7
# echo 1 >/proc/sys/kernel/printk
# cat /proc/sys/kernel/printk
1       4       1       7

修改之后,默认的printk 打印(级别为4 )不会显示到串口终端,但仍可以通过“ cat /proc/kmsg ”看到打印结果。

5 、通过代码和 /proc/sys/kernel/printk 分别修改log 级别,并对应printk 的源代码来分析结果。

 

二.C 语言可变参数实验

1 、在内核代码kernel/printk.c 中的printk 函数用到了C 语言中的可变参数的用法。请参考下面的代码来学习如何使用可变参数。以下代码可直接在x86 环境测试:

 

#include <stdio.h>

 

typedef char *va_list;

 

/*

  *  * Storage alignment properties

  *   */

#define  _AUPBND                 (sizeof (signed int) - 1)

#define  _ADNBND                (sizeof (signed int) - 1)

 

/*

  *  * Variable argument list macro definitions

  *   */

#define _bnd(X, bnd)            (((sizeof (X)) + (bnd)) & (~(bnd)))

#define va_arg(ap, T)           (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))

#define va_end(ap)              (void) 0

#define va_start(ap, A)         (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

 

 

int max ( int num, ... )

{

    int m = -0x7FFFFFFF;

    int i;

     va_list ap;

    va_start ( ap, num );

    for ( i= 0; i< num; i++ )

    {

        int t = va_arg (ap, int);

        if ( t > m )

        {

            m = t;

        }

    }

    va_end (ap);

    return m;

}

 

int main ( int argc, char* argv[] )

{   

    int n = max ( 5, 2, 6 ,3 ,8 ,1);

    printf("n=%d./n", n);

    return 0;

}

 

2 、自己动手 分析上面代码可变参数的用法及实现方式。提示:va_start ( ap, num ) 是为了取得可变参数在栈中的位置,该宏展开执行后,ap 将指向第一个可变参数。可利用GDB 和汇编代码协助分析。

  

3 、ARM 架构中通常使用寄存器而不是栈来传递参数,那么,上述可变参数的方式能够用于ARM 架构中吗?请想办法找到证据 来支持你的猜测。

 

三. Oops 实验 

1 、在上次系统调用实验的代码中,人为的制造产生oops 的条件,比如下面的改动:

 

asmlinkage long sys_mytest()

{

printk("pid: %d:/tThis is my call./n",current->pid);

*(int *)0 = 0;

    return 0;

}

 

2 、从串口终端得到所产生的oops 消息,并进行初步分析

3 、在内核代码中,利用arm-linux-objdump 得到kernel/sys.o 的汇编代码,对照汇编代码进行分析

4 、arm-linux-addr2line 工具可用来寻找代码地址所对应的c 代码(),可尝试:

arm-linux-addr2line  e sys.o 0xnnnn( 你出错的代码地址)

 

(1) 在kernel/sys.c 文件里

#arm-linux-gcc  c sys.o sys.c( 生成汇编文件)

#vim sys.o

寻找:sys_mytest

找到地址是:000000b0

而通过下面Oops 信息知道:PC is at sys_mytest+0x28/0x34

则0xnnnn( 代码出错的地址)=000000b0+0x28=0xd8

(2) # arm-linux-addr2line  e sys.o 0xd8

结果输出是137 行

则查出出错的地方在sys.c 中的137 行。

 

5 、下面是我实验中遇到的oops ,请尝试做一些初步分析:

Unable to handle kernel NULL pointer dereference at virtual address 00000000

pgd = c3eb0000

[00000000] *pgd=33ddd031, *pte=00000000, *ppte=00000000

Internal error: Oops: 817 [#1]

Modules linked in:

CPU: 0    Not tainted  (2.6.25.8 #7)

PC is at sys_mytest+0x28/0x34 (表示在sys_mytest+0x28 至sys_mytest+0x34 之间,以4 个字节为一个单位)

LR is at 0xc031781c

pc : [<c00561ec>]    lr : [<c031781c>]    psr: 60000013

sp : c3ec3f98  ip : c031781c  fp : c3ec3fa4

r10: 00008528  r9 : c3ec2000  r8 : c002cb84

r7 : 00000161  r6 : beeced1c  r5 : 00000000  r4 : 0000000a

r3 : 80000013  r2 : 00000001  r1 : 00000001  r0 : 00000000

Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user

Control: 0000717f  Table: 33eb0000  DAC: 00000015

Process syscall_test (pid: 777, stack limit = 0xc3ec2260)

Stack: (0xc3ec3f98 to 0xc3ec4000)

3f80:                                                       00000000 c3ec3fa8

3fa0: c002c9e0 c00561d4 00000000 beeced1c 00000161 beecef14 beecef1c 0000000a

3fc0: 0000000a 00000000 beeced1c 00000001 beecef14 000081c4 00008528 beeced18

3fe0: beeced08 beececfc 000081e8 0000f8e0 60000010 00000161 7bf2fafd eff7fbbd

Backtrace:

[<c00561c4>] (sys_mytest+0x0/0x34) from [<c002c9e0>] (ret_fast_syscall+0x0/0x2c)

Code: e59f0010 e59310d8 ebffcf47 e3a00000 (e5800000)

---[ end trace e5388d99d2481600 ]---

 

 

四. Strace 实验 

1 、从www.sourceforge.net 上下载strace 的源代码

2 、配置并编译strace :

./configure --host=arm-linux

make

3 .将strace 工具加入到你的根文件系统中,并测试使用它

 

第一篇 : 定位Oops的具体代码行 作者:  albcamus (百無一用書生) 
(
来自Linus Torvalds的讨论:
[url]https://groups.google.com/group/linux.kernel/browse_thread/thread/b70bffe9015a8c41/ed9c0a0cfcd31111[/url]
又,[url]http://kerneltrap.org/Linux/Further_Oops_Insights[/url]
)
     
        例如这样的一个Oops:
                Oops: 0000 [#1] PREEMPT SMP  
                Modules linked in: capidrv kernelcapi isdn slhc ipv6 loop dm_multipath snd_ens1371 gameport snd_rawmidi snd_ac97_codec ac97_bus snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd parport_pc floppy parport pcnet32 soundcore mii pcspkr snd_page_alloc ac i2c_piix4 i2c_core button power_supply sr_mod sg cdrom ata_piix libata dm_snapshot dm_zero dm_mirror dm_mod BusLogic sd_mod scsi_mod ext3 jbd mbcache uhci_hcd ohci_hcd ehci_hcd

                Pid: 1726, comm: kstopmachine Not tainted (2.6.24-rc3-module #2)
                EIP: 0060:[<c04e53d6>] EFLAGS: 00010092 CPU: 0
                EIP is at list_del+0xa/0x61
                EAX: e0c3cc04 EBX: 00000020 ECX: 0000000e EDX: dec62000
                ESI: df6e8f08 EDI: 000006bf EBP: dec62fb4 ESP: dec62fa4
                 DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068 
                Process kstopmachine (pid: 1726, ti=dec62000 task=df8d2d40 task.ti=dec62000)
                Stack: 000006bf dec62fb4 c04276c7 00000020 dec62fbc c044ab4c dec62fd0 c045336c 
                       df6e8f08 c04532b4 00000000 dec62fe0 c043deb0 c043de75 00000000 00000000 
                       c0405cdf df6e8eb4 00000000 00000000 00000000 00000000 00000000 
                Call Trace:
                 [<c0406081>] show_trace_log_lvl+0x1a/0x2f
                 [<c0406131>] show_stack_log_lvl+0x9b/0xa3
                 [<c04061dc>] show_registers+0xa3/0x1df
                 [<c0406437>] die+0x11f/0x200
                 [<c0613cba>] do_page_fault+0x533/0x61a
                 [<c06123ea>] error_code+0x72/0x78
                 [<c044ab4c>] __unlink_module+0xb/0xf
                 [<c045336c>] do_stop+0xb8/0x108
                 [<c043deb0>] kthread+0x3b/0x63
                 [<c0405cdf>] kernel_thread_helper+0x7/0x10
                 =======================
                Code: 6b c0 e8 2e 7e f6 ff e8 d1 16 f2 ff b8 01 00 00 00 e8 aa 1c f4 ff 89 d8 83 c4 10 5b 5d c3 90 90 90 55 89 e5 53 83 ec 0c 8b 48 04 <8b> 11 39 c2 74 18 89 54 24 08 89 44 24 04 c7 04 24 be 32 6b c0  
                EIP: [<c04e53d6>] list_del+0xa/0x61 SS:ESP 0068:dec62fa4
                note: kstopmachine[1726] exited with preempt_count 1
     
        1, 有自己编译的vmlinux: 使用gdb
     
           编译时打开complie with debug info选项。 

           注意这行: 
     
                EIP is at list_del+0xa/0x61
     
           这告诉我们,list_del函数有0x61这么大,而Oops发生在0xa处。 那么我们先看一下list_del从哪里开始: 

                # grep list_del /boot/System.map-2.6.24-rc3-module
                c10e5234 T plist_del
                c10e53cc T list_del
                c120feb6 T klist_del
                c12d6d34 r __ksymtab_list_del
                c12dadfc r __ksymtab_klist_del
                c12e1abd r __kstrtab_list_del
                c12e9d03 r __kstrtab_klist_del

           于是我们知道,发生Oops时的EIP值是:

                c10e53cc + 0xa  == c10e53d6

           然后用gdb查看:

                # gdb /home/arc/build/linux-2.6/vmlinux
                (gdb) b *0xc10e53d6
                Breakpoint 1 at 0xc10e53d6: file /usr/src/linux-2.6.24-rc3/lib/list_debug.c, line 64.

           看,gdb直接就告诉你在哪个文件、哪一行了。

           gdb中还可以这样:

                # gdb Sources/linux-2.6.24/vmlinux
                (gdb) l *do_fork+0x1f
                0xc102b7ac is in do_fork (kernel/fork.c:1385).
                1380
                1381    static int fork_traceflag(unsigned clone_flags)
                1382    {
                1383            if (clone_flags & CLONE_UNTRACED)
                1384                    return 0;
                1385            else if (clone_flags & CLONE_VFORK) {
                1386                    if (current->ptrace & PT_TRACE_VFORK)
                1387                            return PTRACE_EVENT_VFORK;
                1388            } else if ((clone_flags & CSIGNAL) != SIGCHLD) {
                1389                    if (current->ptrace & PT_TRACE_CLONE)
                (gdb)

            也可以直接知道line number。

            或者:

                (gdb) l *(0xffffffff8023eaf0 + 0xff)  /* 出错函数的地址加上偏移 */



        2, 没有自己编译的vmlinux: TIPS

           如果在lkml或bugzilla上看到一个Oops,而自己不能重现,那就只能反汇编以"Code:"开始的行。 这样可以尝试定位到
           源代码中。

           注意,Oops中的Code:行,会把导致Oops的第一条指令,也就是EIP的值的第一个字节, 用尖括号<>括起来。 但是,有些
           体系结构(例如常见的x86)指令是不等长的(不一样的指令可能有不一样的长度),所以要不断的尝试(trial-and-error)。

           Linus通常使用一个小程序,类似这样:

                const char array[] = "/xnn/xnn/xnn...";
                int main(int argc, char *argv[])
                {
                        printf("%p/n", array);
                        *(int *)0 = 0;
                }

e.g. /*{{{*/ /* 注意, array一共有从array[0]到array[64]这65个元素, 其中出错的那个操作码<8b> == arry[43] */
#include <stdio.h>
#include <stdlib.h>


const char array[] ="/x6b/xc0/xe8/x2e/x7e/xf6/xff/xe8/xd1/x16/xf2/xff/xb8/x01/x00/x00/x00/xe8/xaa/x1c/xf4/xff/x89/xd8/x83/xc4/x10/x5b/x5d/xc3/x90/x90/x90/x55/x89/xe5/x53/x83/xec/x0c/x8b/x48/x04/x8b/x11/x39/xc2/x74/x18/x89/x54/x24/x08/x89/x44/x24/x04/xc7/x04/x24/xbe/x32/x6b/xc0";
int main(int argc, char *argv[])
{
        printf("%p/n", array);
        *(int *)0 = 0;
}
/*}}}*/



           用gcc -g编译,在gdb里运行它:

                [arc@dhcp-cbjs05-218-251 ~]$ gdb hello
                GNU gdb Fedora (6.8-1.fc9)
                Copyright (C) 2008 Free Software Foundation, Inc.
                License GPLv3+: GNU GPL version 3 or later <[url]http://gnu.org/licenses/gpl.html[/url]>
                This is free software: you are free to change and redistribute it.
                There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
                and "show warranty" for details.
                This GDB was configured as "x86_64-redhat-linux-gnu"...
                (no debugging symbols found)
                (gdb) r
                Starting program: /home/arc/hello
                0x80484e0

                Program received signal SIGSEGV, Segmentation fault.

           注意,这时候就可以反汇编0x80484e0这个地址:

                (gdb) disassemble 0x80484e0
                Dump of assembler code for function array:
                0x080484e0 <array+0>:   imul   $0xffffffe8,%eax,%eax
                0x080484e3 <array+3>:   jle,pn 0x80484dc <__dso_handle+20>
                0x080484e6 <array+6>:   ljmp   *<internal disassembler error>
                0x080484e8 <array+8>:   rcll   (%esi)
                0x080484ea <array+10>:  repnz (bad)
                0x080484ec <array+12>:  mov    $0x1,%eax
                0x080484f1 <array+17>:  call   0x7f8a1a0
                0x080484f6 <array+22>:  mov    %ebx,%eax
                0x080484f8 <array+24>:  add    $0x10,%esp
                0x080484fb <array+27>:  pop    %ebx
                0x080484fc <array+28>:  pop    %ebp
                0x080484fd <array+29>:  ret
                0x080484fe <array+30>:  nop
                0x080484ff <array+31>:  nop
                0x08048500 <array+32>:  nop
                0x08048501 <array+33>:  push   %ebp
                0x08048502 <array+34>:  mov    %esp,%ebp
                0x08048504 <array+36>:  push   %ebx
                0x08048505 <array+37>:  sub    $0xc,%esp
                0x08048508 <array+40>:  mov    0x4(%eax),%ecx
                0x0804850b <array+43>:  mov    (%ecx),%edx
                0x0804850d <array+45>:  cmp    %eax,%edx
                0x0804850f <array+47>:  je     0x8048529
                0x08048511 <array+49>:  mov    %edx,0x8(%esp)
                0x08048515 <array+53>:  mov    %eax,0x4(%esp)
                0x08048519 <array+57>:  movl   $0xc06b32be,(%esp)
                0x08048520 <array+64>:  add    %ah,0xa70
                End of assembler dump.
                (gdb)

          OK, 现在你知道出错的那条指令是array[43],也就是mov    (%ecx),%edx,也就是说,(%ecx)指向了一个错误内存地址。

补充:

为了使汇编代码和C代码更好的对应起来, Linux内核的Kbuild子系统提供了这样一个功能: 任何一个C文件都可以单独编译成汇编文件,例如:

make path/to/the/sourcefile.s

例如我想把kernel/sched.c编译成汇编,那么:

make kernel/sched.s V=1

或者:

make kernel/sched.lst V=1

         编译出*.s文件
           
           有时侯需要对*.s文件进行分析,以确定BUG所在的位置。 对任何一个内核build目录下的*.c文件,都可以
           直接编译出*.s文件。

                   # make drivers/net/e100.s V=1
           
           而对于自己写的module,就需要在Makefile中有一个灵活的target写法:
                   
                # cat Makefile
                obj-m := usb-skel.o
                KDIR := /lib/modules/`uname -r`/build
                traget := modules

                default:
                        make -C $(KDIR) M=$(shell pwd) $(target)
                clean:
                        rm -f *.o *.ko .*.cmd *.symvers *.mod.c
                        rm -rf .tmp_versions


                # make target=usb-skel.s V=1
           
           这样,kbuild系统才知道你要make的目标不是modules,而是usb-skel.s。




另外, 内核源代码目录的./scripts/decodecode文件是用来解码Oops的:

./scripts/decodecode < Oops.txt
 
第二篇:  定位可动态加载的内核模块的OOPS代码行 作者:  Godbach (To be 千里马!)
最近又仔细学习了albcamus版主提供的《定位Oops的具体代码行》(链接:http://linux.chinaunix.net/bbs/viewthread.php?tid=1008573 ),并且进行了实践。因此这里简单总结一下,并且以实例的方式给出定位可动态加载模块Oops信息的方法。

本文欢迎自由转载,但请标明出处,并保证本文的完整性。
Godbach
Apr 19, 2009

1. 从vmlinux获取具体的代码行

文 章中albcamus版主也提到了,需要有自己编译的vmlinux,而且编译时打开compile with debug info. 这个选项打开之后会使vmlinux文件比不加调试信息大一些。我这里代调试信息的是49M。建议如果学习的时候,想使用gdb的方式获取出错代码行的 话,就加上这个编译条件。
然后就可以按照具体的方法去操作,可以定位到具体的C 代码行。

2. 从自己编译的内核模块出错信息中获取代码行
以ldd3中提供的misc-modules/faulty.c为例。主要以faulty_write函数作分析。
(1)由于作者提供的函数代码就一样,过于简单,我这里简单加上一些代码(也就是判断和赋值),如下:

ssize_t faulty_write ( struct file * filp, const char __user * buf, size_t count , 
                loff_t * pos) 
{ 
        /* make a simple fault by dereferencing a NULL pointer */ 
        if ( count > 0x100) 
                count = 0x100; 
        * ( int * ) 0 = 0; 
        return count ; 
}

(2)编译该模块,并且mknod /dev/faulty
(3)向该模块写入数据:echo 1 > /dev/faulty, 内核OOPS,信息如下:

 

< 1> BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000
printing eip: 
f8e8000e
* pde = 00000000
Oops: 0002 [ # 3] 
SMP
Modules linked in: faulty autofs4 hidp rfcomm l2cap . . . . . . //此处省略若干字符

CPU: 1
EIP: 0060: [ < f8e8000e> ] Not tainted VLI
EFLAGS: 00010283 ( 2. 6. 18. 3 # 2) 
EIP is at faulty_write+ 0xe/ 0x19 [ faulty] 
eax: 00000001 ebx: f4f6ca40 ecx: 00000001 edx: b7c2d000
esi: f8e80000 edi: b7c2d000 ebp: 00000001 esp: f4dc5f84
ds: 007b es: 007b ss: 0068
Process bash ( pid: 6084, ti= f4dc5000 task= f7c8d4d0 task. ti= f4dc5000) 
Stack : c1065914 f4dc5fa4 f4f6ca40 fffffff7 b7c2d000 f4dc5000 c1065f06 f4dc5fa4
       00000000 00000000 00000000 00000001 00000001 c1003d0b 00000001 b7c2d000
       00000001 00000001 b7c2d000 bfd40aa8 ffffffda 0000007b c100007b 00000004
Call Trace: 
[ < c1065914> ] vfs_write+ 0xa1/ 0x143
[ < c1065f06> ] sys_write+ 0x3c/ 0x63
[ < c1003d0b> ] syscall_call+ 0x7/ 0xb
Code: Bad EIP value. 
EIP: [ < f8e8000e> ] faulty_write+ 0xe/ 0x19 [ faulty] SS: ESP 0068: f4dc5f84

其 中,我们应该关注的信息是第一行红色标出部分:告诉我们操作了NULL指针。其次,就是第二行红色部分:EIP is at faulty_write+0xe/0x19。这个出错信息告诉我们EIP指针出现问题的地方时faulty_write函数,而且指出了是faulty 模块。
同时,faulty_write+0xe/0x19的后半部分0xe/0x19,说明该函数的大小时0x019,出错位置是在0x0e。这两个值应该值得都是汇编代码的值。
(4)将faulty模块反汇编出汇编代码:
          objdump -d faulty.ko > faulty.s
        或
          objdump -d faulty.o > faulty.s
然后,我们打开faulty.s文件。由于我们需要关注的部分正好在文件的前面,因此我这里只贴出文件的前面一部分内容:

faulty. o: file format elf32- i386

Disassembly of section . text: 

00000000 < faulty_write> : 
   0: 81 f9 00 01 00 00 cmp $0x100, % ecx
   6: b8 00 01 00 00 mov $0x100, % eax
   b: 0f 46 c1 cmovbe % ecx, % eax
   e: c7 05 00 00 00 00 00 movl $0x0, 0x0
  15: 00 00 00
  18: c3 ret 

00000019 < cleanup_module> : 
  19: a1 00 00 00 00 mov 0x0, % eax
  1e: ba 00 00 00 00 mov $0x0, % edx
  23: e9 fc ff ff ff jmp 24 < cleanup_module+ 0xb> 

00000028 < faulty_init> : 
  28: a1 00 00 00 00 mov 0x0, % eax
  2d: b9 00 00 00 00 mov $0x0, % ecx
  32: ba 00 00 00 00 mov $0x0, % edx
  37: e8 fc ff ff ff call 38 < faulty_init+ 0x10> 
  3c: 85 c0 test % eax, % eax
  3e: 78 13 js 53 < faulty_init+ 0x2b> 
  40: 83 3d 00 00 00 00 00 cmpl $0x0, 0x0
  47: 74 03 je 4c < faulty_init+ 0x24> 
  49: 31 c0 xor % eax, % eax
  4b: c3 ret 
  4c: a3 00 00 00 00 mov % eax, 0x0
  51: 31 c0 xor % eax, % eax
  53: c3 ret

由以上汇编代码可以看出,faulty_write函数的大小确实是0x18 -0x00 +1 = 0x19. 那么EIP指针出问题的地方是0x0e处,代码为:

e: c7 05 00 00 00 00 00 movl $0x0, 0x0

这行汇编代码就是将0值保存到0地址的位置。那么很显然是非法的。这一行对应的C 代码应该就是:
*(int *)0 = 0;

(5)以上是对模块出错信息的分析。不过也有一定的局限。
   首先就是EIP出错的位置正好在本模块内部,这样可以在本模块定位问题;
   其次,要求一定的汇编基础,特别是当一个函数的代码比较多时,对应的汇编代码也比较大,如何准确定位到C代码行需要一定的经验和时间。

    实际运用中,可以将内核代码行的定位和可动态加载的内核模块代码行的定位结合起来使用,应该可以较快的定位问题。

    分析中有纰漏或者不妥的地方希望大家指出,也希望有网友分享更有效的方法。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值