course:nctu-高等unix程式設計:chapter5
目錄表
Process Environment
0x00 Outline
- Process start and termination
- Environment variables
- Memory layout
- Shared libraries
- Memory allocation
- setjump and longjump
- Process resource limit
0x01 Process start and termination
Process Start
int main (int argc, char *argv[]) int main (int argc, char *argv[], char *envp[])
- argc 為 argument count,紀錄參數個數,程式名稱本身為第一個參數
- argv 為 argument vector,會包含各個參數,最後一項是NULL
- envp 為 environment variables,包含此程式的環境變數,最後一項也是NULL
Process Termination
- 正常結束
- Return from main
- Calling exit
- Calling _exit or _Exit
- Return of the last thread from its start routine(程序的最後一個執行緒返回)
- Calling pthread_exit from the last thread
- 異常結束
- Calling abort
- Receipt of a signal
- Response of the last thread to a cancellation request(最後一個執行緒回應取消請求)
atexit and exit Function
int atexit(void (*function)(void)); void exit(int status);
- atexit
- 註冊自定義的 function,再呼叫 exit 前會先執行被註冊的 function
- 最多可註冊 32 個 function,不過 Linux 有將上限再提高
- 註冊的 function 是採 stack 機制,後註冊的先執行
- exit
- 呼叫 atexit 中被註冊的 function
- 清理關閉 standard I/O library
- fclose() all stream,remove tmpfile()
- _exit and _Exit
- 不做其他處理直接結束,I/O 的 buffer 不會被 flush 就會中斷
/* atexit example */ #include <stdio.h> /* puts */ #include <stdlib.h> /* atexit */ void fnExit1 (void) { puts ("Exit function 1."); } void fnExit2 (void) { puts ("Exit function 2."); } int main () { atexit (fnExit1); atexit (fnExit2); puts ("Main function."); return 0; } /* result: Main function. Exit function 2. Exit function 1. */
0x02 Environment Variables
Environment Variables
- 一般格式為 name=value
- 相關指令有 env、export
- 使用
$
從 shell 讀取環境變數
Environment List
- 直接存取環境變數的方法
- int main(int argc, char *argv[], char *envp[]);
- extern char **environ;
- environ is defined as a global variable in the Glibc source file posix/environ.c
extern char **environ; int main(int argc, char *argv[]) { int count = 0; printf("\n"); while(environ[count] != NULL) { printf("[%s] :: ", environ[count]); count++; } return 0; }
Environment Function
#include <stdlib.h> char *getenv(const char *name); int putenv(char *string); int setenv(const char *name, const char *value, int overwrite); int unsetenv(const char *name); int clearenv(void);
- getenv 給環境變數名稱,取得值,error on NULL
- putenv 參數 string 格式為 name=value ,如果環境變數名稱不存在,則新建並加入環境變數中,若變數已存在,則修改變數值。return nonzero if OK,zero on error
- setenv 基本上跟 putenv 功能一樣,不過參數 overwrite nonzero 時會覆寫數值,zero 時則不會
- unsetenv 從環境變數中移除指定名稱的變數
- clearenv 清除環境變數中所有的 name-value pairs,並將 global variable environ 設為 NULL
Environment List Operations
- 刪除變數
- 將 list 中該 string 指標指向的空間釋放,其他指標往後指一個
- 修改變數
- 如果新值佔用空間不大於舊值,直接覆寫舊的
- 如果新值佔用空間大於舊值,重新分配一塊空間給新值,並將指標指向新值
- 新增變數
- 分配一塊更大的空間給整個 list,將舊資料搬移
0x03 Memory layout
- Text segment
- 也稱 Code Segment,包含程式碼本身以及可以執行的指令
- 放在 Memory 中 low level address 的位置,避免被 stack/heap overwrite
- 此區塊通常是 shareable,例如 shared libraries 會共用一份 text
- Read-only,避免程式意外修改到當中的 Instruction
- Initialized data segment
- 此區用於存放 Programmer 自己定義且已經初始化的 static or global variables
- 又可再細分為 Read-only 和 Read-Write Segment,Read-only Segment 存放帶有 const 宣告的變數,一般變數則很可能在執行期間改變,存放在 Read-Write Segment
- Uninitialized data segment (bss,block started by symbol)
- 此區用於存放 Programmer 自己定義且未初始化或初始化為零的 static or global variables
- 程式開始執行前,kernel 會將這些未初始化的變數初始化為零
- Stack
- 一般和 heap 對向成長,當 stack 頂端和 heap 頂端碰頭時即為記憶體枯竭
- function 中的 local variables 存放於此
- 是一個 LIFO structure,記錄function的堆疊狀況,比如 caller's address,讓 function 結束後可以順利 return。每個 function 都有自己的堆疊空間,不同 function 的變數不會互相干擾
- Heap
- heap 是個動態分配空間的記憶體區塊,開始於 bss 結束之處,往記憶體高位址成長
- 可透過 malloc、realloc and free 管理
- 系統可透過 brk(2)、sbrk(2) 調整區塊大小
- 一個 process 中的所有 shared libraries 和 dynamically loaded modules 是共用 heap 空間的
- 可以使用 size(1) 指令查看執行檔的各 memory size 分配
0x04 Shared libraries
shared libraries
- 現今多數 UNIX 支援 shared libraries
- shared libraries 從執行檔中移出了 commom library
- 在記憶體維護一份 library routine 的副本,讓所有 process 可以參照,可以減少每個執行檔對記憶體的需求,但可能會產生一些 runtime overhead
- shared libraries function 可以被攔截取代,但不需要 relink 每個執行檔,然而這也帶來一些安全疑慮
- gcc 編譯時可加上 -static 參數,可明顯見到將所有 libraries 在編譯時直接涵蓋進來,編出來的執行檔會較大
Library Injection
- Functions referenced to shared libraries can be overridden
- 使用 LD_PRELOAD 這個環境變數
- Library injection does not work suid/sgid executables
- 使用 “gcc -o inject1.so -shared -fPIC inject1.c -ldl” 來將自己寫的 inject1.c 編譯成 shared library
$ LD_PRELOAD=./inject1.so ./getuid
即可看見原來的 getuid() function 被 inject1 攔截取代
//getuid.c int main() { printf("UID = %d\n", getuid()); return 0; }
//injected library, inject1.c #include <stdio.h> #include <sys/types.h> uid_t getuid(void) { fprintf(stderr, "injected getuid, always return 0\n"); return 0; }
More on Library Inject
- 在我們 library inject 後還是會希望原來的 library 正常運作,有些 function可以幫助我們達到這個目的
- 使用這些 function 在 gcc 編譯時要加上 -ldl 來連結
#include <dlfcn.h> void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle);
- dlopen() 第一個參數為 library 名稱,ex:libc.so.6
- dlopen() 第二參數 flag 包含
- RTLD_LAZY
- RTLD_NOW
- RTLD_GLOBAL
- RTLD_LOCAL
- RTLD_NODELETE
- RTLD_NOLOAD
- RTLD_DEEPBIND
- 如果同個 library 被 load 超過一次,則會直接回傳相同的 handle
- dlopen() 回傳一個 handle,可用於 dlsym,error on NULL
- dlerror() 回傳最近一次關於 dlopen()、dlsym() 或 dlclose() 遭遇的錯誤,by human readable string
- dlsym() 第一個參數是 dlopen() 回傳的一個 dynamic library handle
- dlsym() 第二個參數數 null-terminated symbol name,也就是被我們攔截覆寫,而我們要呼叫的原始指令
- dlsym() returning the address where that symbol is loaded into memory,所以可以用一個 function pointer 儲存,之後可以調用
- dlclose() 會減少 dynamic library handle 上的 reference count,當減至零,且沒有其他 library 使用其中的 symbol 時,dynamic library 會被 unload
- dlclose() return 0,error on nonzero
/* inject2.c gcc -o inject2.so -shared -fPIC inject2.c -ldl */ static uid_t (*old_getuid)(void) = NULL; /* function pointer */ uid_t getuid(void) { if(old_getuid == NULL) { void *handle = dlopen("libc.so.6", RTLD_LAZY); if(handle != NULL) old_getuid = dlsym(handle, "getuid"); } fprintf(stderr, "injected getuid, always return 0\n"); if(old_getuid != NULL) fprintf(stderr, "real uid = %d\n", old_getuid()); return 0; }
Determine Library Injection Possibility
- No SUID/SGID enabled
- Not a statically linked binary,library 若編譯時用 -static 直接靜態編入也無法 inject
ldd
command print shared object dependenciesnm
command list symbols from object filesstrip
command discard symbols from object files.
0x05 Memory allocation
memory allocation functions
#include <stdlib.h> void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size); #include <alloca.h> void *alloca(size_t size);
- malloc() function allocates size bytes and returns a pointer to the allocated memory
- malloc() memory is not initialized,if size is 0,return NULL
- free() function frees the memory space pointed to by ptr,which must have been returned by a previous call tomalloc()、calloc() or realloc()
- calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory
- realloc() function changes the size of the memory block pointed to by ptr to size bytes
- allocation 會改變 memory heap 大小,常會以 system call sbrk(2) 處理,但實際上 free space 常保存在 malloc pool,而沒有回到 kernel
- alloca 把空間配置在 caller 的 stack frame 中,所以 programmer 不需要 free() function,在 呼叫 alloca() 的 function return 時,空間會自動被釋放
0x06 Setjmp and longjmp
setjmp and longjmp Function
- C 語言中 goto 這個保留字只能用在同個 function 中,無法跳到其他 function 中的 label
- 為達到跳躍至不同 function 這個目的,我們可以使用 setjmp() 和 longjmp()
int setjmp(jmp_buf env); void longjmp(jmp_buf env, int val);
- setjmp() save stack context for nonlocal goto
- setjmp() 和 longjmp 可以用來處理函式呼叫後遭遇到 error 或 interrupts
- setjmp() 將 stack context/environment 儲存在 env 中,之後可供 longjmp() 使用
- setjmp() return 0 if called directly, nonzero if returning from a call to longjmp
- longjmp() restores the environment saved by the last call of setjmp(3) with the corresponding env argument
- longjmp() 被呼叫後,程式回到前面 setjmp() 狀態與環境,longjmp() 的第二個參數 val 即是 回到前面 setjmp() 後,setjmp() 的 return value,不為 0 (即使 programmer 把 val 設為 0,longjmp() 會自動回傳 1)
Restoration of Variables
- C 語言中的變數種類:
- automatic : [auto] int autoVal; 一般宣告預設是這種
- register : register int regVal; 可能會存放在 register 中
- vloatile : volatile int volVal; 存放在 memory
- setjmp() 和 longjmp() 時變數的情況:
- auto variables 不一定,看編譯器決定,gcc 加上 -o 參數做最佳化之後會恢復到 setjmp() 被呼叫時的變數值
- register variables 會恢復到 setjmp() 被呼叫時的變數值
- volatile variables 則會保持在 longjmp() 被呼叫時的變數值
0x07 Process resource limits
Process resource limits
- 每個 process 都有設置資源限制
- 通常 resource limit 由 parents process 繼承
getrlimit and setrlimit Function
#include <sys/time.h> #include <sys/resource.h> int getrlimit(int resource, struct rlimit *rlim); int setrlimit(int resource, const struct rlimit *rlim); struct rlimit { rlim_t rlim_cur; /* Soft limit */ rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */ };
0x08 參考資料
course/nctu-高等unix程式設計/chapter5.txt · 上一次變更: 由 127.0.0.1