2018年5月4日 星期五

c 語言, 刪除執行過的 function

看到《C是否能指定函數在特定記憶體, 執行後抹除?》這問題我覺得有趣, 想試試看能不能辦到?

想法就是找出 function 的位址以及長度, 然後用 memset 填 0 即可, 原理很簡單, 實作就麻煩了不只一點。

ref:
How to get the length of a function in bytes?

首先要怎麼得到被刪除的 function address, size?
function address 很簡單, 就是 function 名稱, function 名稱本身就是位址, 沒問題, 問題在怎麼取得這個 function 的 size。

一開始我用了 list 1 L37 的方法, 把 f2 - f1 得到的 size 當作 f1() 的長度, 但是這有個前提, 編譯器產生的程式碼得把 f2 緊緊接在 f1 之後, 這是有保證的嗎? 我不確定, 不過要是擔心的話, 可以用 objdump 反組譯確認, 我當然是確認過了。

另外一個作法麻煩點, 相依於編譯器開發工具, 我修改了內建的 linker script, 插入 list 2 L24 ~ L31 的 linker script 指令, 計算出 f1 真正的長度, 這是最保險的方式, 可惜沒有可攜性。再使用 list 1 L29 ~L32 取得 f1() 長度。

有了長度, 位址, 把它傳給 memset 就結束了嗎? 還沒, 我們在 linux os 的控制下, 記憶體不是 user mode 程式要幹麻就能幹麻, 得把這個記憶體設定為可執行、可讀取、可寫入, 才可以用 memset, 這是 list 1 L47 在做的事情, 還沒完, 在這之前, 得先要算出 page align 的位址才可以, 因為 mprotect 需要傳入 page align 的位址。list 1 L38 ~ L42 就是在計算這個位址, 好了, 真的結束了, 在 list 1 L58 之後, 會發出 segment fault 的錯誤, 因為 f1 不再存在了。

list 1. erase_function.cpp
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 // after call f1(), erase f1()
 4 
 5 #include <malloc.h>
 6 #include <cstring>
 7 
 8 #include <cstdint>
 9 
10 #include <unistd.h>
11 #include <sys/mman.h>
12 
13 using namespace std;
14 
15 // ref: https://stackoverflow.com/questions/4156585/how-to-get-the-length-of-a-function-in-bytes
16 __attribute__((noinline, section("f1_sec"))) 
17 void f1()
18 {
19   printf("xx f1\n");
20 }
21 
22 void f2()
23 {
24   printf("xx f2\n");
25 }
26 
27 int main(int argc, char *argv[])
28 {    
29   extern unsigned char f1_sec_start[];
30   extern unsigned char f1_sec_end[];
31 
32   printf("f1_sec_end - f1_sec_start: %p\n", f1_sec_end - f1_sec_start);
33 
34   f1();
35   f2();
36 
37   auto len = ((char *)f2-(char *)f1);
38   auto pagesize = getpagesize();
39   printf("pagesize: %d\n", pagesize);
40 
41   uintptr_t b = (((uintptr_t)f1)+4095 & (~4095));
42   b = (uintptr_t)f1 - (uintptr_t)f1 % pagesize;
43 
44   printf("f1: %p\n", f1);
45   printf("b: %p\n", b);
46 
47   if (0 == mprotect((void*)b, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC))
48   {
49     printf("set to write|read|exec\n");
50   }
51   else
52   {
53     perror("mprotect fail\n");
54   }
55 
56   memset((void*)f1, 0, len);
57   f2();
58   f1();
59 
60   //printf("len: %p\n", len);
61 
62   return 0;
63 }



list 2. ld --verbose f1_sec.diff
 1 --- c1.ld 2018-03-19 09:58:21.161399780 +0800
 2 +++ c.ld 2018-03-19 10:06:07.377399780 +0800
 3 @@ -1,16 +1,3 @@
 4 -GNU ld (GNU Binutils for Debian) 2.29.1
 5 -  Supported emulations:
 6 -   elf_x86_64
 7 -   elf32_x86_64
 8 -   elf_i386
 9 -   elf_iamcu
10 -   i386linux
11 -   elf_l1om
12 -   elf_k1om
13 -   i386pep
14 -   i386pe
15 -using internal linker script:
16 -==================================================
17  /* Script for -z combreloc: combine and sort reloc sections */
18  /* Copyright (C) 2014-2017 Free Software Foundation, Inc.
19     Copying and distribution of this script, with or without modification,
20 @@ -66,6 +53,14 @@
21    .plt            : { *(.plt) *(.iplt) }
22  .plt.got        : { *(.plt.got) }
23  .plt.sec        : { *(.plt.sec) }
24 +
25 +  f1_sec :
26 +  {
27 +    f1_sec_start = .;
28 +    *(f1_sec)
29 +    f1_sec_end = .;
30 +  }
31 +
32    .text           :
33    {
34      *(.text.unlikely .text.*_unlikely .text.unlikely.*)
35 @@ -242,6 +237,3 @@
36    .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
37    /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
38  }
39 -
40 -
41 -==================================================

這樣的功能有實用性嗎?
skype 為了避免被反組譯出來, 就用了類似的技巧, 將自己初始化的函式消去。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。