資訊人筆記

Work hard, Have fun, Make history!

使用者工具

網站工具


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 dependencies
  • nm command list symbols from object files
  • strip 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