講師:ItisCaleb
為 Executable and Linkable Format 的縮寫
是 Linux 的可執行程式的格式
一個 ELF 可以分成
ELF header 會包括一些 metadata 之類的東西
segment 則是包括實際的資料本身
常見的區段
可使用 readelf 指令來觀察
readelf -a <your elf>
loader 的其中一個作用就是將檔案中的 segments 放到記憶體中
text |
data |
bss |
heap |
free space |
free space |
stack |
environments |
low address
high address
全名為 Portable Executable
為 Windows 的程式所使用的檔案格式
一般常見的 .exe 皆是 PE 格式
與 ELF 類似,同樣擁有 header 及 sections
不過多了非常多的 header 跟 section
Linux 的程式若要與作業系統交互(檔案系統、網路、執行程式)便需要使用 system call
Linux 會透過暫存器的內容來呼叫不同的 system call
mov rax, 0x3c
mov rdi, 0
syscall //exit(0)
Linux 與作業系統交互是使用 system call
而 Windows 則是使用 Windows API
不知道某個 syscall 或是 WindowsAPI 在幹嘛?
在函式呼叫的時候,編譯器會根據不同的指令集架構將參數放到不同的暫存器或是 stack 上面
// x86-64
#include <stdio.h>
int sum(int a,int b){
return a+b;
}
int main(){
int a,b;
a = 5;
b = 4;
printf("%d",sum(a,b));
return 0;
}
sum:
endbr64
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], edi
mov DWORD PTR -8[rbp], esi
mov edx, DWORD PTR -4[rbp]
mov eax, DWORD PTR -8[rbp]
add eax, edx
pop rbp
ret
程式在執行時,會使用 stack frame 來存放區域變數
每次呼叫函式時,會在記憶體中劃分新的 stack frame,並將區域變數放置其中
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
pop rbp
ret
call func = push rip; jmp func;
leave = mov rsp,rbp; pop rbp;
ret = pop rip
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rsp
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rsp
rbp 初始值
0x7fffffffe3f0
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rsp rbp
rbp 初始值
0x7fffffffe3f0
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rbp
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rbp
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
0x55555555518a
call func = push rip
jmp func
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rbp
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
0x55555555518a
0x7fffffffe3d8
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
Stack
0x7fffffffe3f8
rbp
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
0x55555555518a
0x7fffffffe3d8
0x7fffffffe3d0
0x7fffffffe3f0 (saved rbp)
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
rsp rbp
0x7fffffffe3e0
main stack frame
0x55555555518a
0x7fffffffe3d8
0x7fffffffe3d0
0x7fffffffe3f0 (saved rbp)
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
0x55555555518a
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
0x7fffffffe3d8
0x7fffffffe3f0 (saved rbp)
0x7fffffffe3d0
rbp
0x7fffffffe3c0
sum stack frame
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
rsp
0x7fffffffe3e0
main stack frame
0x55555555518a
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
0x7fffffffe3d8
0x7fffffffe3f0 (saved rbp)
0x7fffffffe3d0
rbp
0x7fffffffe3c0
sum stack frame
leave = mov rsp,rbp
pop rbp
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
0x7fffffffe3e0
main stack frame
0x55555555518a
<sum>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
leave
ret
0x7fffffffe3d8
rsp
ret = pop rip
rbp
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
0x7fffffffe3e0
main stack frame
rsp
rbp
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rbp 初始值
0x7fffffffe3f0
0x7fffffffe3e0
main stack frame
rsp
rbp
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
Stack
0x7fffffffe3f8
rsp
<main>:
push rbp
mov rbp,rsp
sub rsp,0x10
...
call 1149 <sum>
...
leave
ret
一般來說資料存放的方式有兩種,Big endian 及 Little endian
7f | ff | ff | ab |
---|
0x7fffffab (4 bytes)
ab | ff | ff | 7f |
---|
Big Endian
Little Endian
雖然 Big endian 比較容易閱讀,但 Little endian 在程式中比較常見
它可以使程式在取低位時不需要改變記憶體位置
Big endian 則是會出現在網路傳輸之中
ab | ff | ff | 7f |
---|
int a = 0x7fffffab
short b = (short) 0x7fffffab
ab | ff | ff | 7f |
---|
struct list{
struct list *prev, *next;
int value;
};
prev | next |
value |
0x7fffffff0000
0x7fffffff0010
0
3
7
b
7
f
f
7
0
8 bytes
4 bytes
not used |
expected size: 16 + 4 = 20 bytes
actual size: 24 bytes
struct list{
struct list *prev, *next;
int value;
};
expected size: 16 + 4 = 20 bytes
actual size: 24 bytes
(gdb) b main
(gdb) r
(gdb) print sizeof(struct list)
$1 = 24