目录

Linux X86 Program Start Up

目录

程序开始

一个程序真正的入口是_start函数。

start函数有三个参数:

  • agrc——表示有多少个命令行参数,第一个就是执行程序名,所以argc最少为1。
  • argv是具体的参数。字符串数组
    • argv[0]为空串("") 。 argv[1] 为在DOS命令行中执行程序名后的第一个字符串; argv[2] 为执行程序名后的第二个字符串; …… argv[argc]为NULL。
  • envp是系统的环境变量,字符串数组,envp[]的每一个元素都包含在ENVVAR=value形式的字符串。

我们使用一个简单的程序simple.c看一下这个过程:

1
2
3
4
int main()
{
    return 0;
}

编译后使用

1
objdump -f simple
1
2
3
4
5
giantbranch@ubuntu:~/Desktop$ objdump -f simple
simple:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000004003e0

这里显示的开始地址就是_start函数的地址,我们进行反编译看一看:

1
objdump -d simple
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Disassembly of section .text:

00000000004003e0 <_start>:
  4003e0:	31 ed                	xor    %ebp,%ebp
  4003e2:	49 89 d1             	mov    %rdx,%r9
  4003e5:	5e                   	pop    %rsi
  4003e6:	48 89 e2             	mov    %rsp,%rdx
  4003e9:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
  4003ed:	50                   	push   %rax
  4003ee:	54                   	push   %rsp
  4003ef:	49 c7 c0 60 05 40 00 	mov    $0x400560,%r8
  4003f6:	48 c7 c1 f0 04 40 00 	mov    $0x4004f0,%rcx
  4003fd:	48 c7 c7 d6 04 40 00 	mov    $0x4004d6,%rdi
  400404:	e8 b7 ff ff ff       	callq  4003c0 <__libc_start_main@plt>
  400409:	f4                   	hlt    
  40040a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)

可以看到_start函数向寄存器中传递了参数:

  • 0x400560:__libc_csu_fini的地址
  • 0x4004f0:__libc_csu_init的地址
  • 0x4004d6:main的地址

然后调用了一个libc函数__libc_start_main:

1
2
3
4
5
6
7
8
extern int BP_SYM (__libc_start_main) (int (*main) (int, char **, char **),
		int argc,
		char *__unbounded *__unbounded ubp_av,
		void (*init) (void),
		void (*fini) (void),
		void (*rtld_fini) (void),
		void *__unbounded stack_end)
__attribute__ ((noreturn));

__libc_start_main函数做了什么呢?

  1. 处理关于setuid、setgid程序的安全问题
  2. 启动线程
  3. fini函数和rtld_fini函数作为参数传递给at_exit调用,使它们在at_exit里被调用,从而完成用户程序和加载器的调用结束之后的清理工作
  4. 调用其init参数
  5. 调用main函数,并把argcargv参数、环境变量传递给它
  6. 调用exit函数,并将main函数的返回值传递给它

init参数就是__libc_csu_init函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.text:00000000004005C0 ; void _libc_csu_init(void)
.text:00000000004005C0                 public __libc_csu_init
.text:00000000004005C0 __libc_csu_init proc near               ; DATA XREF: _start+16o
.text:00000000004005C0                 push    r15
.text:00000000004005C2                 push    r14
.text:00000000004005C4                 mov     r15d, edi
.text:00000000004005C7                 push    r13
.text:00000000004005C9                 push    r12
.text:00000000004005CB                 lea     r12, __frame_dummy_init_array_entry
.text:00000000004005D2                 push    rbp
.text:00000000004005D3                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:00000000004005DA                 push    rbx
.text:00000000004005DB                 mov     r14, rsi
.text:00000000004005DE                 mov     r13, rdx
.text:00000000004005E1                 sub     rbp, r12
.text:00000000004005E4                 sub     rsp, 8
.text:00000000004005E8                 sar     rbp, 3
.text:00000000004005EC                 call    _init_proc
.text:00000000004005F1                 test    rbp, rbp
.text:00000000004005F4                 jz      short loc_400616
.text:00000000004005F6                 xor     ebx, ebx
.text:00000000004005F8                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400600
.text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54j
.text:0000000000400600                 mov     rdx, r13
.text:0000000000400603                 mov     rsi, r14
.text:0000000000400606                 mov     edi, r15d
.text:0000000000400609                 call    qword ptr [r12+rbx*8]
.text:000000000040060D                 add     rbx, 1
.text:0000000000400611                 cmp     rbx, rbp
.text:0000000000400614                 jnz     short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34j
.text:0000000000400616                 add     rsp, 8
.text:000000000040061A                 pop     rbx
.text:000000000040061B                 pop     rbp
.text:000000000040061C                 pop     r12
.text:000000000040061E                 pop     r13
.text:0000000000400620                 pop     r14
.text:0000000000400622                 pop     r15
.text:0000000000400624                 retn
.text:0000000000400624 __libc_csu_init endp

__libc_csu_init函数会调用get_pc_truck。它是给位置无关码使用的。设置它们可以让位置无关码正常工作。为了让它们工作,基址寄存器(%ebp)需要知道GLOBAL_OFFSET_TABLE(GOT)。该函数的主要目的其实是获取变量对应的GOT,以通过它获取变量真正的值。

之后会进入一个循环中,为main设置环境,和寄存器参数。下面的函数都在循环中执行

然后,我们来看gmon_start函数。如果它是空的,我们跳过它,不调用它。否则,调用它来设置profiling。该函数调用一个例程开始profiling,并且调用at_exit去调用另一个程序运行,并且在运行结束的时候生成gmon.out。

接下来frame_dummy函数会被调用。其目的是调用__register_frame_info函数,但是,调用frame_dummy是为了给上述函数设置参数。

之后是__do_global_ctors_aux

。。。。

最后跳出循环,回到__libc_start_main,__libc_start_main去调用我们的mian。

结束

进程正常结束有两种情况:

  1. main正常返回,由__libc_start_main来调用exit函数。
  2. 程序中直接使用exit退出。

__libc_csu_fini函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.text:0000000000402960 __libc_csu_fini proc near               ; DATA XREF: start+Fo
.text:0000000000402960 ; __unwind {
.text:0000000000402960                 push    rbp
.text:0000000000402961                 lea     rax, unk_4B4100
.text:0000000000402968                 lea     rbp, _fini_array_0
.text:000000000040296F                 push    rbx
.text:0000000000402970                 sub     rax, rbp
.text:0000000000402973                 sub     rsp, 8
.text:0000000000402977                 sar     rax, 3
.text:000000000040297B                 jz      short loc_402996
.text:000000000040297D                 lea     rbx, [rax-1]
.text:0000000000402981                 nop     dword ptr [rax+00000000h]
.text:0000000000402988
.text:0000000000402988 loc_402988:                             ; CODE XREF: __libc_csu_fini+34j
.text:0000000000402988                 call    qword ptr [rbp+rbx*8+0]
.text:000000000040298C                 sub     rbx, 1
.text:0000000000402990                 cmp     rbx, 0FFFFFFFFFFFFFFFFh
.text:0000000000402994                 jnz     short loc_402988
.text:0000000000402996
.text:0000000000402996 loc_402996:                             ; CODE XREF: __libc_csu_fini+1Bj
.text:0000000000402996                 add     rsp, 8
.text:000000000040299A                 pop     rbx
.text:000000000040299B                 pop     rbp
.text:000000000040299C                 jmp     sub_48E32C
.text:000000000040299C ; } // starts at 402960
.text:000000000040299C __libc_csu_fini endp

在.text:0000000000402988这个地方有一个call指令,结合前面的代码可以知道rbp保存的是fini_array的值,所以这里会调用fini_array中的函数.所以只要修改了fini_array的数值,我们就可以劫持eip.看一下fini_array的代码:

1
2
3
4
5
6
7
.fini_array:``00000000004B40F0` `_fini_array   segment para public ``'DATA'` `use64
.fini_array:``00000000004B40F0`         `assume cs:_fini_array
.fini_array:``00000000004B40F0`         `;org ``4B40F0h
.fini_array:``00000000004B40F0` `_fini_array_0  dq offset sub_401B00  ; DATA XREF: .text:``000000000040291Co
.fini_array:``00000000004B40F0`                     `; __libc_csu_fini``+``8o
.fini_array:``00000000004B40F8`         `dq offset sub_401580
.fini_array:``00000000004B40F8` `_fini_array   ends

这里保存了两个函数指针,分别是fini_array[0]和fini_array[1],观察libc_csu_fini中的汇编代码我们可以得知这俩函数指针是反向执行的,先执行fini_array[1],再执行fini_array[0].如果我们将fini_array[0]覆盖为libc_csu_fini的地址,再将fini_array[1]覆盖为任意一个地址A,那么程序就会循环执行A地址的代码,直到fini_array[0]覆盖为其他值.

其次,在.text:0000000000402968可以修改rbp为fini_array的首地址,配合leave;ret可以把栈迁移到fini_array.

参考:__libc_start_main