作答表單:
測驗 1
考慮一個運用 Linux 核心記憶體管理 和 CS:APP 第 9 章 提到的 mmap 系統呼叫,來實作類似 C++ std::vector 的機制,並自動管理記憶體,程式碼列表如下:
#include <asm-generic/param.h>
#define PAGESIZE EXEC_PAGESIZE
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#define N_VM_ELEMENTS (PAGESIZE / 16)
void __attribute__((noipa, cold, naked)) opt_fence(void *p, ...) {}
#define _optjmp(a, b) asm(a "OPTFENCE_" #b)
#define _optlabel(a) asm("OPTFENCE_" #a ":")
#define __optfence(a, ...) \
_optjmp("jmp ", a); \
opt_fence(__VA_ARGS__); \
_optlabel(a)
#define OPTFENCE(...) __optfence(__COUNTER__, __VA_ARGS__)
typedef unsigned int p_rel;
static inline char *_getaddr(p_rel *i, p_rel addr)
{
return ((char *) i + addr);
}
#define getaddr(addr) _getaddr(&addr, addr)
static inline p_rel _setaddr(p_rel *i, char *p)
{
return (*i = (p - (char *) i));
}
#define setaddr(relative_p, pointer) _setaddr(&relative_p, pointer)
typedef struct __vm {
p_rel array[N_VM_ELEMENTS];
struct __vm *next;
int max, subtract;
char str[0];
} vm_t;
vm_t *vm_new()
{
vm_t *node = mmap(0, PAGESIZE, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (node == MAP_FAILED)
err(ENOMEM, "Failed to map memory");
node->max = N_VM_ELEMENTS;
node->next = NULL;
setaddr(node->array[0], node->str);
OPTFENCE(node);
return node;
}
const char *vm_get(int num, vm_t *nod)
{
num -= nod->subtract;
if (num < 0)
return NULL;
while (num >= (nod->max - 1)) {
num -= nod->max - 1;
if (!nod->next)
return 0;
nod = nod->next;
}
return getaddr(nod->array[num]);
}
void vm_destroy(vm_t *nod, void *(callback)(const char *e))
{
const char *ee;
for (int a = nod->subtract; (ee = vm_get(a, nod)); a++)
callback(ee);
do {
char *tmp = (char *) nod;
nod = nod->next;
munmap(tmp, PAGESIZE);
} while (nod);
}
static vm_t *vm_extend_map(vm_t *nod)
{
nod->next = mmap(0, PAGESIZE, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
nod = nod->next;
nod->max = N_VM_ELEMENTS;
setaddr(nod->array[0], nod->str);
return nod;
}
void vm_add(int num, const char *path, vm_t *nod)
{
if (!nod->subtract)
nod->subtract = num;
num -= nod->subtract;
while (num >= (nod->max - 1)) {
num -= MMMM;
if (!nod->next) {
nod = vm_extend_map(nod);
break;
}
nod = nod->next;
}
if (nod->array[num + 1])
err(0, "Num %d already used!\n", num);
if ((int) ((nod->array[num] + (sizeof(p_rel) * (N_VM_ELEMENTS - num)) +
strlen(path))) >= PAGESIZE) {
nod->max = num + 1;
NNNN;
nod = vm_extend_map(nod);
}
char *p = stpcpy(getaddr(nod->array[num]), path);
p++;
setaddr(nod->array[num + 1], p);
}
static void *dealloc(const char *e)
{
return NULL;
}
int main(int argc, char **argv)
{
vm_t *vm = vm_new();
vm_add(1, "First element\n", vm);
vm_add(2, "Second element\n", vm);
char buf[64];
strcpy(buf, "Element # \n");
for (int i = 3; i < 10; i++) {
buf[12] = i + '0';
vm_add(i, buf, vm);
}
printf("fetch, 5: %s", vm_get(5, vm));
for (int i = 10; i < 400; i++) {
sprintf(buf, "Element (extending the medium size): %d\n", i);
vm_add(i, buf, vm);
}
printf("fetch: %s", vm_get(15, vm));
printf("fetch: %s", vm_get(155, vm));
printf("fetch: %s", vm_get(355, vm));
printf("fetch: %s", vm_get(100, vm));
printf("fetch: %s", vm_get(224, vm));
vm_destroy(vm, dealloc);
return (0);
}
在 GNU/Linux 參考執行輸出:
請補完程式碼,使其執行符合預期。作答規範:
MMMM
和 NNNN
均為 C 表示式,以符合 C99 規範撰寫最精簡的形式
測驗 2
考慮使用 mmap 和 memfd 系統呼叫的 circular buffer 實作: cbuf (部分程式碼遮蔽)。參考執行結果:
請補完程式碼,使其執行符合預期。作答規範:
CCCC
為 C 表示式,以符合 C99 規範撰寫最精簡的形式
測驗 3
在 你所不知道的 C 語言:連結器和執行檔資訊 提過 ELF 執行檔格式,更多資訊可見 Executable and Linkable Format,以 64 位元 ELF 來說,開頭的幾個位元組的意義:
offset |
size |
Purpose |
0x00 |
4 |
0x7F followed by ELF(45 4c 46 ) in ASCII; these four bytes constitute the magic number. |
0x04 |
1 |
This byte is set to either 1 or 2 to signify 32- or 64-bit format, respectively. |
0x05 |
1 |
This byte is set to either 1 or 2 to signify little or big endianness, respectively. This affects interpretation of multi-byte fields starting with offset 0x10 . |
x06 |
1 |
Set to 1 for the original and current version of ELF. |
… |
… |
…待續… |
以下是相關的函式及系統呼叫:
給定要載入的程式碼: (檔名 hello.c
)
以下程式碼可載入並動態修改程式行為:
編譯和執行:
預期會得到 "Yellow" 輸出
接下來的程式碼嘗試在既有的 ELF 檔案內嵌另一個 ELF 檔案 (可預先加密),目的是隱匿特定的程式,避免被掃毒程式或防火牆偵測出來,或將高價值的程式嵌入到文件、圖片,甚至是影音檔案中,透過特定的載入器自檔案提取出執行檔並執行,這手法在 Digital rights management (DRM) 和 Digital watermarking 領域不算少見。
假設即將被嵌入的程式碼名為 payload.c
:
編譯並移去除錯用的符號:
接著我們要開發得以載入 ELF 的程式,在這之前,
假定程式載入器檔名為 loader.c
,內容如下:
編譯、嵌入上述 payload
執行檔,然後再執行: (你沒看錯,真的用 cat
命令)
在 x86_64 GNU/Linux (核心版本: 5.4) 預期輸出為:
注意:只有一行 "Hello world!" 字串
請補完程式碼,只要考慮 x86_64 硬體架構即可。
作答區 (注意: 複選題,儘量選取有效的答案)
AAA = ?
(a)
newelf - entirefile
(b)
size - newelf
(c)
entirefile - newelf
(d)
size - newelf - entirefile
(e)
size - newelf + entirefile
(f)
newelf - entirefile + size
(g)
entirefile - newelf - size
BBB = ?
(a)
getpid()
(b)
getpid() != pid
(c)
getpid() == pid
(d)
0
(e)
1