elf-03 装作操作系统来构建栈吧?
The System V Application Binary Interface is a set of specifications that detail calling conventions, object file formats, executable file formats, dynamic linking semantics, and much more for systems that complies with the X/Open Common Application Environment Specification and the System V Interface Definition. It is today the standard ABI used by the major Unix operating systems such as Linux, the BSD systems, and many others. The Executable and Linkable Format (ELF) is part of the System V ABI. -- wiki.osdev.org
System V 应用程序二进制接口是一套规范, 详细说明了符合 X/Open 通用应用环境规范和 System V 接口定义的系统上的 调用约定, 目标文件格式, 可执行文件格式, 动态链接语义以及其他诸多内容. 如今, 它是 Linux, BSD 系统以及许多其他主流 Unix 操作系统所使用的标准 ABI. ELF 是 System V ABI 的一部分
平常我们使用的 寄存器, rdi rsi rdx rcx 代表 第 1, 2, 3, 4 个参数... 以及 syscall 传参约定, caller/callee 保存寄存器的规则, 是 SysV ABI (AMD64 ver.) 规定的
以及 动态链接器 (ldso) 的 部分加载规则 / 重定位规则:
layout: center
栈的初始状态.
zoom: 0.7
layout: default
Figure 3.9: Initial Process Stack
| Purpose |
Start Address |
Length |
| Unspecified |
High Addresses |
|
Information block, including argument strings, environment strings, auxiliary information ... |
varies |
Unspecified |
| Null auxiliary vector entry |
|
1 eightbyte |
| Auxiliary vector entries ... |
|
2 eightbytes each |
| 0 |
|
eightbyte |
| Environment pointers ... |
|
1 eightbyte each |
| 0 |
8+8*argc+%rsp |
eightbyte |
| Argument pointers |
8+%rsp |
argc eightbytes |
| Argument count |
%rsp |
eightbyte |
| Undefined |
Low Addresses |
|
Argument strings, environment strings, and the auxiliary information appear in no specific order within the information block and they need not be compactly allocated.
Only the registers listed below have specified values at process entry:
%rbp — The content of this register is unspecified at process initialization time, but the user code should mark the deepest stack frame by setting the frame pointer to zero.
%rsp — The stack pointer holds the address of the byte with lowest address which is part of the stack. It is guaranteed to be 16-byte aligned at process entry.
%rdx — a function pointer that the application should register with atexit (BA_OS).
layout: two-cols
To put it in a nutshell

::right::
````md magic-move
| int main(void);
int main(int argc, char *argv[]);
|
| int main(void);
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char *envp[]);
|
| int main(void);
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char *envp[]);
unsigned long getauxval(unsigned long type);
|
| ***
```c
// long *sp;
long argc = sp[0];
char **argv = (void *)&sp[1];
char **envp = (void *)&sp[1 + argc + 1];
```
因为是 `NUL-terminated`
```c
const void **auxv = (const void **)envp;
while (*auxv++);
```
---
<v-clicks>
构造栈之前想想用什么区域来作为栈吧!
栈是一块 可读可写 的内存区域
```c
long stack[stack_size];
```
```c
long *stack = malloc(stack_size);
```
如果使用现有的栈呢?
这就是一个比较 HACK 的方式了
</v-clicks>
---
## 使用现有栈作为栈
<v-clicks>
libc 的启动方式:
````md magic-move
```c
-> _start (`crt1.o`)
-> __libc_start_xxx
```c
-> _start (`crt1.o`)
-> __libc_start_xxx (`xxx.so`)
```
```c
-> _start (`crt1.o`) -> 最先接触到 sp
-> __libc_start_xxx (`xxx.so`)
```
|
那就劫持 _start!
| -e entry
--entry=entry
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.
|
| void *initial_sp;
// -Wl,-e,mystart
// do hijack
asm(" .text\n"
" .globl mystart\n"
"mystart:\n"
" movq %rsp, initial_sp(%rip)\n"
" jmp _start\n");
|
| int main(int argc, char *argv[]) {
printf("initial_sp = %p\n", initial_sp);
printf(" argc = %ld\n", *cast(long *, initial_sp));
printf(" prog = %s\n", *cast(char **, cast(long *, initial_sp) + 1));
...
}
|
| $ ./elf.out asm.out
initial_sp = 0x7ffdda605680
argc = 2
prog = ./elf.out
|
栈对齐
更新 auxv
````md magic-move
| // auxv overlay
long auxv_ov[] = {
[AT_PHDR] = cast(long, pmapself),
[AT_PHENT] = ehdr->e_phentsize,
[AT_PHNUM] = ehdr->e_phnum,
[AT_FLAGS] = 0,
[AT_BASE] = cast(long, virt),
[AT_ENTRY] = cast(long, start),
[AT_NOTELF] = 0,
[AT_EXECFN] = cast(long, argv[1]),
};
|
| // auxv overlay
long auxv_ov[] = {
[AT_PHDR] = cast(long, pmapself), // program header 地址
[AT_PHENT] = ehdr->e_phentsize, //
[AT_PHNUM] = ehdr->e_phnum, //
[AT_FLAGS] = 0, // 自己看
[AT_BASE] = cast(long, virt), // elf 映射基地址
[AT_ENTRY] = cast(long, start), // elf 入口地址
[AT_NOTELF] = 0, // elf 而不是 a.out
[AT_EXECFN] = cast(long, argv[1]),// elf filename (丢给 execve 的路径)
};
|
| ### program header 地址
> 主要给 dynamic linker (ldso) 使用, 简而言之:
>
> OS 加载 一个可执行程序 的时候, 将 ldso 与 elf 一起加载进内存, 之后跳转到 ldso 入口.
>
> ldso 可以不用再次读取 elf, 而是依照 auxv 来 重定位/动态链接
---
### 但是
`[AT_PHDR]` 本质是一个指针. 指向 何处?
<v-switch>
<template #1-3>
- 许多 elf 都会将其 `Ehdr` 与 `Phdr` 放入 `PT_LOAD`
````md magic-move {at: '2'}
```c
void *pmapself = 0;
for (int i = 0; i < ehdr->e_phnum; i++) {
Elf64_Phdr *phdr = cast(Elf64_Phdr *, phdrs + i * ehdr->e_phentsize);
if (phdr->p_type != PT_LOAD)
continue;
...
}
```
```c
void *pmapself = 0;
for (int i = 0; i < ehdr->e_phnum; i++) {
Elf64_Phdr *phdr = cast(Elf64_Phdr *, phdrs + i * ehdr->e_phentsize);
if (phdr->p_type != PT_LOAD)
continue;
// the phdr includes phdr itself, set pmapself
if (phdr->p_offset <= ehdr->e_phoff &&
phdr->p_offset + phdr->p_filesz >=
ehdr->e_phoff + ehdr->e_phentsize * ehdr->e_phnum)
pmapself = virt + phdr->p_vaddr // va
+ ehdr->e_phoff - phdr->p_offset; // distance to seg start
...
}
```
|
| if (pmapself == 0) {
size_t off = align_dn(ehdr->e_phoff, PAGE_SIZE);
size_t end = align_up(off + ehdr->e_phentsize * ehdr->e_phnum, PAGE_SIZE);
size_t sz = end - off;
pmapself = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, off);
assert(pmapself != MAP_FAILED);
printf("external AT_PHDR used = %p\n", pmapself);
} else {
printf("internal AT_PHDR used = %p\n", pmapself);
}
|
老生常谈的 .bss
-
未写入 elf 但是必须初始化为 0 的数据部分
-
.bss 可读可写
-
.bss 体现在 文件中大小与内存中大小的 差值
md magic-move
| if (prot & PROT_WRITE) {
...
}
|
| if (prot & PROT_WRITE) {
// clear bss
void *bss = virt + phdr->p_vaddr + phdr->p_filesz;
size_t bss_size = virt + mend - bss;
printf(" clearing bss %p - %p\n", bss, bss + bss_size);
memset(bss, 0, bss_size);
}
|
| if (prot & PROT_WRITE) {
if (msize > fsize) {
// create remained mapping for zero-padded pages
size_t exsize = msize - fsize;
void *expages = mmap(exp + fsize, exsize, prot,
MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0);
assert(expages != MAP_FAILED);
}
// clear bss
void *bss = virt + phdr->p_vaddr + phdr->p_filesz;
size_t bss_size = virt + mend - bss;
printf(" clearing bss %p - %p\n", bss, bss + bss_size);
memset(bss, 0, bss_size);
}
|
效果
- can load some simple static glibc-compiled programs
- use
vecs to test it
- static-pie
lua (dev version v5.5)
make MYCFLAGS="-DLUA_USE_POSIX" MYLIBS="-ldl" MYLDFLAGS="-static-pie" clean all
layout: center
Thanks for watching!