main函数返回后(After main return)

main函数的return

我们平时写代码的时候可能并没有注意到缓冲区的问题,但是我们的printf还是输出了

举个简单例子

#include <stdio.h>
#include <stdlib.h>
int main() {
printf("hello");
sleep(3);
printf("world");
sleep(3);
return 0;
}

执行起来会发现第一个printf在执行完后并没有立刻输出,第二个printf也没有立即输出,而是在6s后一块输出,我们经过前面的IO FILE分析都知道IO是有缓冲区的,但是我们并没有刷新缓冲区输出到屏幕,return后做了什么操作吗,一切答案都在source code里

当我们运行一个动态链接的程序时,执行的第一条指令是动态链接器ld去做动态链接也就是ld-linux-x86-64.so加载libc,在Unix-like操作系统中,_start符号被作为程序入口点的默认值。

_start函数调用系统启动函数__libc_start_main, 该函数定义在 libc.so 中。它初始化执行环境,调用用户层的 main 函数,处理 main 函数的返回值,并且在需要的时候把控制返回给内核。

int
__libc_start_main (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
int argc, char **argv,
__typeof (main) init,
void (*fini) (void),
void (*rtld_fini) (void), void *stack_end)
{
init_cpu_features (&_dl_x86_cpu_features);
return generic_start_main (main, argc, argv, init, fini, rtld_fini,
stack_end);
}

generic_start_main里调用main最后会执行exit,做用户层和内核层的释放工作

#else
/* Nothing fancy, just call the function. */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
#endif

exit (result);

_start hacking

Compiler

在Unix-like操作系统中,_start符号被作为程序入口点的默认值。

我们是不是可不可以不用这个默认值呢

#include <stdio.h>
int my_main() {
printf("This is a program without a main() function! printf");
return 0;
}

gcc -nostartfiles -e my_main -g -o test test.c

  • -nostartfiles:Do not use the standard system startup files when linking. The standard system libraries are used normally, unless -nostdlib, -nolibc, or -nodefaultlibs is used.
  • -e:Specify that the program entry point is entry. The argument is interpreted by the linker; the GNU linker accepts either a symbol name or an address.
grxer@grxer /m/h/S/c/p/io_file> gcc -nostartfiles -e my_main -g -o test test.c
grxer@grxer /m/h/S/c/p/io_file> ./test
fish: “./test” terminated by signal SIGSEGV (Address boundary error)

直接终止了,没有输出

gdb看下,发现printf函数执行完了,ret时程序返回非法地址崩溃,没有输出是因为没有刷新缓冲区

image-20230403005428232

#include <stdio.h>
int my_main() {
puts("This is a program without a main() function! printf");
return 0;
}
grxer@grxer /m/h/S/c/p/io_file> gcc -nostartfiles -e my_main -g -o test test.c
grxer@grxer /m/h/S/c/p/io_file> ./test
This is a program without a main() function! puts
fish: “./test” terminated by signal SIGSEGV (Address boundary error)

改为puts,输出了,因为puts是行缓存属性,所有输出了

grxer@grxer /m/h/S/c/p/io_file> objdump -d test

test: file format elf64-x86-64


Disassembly of section .plt:

0000000000400320 <puts@plt-0x10>:
400320: ff 35 e2 0c 20 00 pushq 0x200ce2(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
400326: ff 25 e4 0c 20 00 jmpq *0x200ce4(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
40032c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000400330 <puts@plt>:
400330: ff 25 e2 0c 20 00 jmpq *0x200ce2(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400336: 68 00 00 00 00 pushq $0x0
40033b: e9 e0 ff ff ff jmpq 400320 <puts@plt-0x10>

Disassembly of section .text:

0000000000400340 <my_main>:
400340: 55 push %rbp
400341: 48 89 e5 mov %rsp,%rbp
400344: bf 58 03 40 00 mov $0x400358,%edi
400349: e8 e2 ff ff ff callq 400330 <puts@plt>
40034e: 90 nop
40034f: 5d pop %rbp
400350: c3 retq

看下汇编也是retq不合法

#include <stdio.h>
int my_main() {
printf("This is a program without a main() function! printf");
exit(0);
}

如我们不用return返回,而是直接调用exit终止应该就可以正常刷新缓冲区,并退出

grxer@grxer /m/h/S/c/p/io_file> gcc -nostartfiles -e my_main -g -o test test.c
grxer@grxer /m/h/S/c/p/io_file> ./test
This is a program without a main() function! printf

printf也被刷新缓冲区输出了,正常退出

我们也可以利用默认_start入口点去做

#include <stdio.h>
#include <stdlib.h>
void _start() {
int ret = my_main();
exit(ret);
}
int my_main() {

puts("This is a program without a main() function!");
}
//gcc -nostartfiles -g -o test test.c
grxer@grxer /m/h/S/c/p/io_file> gcc -nostartfiles -g -o test test.c
test.c: In function ‘_start’:
test.c:4:13: warning: implicit declaration of function ‘my_main’ [-Wimplicit-function-declaration]
int ret = my_main();
^
grxer@grxer /m/h/S/c/p/io_file> ./test
This is a program without a main() function!

ELF

ELF Header里的 Elf32_Addr e_entry; /* 程序入口地址 */规定了程序的入口点,我们可以改掉这个入口点来做hacking