预备知识:
1. 常用命令
(gdb) b * 0x80483d8 # 在地址处设置断点
(gdb) b main # 在标号处设置断点
(gdb) disass 0x80483d8 # 反汇编
(gdb) si 执行1条汇编指令,ni执行整个汇编函数;s执行1条C语句,n执行整个C函数
(gdb) i reg # 查看所有寄存器内容
(gdb) x/20i 0x12345678 # 按汇编代码查看内容
(gdb) x/20i $esp # 按汇编代码查看内容
(gdb) x/5x $esp # 按16进制查看寄存器esp内容
(gdb) x/5x 0x12345678 # 按16进制查看内容
(gdb) x/20strings 0x12345678 # 按字符串查看内容
2. 父子程序之间的关系:
|---------------| <----- 父进程堆栈
| param_1 |
|---------------|
| param_2 |
|---------------|
| param_3 | <----- call执行之前的esp
|---------------|
|调用者eip | <----- call执行之后的esp
|---------------|
| | <----- 以下为子进程堆栈
|---------------|
通常父程序把子程序所需要的参数压入父程序的堆栈,"call 子程序地址" 把紧接call指令的下一条指令地址压入父程序的堆栈,作为子程序的返回地址。刚进入子程序时(尚未执行自程序的第1条汇编指令),ESP = 父亲的EIP。
我们根据以上的结论,把main当作子进程,向上反推,看看main的老爸是谁,以及它老爸的老爸是谁
注意:
(1)如果用jump指令,则上述不成立。比如从动态连接器进入_start时是用jmp指令而非call指令。动态连接器的开头也定义了一个全局符号:也叫_start。这样从动态连接器里jmp到后面的_start(start.s里)可以保持名字统一连贯。
(2)在Linux系统中,应用程序和解释器的装入/启动是在Linux内核中由系统调用execve()完成,2者都被映射装入用户地址空间。execve()系统调用结束,返回用户空间时进入动态连接器入口开始执行。动态连接的实现由动态连接器在用户空间完成。(参考:漫谈兼容内核之八)。所以,从进入动态连接器直到main()程序启动,全都是在用户态的事情,使用用户态堆栈,多个函数之间的call调用形成堆栈侦(push %ebp; mov %esp,%ebp)也都在同一用户堆栈上,不能从用户空间向上一直反推到内核堆栈。
3.参见before main()分析:
_start( )的堆栈内容如下:
%esp The stack contains the arguments and environment:
0(%esp) argc 堆栈顶(低地址)
4(%esp) argv[0]
8(%esp) argv[1]
...
...
(4*argc)(%esp) NULL
(4*(argc+1))(%esp) envp[0]
(4*(argc+2))(%esp) envp[1] (高地址)
...
...
NULL
从_start到_main经过多级调用,后面我们看到,到_main时,堆栈实际上并不是这样排列的,可能是main之前的例程太多,堆栈塞了一堆其他东西。但是只要给出**envp和**argv,就可以找到那些在堆栈里连续存放的字符串。
4. 在Linux上面的虚拟地址空间:
(1) user area: 0x0 - 0x0bffffff (3GB内存空间)
0x0 - 0x08048000 Thread stack
0x08048000 - 0x40000000 用户程序 Text, Data
0x40000000 - 0xbfffffff shared LIB(ld-linux.so,libm.so,libc.so...)、用户程序Stack
(2) kernel area: 0x0c000000 - 0xffffffff (1GB内存空间)
(一)首先上查 main的前三代
=====================================================================
[test@radhat]$ more t1.c
#include <stdio.h>
Foo(char* s)
{
char buf[16]="";
strcpy(buf, s);
# printf("The input String is %s/n", buf); # 注释掉printf函数,容易跟踪,不至于眼花。
}
main(int argc, char* argv[])
{
if(argc == 2)
{
Foo(argv[1]);
}
else
{
printf("Usage: %s <A string>/n", argv[0]);
}
}
[test@radhat]$ gcc -g -o t1 t1.c
[test@radhat]$ gdb –q t1
(gdb) b main
(gdb) r
(gdb) disass main
Dump of assembler code for function main:
0x080483c7 <main+0>: lea 0x4(%esp),%ecx
0x080483cb <main+4>: and $0xfffffff0,%esp
0x080483ce <main+7>: pushl 0xfffffffc(%ecx)
0x080483d1 <main+10>: push %ebp
0x080483d2 <main+11>: mov %esp,%ebp
。。。
。。。
(gdb) info reg esp
esp 0xbfc93ea0
(gdb) x/1x 0xbfc93ea0
0xbfc93ea0: 0x4f969875
(gdb) disass 0x4f969875
Dump of assembler code for function __new_exitfn:
0x4f969860 <__new_exitfn+0>: push %ebp
0x4f969861 <__new_exitfn+1>: xor %eax,%eax
0x4f969863 <__new_exitfn+3>: mov %esp,%ebp
0x4f969865 <__new_exitfn+5>: mov $0x1,%ecx
0x4f96986a <__new_exitfn+10>: push %edi
0x4f96986b <__new_exitfn+11>: push %esi
0x4f96986c <__new_exitfn+12>: push %ebx
0x4f96986d <__new_exitfn+13>: sub $0x8,%esp
0x4f969870 <__new_exitfn+16>: call 0x4f953c30 <__i686.get_pc_thunk.bx>
0x4f969875 <__new_exitfn+21>: add $0x10d77f,%ebx
# main的上级是__new_exitfn
#####################################################################
(gdb) b * 0x4f969860 # 在 __new_exitfn开头设断点
(gdb) r
(gdb) i reg esp
esp 0xbfaf351c
(gdb) x/1x 0xbfaf351c
0xbfaf351c: 0x4f9699a0
(gdb) disass 0x4f9699a0
Dump of assembler code for function __cxa_atexit_internal:
0x4f969980 <__cxa_atexit_internal+0>: push %ebp
0x4f969981 <__cxa_atexit_internal+1>: mov %esp,%ebp
0x4f969983 <__cxa_atexit_internal+3>: sub $0x8,%esp
0x4f969986 <__cxa_atexit_internal+6>: mov %ebx,(%esp)
0x4f969989 <__cxa_atexit_internal+9>: call 0x4f953c30 <__i686.get_pc_thunk.bx>
0x4f96998e <__cxa_atexit_internal+14>: add $0x10d666,%ebx
0x4f969994 <__cxa_atexit_internal+20>: mov %esi,0x4(%esp)
0x4f969998 <__cxa_atexit_internal+24>: mov 0x8(%ebp),%esi
0x4f96999b <__cxa_atexit_internal+27>: call 0x4f969860 <__new_exitfn>
0x4f9699a0 <__cxa_atexit_internal+32>: mov %eax,%edx
# __new_exitfn()的上级是__cxa_atexit_internal()
#####################################################################
(gdb) b * 0x4f969980 # 在 __cxa_atexit_internal开头设断点
(gdb) r
(gdb) i reg esp
esp 0xbf80ddcc
(gdb) x/1x 0xbf80ddcc
0xbf80ddcc: 0x4f953d5e
(gdb) disass 0x4f953d5e
Dump of assembler code for function __libc_start_main:
0x4f953d10 <__libc_start_main+0>: push %ebp
0x4f953d11 <__libc_start_main+1>: xor %edx,%edx
0x4f953d13 <__libc_start_main+3>: mov %esp,%ebp
0x4f953d15 <__libc_start_main+5>: push %edi
0x4f953d16 <__libc_start_main+6>: push %esi
0x4f953d17 <__libc_start_main+7>: push %ebx
0x4f953d18 <__libc_start_main+8>: call 0x4f953c30 <__i686.get_pc_thunk.bx>
0x4f953d1d <__libc_start_main+13>: add $0x1232d7,%ebx
0x4f953d23 <__libc_start_main+19>: sub $0x4c,%esp
0x4f953d26 <__libc_start_main+22>: mov 0x14(%ebp),%edi
0x4f953d29 <__libc_start_main+25>: mov 0x1c(%ebp),%ecx
0x4f953d2c <__libc_start_main+28>: mov 0xffffff14(%ebx),%eax
0x4f953d32 <__libc_start_main+34>: test %eax,%eax
0x4f953d34 <__libc_start_main+36>: jne 0x4f953df4 <__libc_start_main+228>
0x4f953d3a <__libc_start_main+42>: mov 0xffffffd0(%ebx),%eax
0x4f953d40 <__libc_start_main+48>: test %ecx,%ecx
0x4f953d42 <__libc_start_main+50>: mov %edx,(%eax)
0x4f953d44 <__libc_start_main+52>: je 0x4f953d5e <__libc_start_main+78>
0x4f953d46 <__libc_start_main+54>: movl $0x0,0x8(%esp)
0x4f953d4e <__libc_start_main+62>: movl $0x0,0x4(%esp)
0x4f953d56 <__libc_start_main+70>: mov %ecx,(%esp)
0x4f953d59 <__libc_start_main+73>: call 0x4f969980 <__cxa_atexit_internal>
0x4f953d5e <__libc_start_main+78>: mov 0xffffff50(%ebx),%esi
# __cxa_atexit_internal()的上级是 __libc_start_main()
######################################################################
(gdb) b * 0x4f953d10 # 在 __libc_start_main开头设断点
Breakpoint 4 at 0x4f953d10
(gdb) r
(gdb) i reg esp
esp 0xbfe498dc
(gdb) x/1x 0xbfe498dc
0xbfe498dc: 0x08048301
(gdb) disass 0x08048301
Dump of assembler code for function _start:
0x080482e0 <_start+0>: xor %ebp,%ebp
0x080482e2 <_start+2>: pop %esi
0x080482e3 <_start+3>: mov %esp,%ecx
0x080482e5 <_start+5>: and $0xfffffff0,%esp
0x080482e8 <_start+8>: push %eax
0x080482e9 <_start+9>: push %esp
0x080482ea <_start+10>: push %edx
0x080482eb <_start+11>: push $0x8048420
0x080482f0 <_start+16>: push $0x8048430
0x080482f5 <_start+21>: push %ecx
0x080482f6 <_start+22>: push %esi
0x080482f7 <_start+23>: push $0x80483c7
0x080482fc <_start+28>: call 0x80482a8 <__libc_start_main@plt>
0x08048301 <_start+33>: hlt
0x08048302 <_start+34>: nop
0x08048303 <_start+35>: nop
End of assembler dump.
# __libc_start_main()的上级是 _start()
######################################################################
(gdb) b * 0x080482e0 # 在 _start开头设断点
(gdb) r bbbbbbbbbbb # 传递1个参数“bbbbbbbbbbb”给 t1程序
(gdb) i r esp
esp 0xbfba5660
(gdb) x/1x 0xbfba5660
0xbfba5660: 0x00000002 # 0x00000002 不是一个地址,没办法继续向上追查了。实际上它是argc,即参数个数(1个参数 + 程序本身=2)。
(gdb) x/40 0xbfba5660 # 看看_start堆栈的内容。基本上都是一些地址(即:字符串指针)
0xbfb54600: 0x00000002 0xbfb55bd4 0xbfb55bdd 0x00000000
0xbfb54610: 0xbfb55be9 0xbfb55bf9 0xbfb55c04 0xbfb55c14
0xbfb54620: 0xbfb55c22 0xbfb55c30 0xbfb55c46 0xbfb55c64
0xbfb54630: 0xbfb55c6e 0xbfb55e31 0xbfb55e3d 0xbfb55e49
…
…
(gdb) x/40strings 0xbfb55bd4 # 看看这些指针里都放了什么内容。参考前面对_start的堆栈的说明。
0xbfb55bd4: "/foot/t1" # *argv[0]
0xbfb55bdd: 'bbbbbbbbbbb' # *argv[1]
0xbfb55be9: "HOSTNAME=radhat" # *envp[0]
0xbfb55bf9: "TERM=vt100" # *envp[1]
0xbfb55c04: "SHELL=/bin/bash" # *envp[2]
0xbfb55c14: "HISTSIZE=1000"
0xbfb55c22: "KDE_NO_IPV6=1"
0xbfb55c30: "QTDIR=/usr/lib/qt-3.3"
0xbfb55c46: "QTINC=/usr/lib/qt-3.3/include"
0xbfb55c64: "USER=test"
...
...
(gdb)
# 这样的方法有局限,如果是jmp指令进入子程序,就没办法由子程序向上反查了。
==========================================================
(二)我们看看程序执行到main()时,堆栈上大致都存放了哪些内容
[test@radhat]$ gdb –q t1
(gdb) b main
(gdb) r aaa bbb cccc dddddd # 传递4个参数
Starting program: /foot/t1 aaa bbb cccc dddddd
Breakpoint 1, main (argc=5, argv=0xbfe490e4) at t1.c:9
9 if(argc == 2)
(gdb) x/40x $esp # 看看main的堆栈。4字节1列。
0xbfe49030: 0x4f969875 0xbfe490fc 0xbfe49058 0x4fa76ff4
0xbfe49040: 0xbfe49060 0xbfe49060 0xbfe490b8 0x4f953dec
0xbfe49050: 0x4f93aca0 0x08048430 0xbfe490b8 0x4f953dec
0xbfe49060: 0x00000005 0xbfe490e4 0xbfe490fc 0x4f92ea20
0xbfe49070: 0x00000000 0xb7fa26a0 0x00000001 0x00000001
0xbfe49080: 0x4fa76ff4 0x4f93aca0 0x00000000 0xbfe490b8
0xbfe49090: 0x00fa6a06 0xf08bc7d7 0x00000000 0x00000000
# 我们1个1个来看。x指令查看1个内存地址里面放了什么内容:一般存放4种数据:1。一个地址 2。一个常数 3。一个字符串 4。汇编指令
(gdb) x/1x 0x4f969875 # 第1个:0x4f969875内存地址里存放 汇编指令。
0x4f969875 <__new_exitfn+21>: 0xd77fc381
(gdb) x/20i 0x4f969875 # 可以用x/ni显示汇编指令。
0x4f969875 <__new_exitfn+21>: add $0x10d77f,%ebx
0x4f96987b <__new_exitfn+27>: cmpl $0x0,%gs:0xc
0x4f969883 <__new_exitfn+35>: je 0x4f969886 <__new_exitfn+38>
0x4f969885 <__new_exitfn+37>: lock cmpxchg %ecx,0xc6c(%ebx)
0x4f96988d <__new_exitfn+45>: jne 0x4f9699d4 <_L_lock_23>
0x4f969893 <__new_exitfn+51>: mov 0x310(%ebx),%esi
…
…
(gdb) x/1x 0xbfe490fc # 第2个:0xbfe490fc里存放1个地址:0xbfe4abe9
0xbfe490fc: 0xbfe4abe9
(gdb) x/1strings 0xbfe4abe9 # 继续看看0xbfe4abe9里存放字符串"HOSTNAME=radhat"
0xbfe4abe9: "HOSTNAME=radhat"
(gdb) x/1x 0xbfe49058 # 第3个:0xbfe49058里存放1个地址:0xbfe490b8
0xbfe49058: 0xbfe490b8
(gdb) x/1x 0xbfe490b8 # 继续看看0xbfe490b8里存放1个常量0
0xbfe490b8: 0x00000000
(gdb) x/1x 0x4fa76ff4 # 第4个:0x4fa76ff4里存放1个地址:0x4fa76d9c
0x4fa76ff4: 0x4fa76d9c
(gdb) x/1x 0x4fa76d9c # 继续看看0x4fa76d9c里存放1个常量1
0x4fa76d9c: 0x00000001
(gdb) x/1x 0xbfe49060 # 第5,6个:0xbfe49060里存放1个常量5
0xbfe49060: 0x00000005
(gdb) x/1x 0xbfe490b8 # 第7个:0xbfe490b8里存放1个常量0
0xbfe490b8: 0x00000000
(gdb) x/1x 0x4f953dec # 第8个:0x4f953dec里存放汇编指令
0x4f953dec <__libc_start_main+220>: 0xe8240489
(gdb) x/1x 0x4f93aca0 # 第9个:0x4f93aca0里存放汇编指令
0x4f93aca0 <_rtld_local_ro>: 0x00000000
(gdb) x/1x 0x08048430 # 第10个:0x08048430里存放汇编指令
0x8048430 <__libc_csu_init>: 0x57e58955
(gdb) x/1x 0xbfe490b8 # 第11个:0xbfe490b8里存放1个常量0
0xbfe490b8: 0x00000000
(gdb) x/1x 0x4f953dec # 第12个:0x4f953dec里存放汇编指令
0x4f953dec <__libc_start_main+220>: 0xe8240489
# 第13个:0x00000005 是 argc 即:参数个数
(gdb) x/1x 0xbfe490e4 # 第14个:0xbfe490e4是 **argv
0xbfe490e4: 0xbfe4abcc # 0xbfe490e4里存放一个地址0xbfe4abcc
(gdb) x/8x 0xbfffe234 # 查看指针数组*argv[]的内容,包含了1组地址,以0结尾
0xbfe490e4: 0xbfe4abcc 0xbfe4abd5 0xbfe4abd9 0xbfe4abdd
0xbfe490f4: 0xbfe4abe2 0x00000000 0xbfe4abe9 0xbfe4abf9
(gdb) x/10strings 0xbfe4abcc # 按字符形式查看指针数组*argv[]的内容
0xbfe4abcc: "/foot/t1"
0xbfe4abd5: "aaa"
0xbfe4abd9: "bbb"
0xbfe4abdd: "cccc"
0xbfe4abe2: "dddddd"
0xbfe4abe9: "HOSTNAME=radhat"
0xbfe4abf9: "TERM=vt100"
0xbfe4ac04: "SHELL=/bin/bash"
0xbfe4ac14: "HISTSIZE=1000"
0xbfe4ac22: "KDE_NO_IPV6=1"
(gdb) x/1x 0xbfe490fc # 第15个:0xbfe490fc是 **envp
0xbfe490fc: 0xbfe4abe9
(gdb) x/1strings 0xbfe4abe9
0xbfe4abe9: "HOSTNAME=radhat" # *envp[0] = "HOSTNAME=radhat"
(gdb) x/20strings 0xbfe4abe9 # 按字符串查看指针数组*envp[]的内容
0xbfe4abe9: "HOSTNAME=radhat"
0xbfe4abf9: "TERM=vt100"
0xbfe4ac04: "SHELL=/bin/bash"
0xbfe4ac14: "HISTSIZE=1000"
0xbfe4ac22: "KDE_NO_IPV6=1"
0xbfe4ac30: "QTDIR=/usr/lib/qt-3.3"
0xbfe4ac46: "QTINC=/usr/lib/qt-3.3/include"
0xbfe4ac64: "USER=test"
...
...
(gdb) x/1x 0x4f92ea20 # 第16个:0x4f92ea20里存放汇编指令
0x4f92ea20 <_dl_fixup+176>: 0x83f0558b
(gdb) quit #用户堆栈的最顶端是0xbfffffff,有兴趣的继续看看。
[test@radhat]$