作者: wzt
原文鏈接:https://mp.weixin.qq.com/s/8g0Hws3eRN8Vp_mzglbmJA
1 簡介
今天繼續分析下Freebsd進程的棧、堆、代碼段的地址隨機化實現。
1.1 不可思議的棧隨機化
可能讀者朋友會比較詫異,freebsd內核沒有提供進程棧的地址隨機化功能。 進程棧的地址是execve加載磁盤上的二進制文件時初始化的:
kern/kern_exec.c:
kern_execve()->do_execve()->exec_copyout_strings()
register_t *
exec_copyout_strings(struct image_params *imgp)
{
arginfo = (struct ps_strings *)p->p_sysent->sv_psstrings;[1]
destp = (uintptr_t)arginfo;[2]
}
exec_copyout_strings用來拷貝當前進程的二進制信息,這些信息會被動態連接器使用。
[1] 處p->p_sysent->sv_psstrings保存的是當前進程的二進制信息,它位于棧基地址的附近。
看下init進程的sysentvec信息:
struct sysentvec null_sysvec = {
.sv_usrstack = USRSTACK,
.sv_psstrings = PS_STRINGS,
}
#define PS_STRINGS (USRSTACK - sizeof(struct ps_strings))
sv_psstrings緊挨著棧的開始地址。
amd64/include/vmparam.h
#define VM_MAXUSER_ADDRESS UVADDR(NUPML4E, 0, 0, 0)
#define SHAREDPAGE (VM_MAXUSER_ADDRESS - PAGE_SIZE)
#define USRSTACK SHAREDPAGE
以amd64架構為例,USRSTACK為進程空間最大的用戶態地址減去一個PAGE_SIZE。 通過代碼路徑的溯源,可以看到freebsd的內核并沒有對棧的地址有隨機化的動作!
1.2 BRK地址隨機化
Libc的brk函數用來控制進程的heap大小,但是從內核源碼來看, freebsd并沒有提供brk的系統調用。
vm/vm_mmap.c:
int
sys_sbrk(struct thread *td, struct sbrk_args *uap)
{
/* Not yet implemented */
return (EOPNOTSUPP);
}
事實上,freebsd的進程空間結構與linux的有所不同, 內核對進程空間的管理并沒有明確的brk和mmap概念,linux的mm_struct結構體會有brk和mmap的開始地址標記。
include/linux/mm_types.h:
struct mm_struct {
unsigned long mmap_base;// mmap區域的基地址
unsigned long start_brk, brk, start_stack;// brk區域的及地址
}
Linux對brk和mmap區域都有明顯的界限劃分,并且都提供了它們的地址隨機化能力。
在來看下freebsd的定義:
vm/vm_map.h:
struct vmspace {
caddr_t vm_taddr; // 代碼段基地址
caddr_t vm_daddr;// 數據段基地址
}
Freebsd的進程空間區域只包含代碼段和數據段, 動態生成的heap區域就在data段的后面。雖然libc庫有包裝了brk,但是內核沒有提供此架構與功能。用戶態的內存分配器比如jemalloc,它會優先選擇使用mmap來分配內存。
1.3 mmap地址隨機化
接下來繼續分析mmap的地址隨機化實現, 我們以mmap建立一個匿名映射的路徑來分析:
vm/vm_mmap.c:
sys_mmap()->kern_mmap()->vm_mmap_object():
int
vm_mmap_object(vm_map_t map, vm_offset_t *addr, vm_size_t size, vm_prot_t prot,
vm_prot_t maxprot, int flags, vm_object_t object, vm_ooffset_t foff,
boolean_t writecounted, struct thread *td)
{
if (curmap) {
rv = vm_map_find_min(map, object, foff, addr, size,[1]
round_page((vm_offset_t)td->td_proc->p_vmspace->
vm_daddr + lim_max(td, RLIMIT_DATA)), max_addr,[2]
findspace, prot, maxprot, docow);
}
[1] 處用來尋找進程空間是否存在一個合適的地址范圍,注意看[2]處的參數為地址范圍的最小值,它被設置為vm_daddr + lim_max(td, RLIMIT_DATA)), 也就是數據段的最后地址,所以說freebsd的mmap是從緊挨著數據段后面開始的。
vm_map_find_min()->vm_map_find():
int
vm_map_find(vm_map_t map, vm_object_t object, vm_ooffset_t offset,
vm_offset_t *addr, /* IN/OUT */
vm_size_t length, vm_offset_t max_addr, int find_space,
vm_prot_t prot, vm_prot_t max, int cow)
{
if (try == 1 && en_aslr && !cluster) {
pidx = MAXPAGESIZES > 1 && pagesizes[1] != 0 &&[1]
(find_space == VMFS_SUPER_SPACE || find_space ==
VMFS_OPTIMAL_SPACE) ? 1 : 0;
gap = vm_map_max(map) > MAP_32BIT_MAX_ADDR &&[2]
(max_addr == 0 || max_addr > MAP_32BIT_MAX_ADDR) ?
aslr_pages_rnd_64[pidx] : aslr_pages_rnd_32[pidx];
if (vm_map_findspace(map, curr_min_addr, length +
gap * pagesizes[pidx], addr) ||
(max_addr != 0 && *addr + length > max_addr))
goto again;
/* And randomize the start address. */
*addr += (arc4random() % gap) * pagesizes[pidx];[3]
}
[1]處Pidx用來選擇page size的小大, [2]處gap用來選擇隨機化的page數目。[3]處通過arc4random()生成一個隨機數, 并計算最后的隨機地址。以amd64架構為例:
static const int aslr_pages_rnd_64[2] = {0x1000, 0x10};
u_long pagesizes[MAXPAGESIZES] = { PAGE_SIZE };
可以看到mmap的最大地址隨機范圍不超過:0x1000*4096=0x1000000, 也就10M的地址范圍,隨機范圍并不大。
1.4 代碼段地址隨機化
代碼段指的是text代碼段,包含共享庫,它們的基地址,freebsd都做了隨機化的能力。
kern/imgact_elf.c:
static int
__CONCAT(exec_, __elfN(imgact))(struct image_params *imgp)
{
if (hdr->e_type == ET_DYN) {
if (baddr == 0) {
if ((sv->sv_flags & SV_ASLR) == 0 ||
(fctl0 & NT_FREEBSD_FCTL_ASLR_DISABLE) != 0)
et_dyn_addr = ET_DYN_LOAD_ADDR;
else if ((__elfN(pie_aslr_enabled) &&
(imgp->proc->p_flag2 & P2_ASLR_DISABLE) == 0) ||
(imgp->proc->p_flag2 & P2_ASLR_ENABLE) != 0)
et_dyn_addr = ET_DYN_ADDR_RAND;
else
et_dyn_addr = ET_DYN_LOAD_ADDR;
}
}
對于使用pie編譯為地址無關代碼的程序,或者共享庫文件,elf文件頭都設置為ET_DYN, elf的第一個load段內存地址都設置為0。如果沒有開啟地址隨機化,那么et_dyn_addr被設置為0x01021000(64位)。如果設置隨機化,則執行如下路徑:
maxv = vm_map_max(map) - lim_max(td, RLIMIT_STACK);
if (et_dyn_addr == ET_DYN_ADDR_RAND) {
et_dyn_addr = __CONCAT(rnd_, __elfN(base))(map,
vm_map_min(map) + mapsz + lim_max(td, RLIMIT_DATA),
/* reserve half of the address space to interpreter */
maxv / 2, 1UL << flsl(maxalign));
}
rnd函數從某個地址范圍隨機選取一個滿足條件的地址區域。它的最小地址設置為數據段的開始地址,最大地址為棧的下限地址。
static u_long
__CONCAT(rnd_, __elfN(base))(vm_map_t map __unused, u_long minv, u_long maxv,
u_int align)
{
u_long rbase, res;
arc4rand(&rbase, sizeof(rbase), 0);
res = roundup(minv, (u_long)align) + rbase % (maxv - minv);
res &= ~((u_long)align - 1);
if (res >= maxv)
res -= align;
}
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1622/
暫無評論