2015-11-03 17 views
6

ジョン・ビーガは、彼の著書「CとC++のためのSecure Programming Cookbook」で関数呼び出しを難読化する方法を提案しています。それはhereと読むことができます。難読化関数呼び出し

#define SET_FN_PTR(func, num)     \ 
    static inline void *get_##func(void) { \ 
     int i, j = num/4;     \ 
     long ptr = (long)func + num;   \ 
     for (i = 0; i < 2; i++) ptr -= j; \ 
     return (void *)(ptr - (j * 2));  \ 
    } 
#define GET_FN_PTR(func) get_##func() 

#include <stdio.h> 

void my_func(void) { 
    printf("my_func() called!\n"); 
} 

SET_FN_PTR(my_func, 0x01301100); /* 0x01301100 is some arbitrary value */ 

int main(int argc, char *argv[ ]) { 
    void (*ptr)(void); 

    ptr = GET_FN_PTR(my_func);  /* get the real address of the function */ 
    (*ptr)();      /* make the function call */ 
return 0; 

} 

私はUbuntuの15.10、64ビット、gcc5.2.1、およびassembyを確認し、gcc fp.c -S -O2でそれをコンパイル:

... 
my_func: 
.LFB23: 
     .cfi_startproc 
     movl $.LC0, %edi 
     jmp  puts 
     .cfi_endproc 
.LFE23: 
     .size my_func, .-my_func 
     .section  .text.unlikely 
.LCOLDE1: 
     .text 
.LHOTE1: 
     .section  .text.unlikely 
.LCOLDB2: 
     .section  .text.startup,"ax",@progbits 
.LHOTB2: 
     .p2align 4,,15 
     .globl main 
     .type main, @function 
main: 
.LFB25: 
     .cfi_startproc 
     subq $8, %rsp 
     .cfi_def_cfa_offset 16 
     call my_func 
     xorl %eax, %eax 
     addq $8, %rsp 
     .cfi_def_cfa_offset 8 
     ret 
     .cfi_endproc 
... 

私はmy_funcがmainで呼び出されていることがわかります。このメソッドが関数呼び出しを難読化する方法を誰かが説明できますか?

私は、多くの読者がちょうど降りてくるのを見る。私は問題を理解する時間と、ここに投稿しなかった時を取った。 downvoteボタンを押すのではなく、少なくともコメントを書いてください。

UPDATE:私はinlineingが現在存在しないと思います

... 
my_func: 
... 
get_my_func: 
... 
main: 
... 
    call get_my_func 
    movq %rax, -8(%rbp) 
    movq -8(%rbp), %rax 
    call *%rax 
... 

:私が得た最適化をオフにします。しかし、私は本当に私はまだそれが今日のスマートコンパイラで作業していない場合でも、このコードの作者の目標は何であったかの説明を探しています、なぜそれが重要である...

を理解していません。

+0

あなたのコメントをお寄せいただきありがとうございます – robert

+7

これは、人々が**より良い**コードを書いて、悪くないように助けることです。 – Olaf

+3

@Olaf私は商用ソフトウェアを保護しようとしています。 – robert

答えて

4

提案されたアプローチの考え方は、間接関数呼び出しを使用して関数アドレスを最初に計算してから呼び出す必要があるという考え方です。 Cプリプロセッサは、実際の関数に対してプロキシ関数を定義する方法を提供するために使用され、このプロキシ関数は、プロキシ関数がアクセスを提供する実際の関数の実際のアドレスを決定するために必要な計算を提供します。

プロキシデザインパターンを使用すると、プロキシとしてのラッパークラスを作成することにより、他の オブジェクトへのインタフェースを提供することができます:

が、これは言っているプロキシデザインパターンの詳細についてはWikipedia article Proxy patternを参照してください。プロキシであるラッパークラス は、オブジェクトのコードを変更することなく、 のオブジェクトに追加の機能を追加できます。

私は、間接呼び出しの同じタイプのソースコードの読み取りが困難にするようしかし、それは、このような形で実装の詳細を隠蔽するためにCプリプロセッサを使用する必要はありませんを実装し、代替をお勧めします。

Cコンパイラでは、structに関数ポインタをメンバーとして含めることができます。これは、構造体が定義されているときに関数ポインタを持つ外部的に見える構造体変数を定義することができるということです。構造体変数の定義で指定された関数はstaticであることを意味します(What does "static" mean in a C program参照)。 )

したがって、structタイプ、外部に見える構造変数の宣言、static修飾子で使用される関数、および修飾子として使用される関数を定義するヘッダファイルfunc.hと実装ファイルfunc.cの2つのファイルがあります。関数のアドレスを使って外部から見える構造体変数の定義。

魅力的なのは、ソースコードを読みやすく、ほとんどのIDEが間接的にこのような間接的な処理を行うという点です。Cプリプロセッサはコンパイル時にソースを作成するために使用されないため、 IDEなどのソフトウェアツールを使用します。関数を使用してCソース・ファイルにインクルードされるだろう例のfunc.hファイルは、次のようになり

// define a type using a typedef so that we can declare the externally 
// visible struct in this include file and then use the same type when 
// defining the externally visible struct in the implementation file which 
// will also have the definitions for the actual functions which will have 
// file visibility only because we will use the static modifier to restrict 
// the functions' visibility to file scope only. 
typedef struct { 
    int (*p1)(int a); 
    int (*p2)(int a); 
} FuncList; 

// declare the externally visible struct so that anything using it will 
// be able to access it and its members or the addresses of the functions 
// available through this struct. 
extern FuncList myFuncList; 

そしてfunc.cファイルの例は次のようになります

#include <stdio.h> 

#include "func.h" 

// the functions that we will be providing through the externally visible struct 
// are here. we mark these static since the only access to these is through 
// the function pointer members of the struct so we do not want them to be 
// visible outside of this file. also this prevents name clashes between these 
// functions and other functions that may be linked into the application. 
// this use of an externally visible struct with function pointer members 
// provides something similar to the use of namespace in C++ in that we 
// can use the externally visible struct as a way to create a kind of 
// namespace by having everything go through the struct and hiding the 
// functions using the static modifier to restrict visibility to the file. 

static int p1Thing(int a) 
{ 
    return printf ("-- p1 %d\n", a); 
} 

static int p2Thing(int a) 
{ 
    return printf ("-- p2 %d\n", a); 
} 

// externally visible struct with function pointers to allow indirect access 
// to the static functions in this file which are not visible outside of 
// this file. we do this definition here so that we have the prototypes 
// of the functions which are defined above to allow the compiler to check 
// calling interface against struct member definition. 
FuncList myFuncList = { 
    p1Thing, 
    p2Thing 
}; 
この外部から見える構造体を使用して簡単なCソースファイルは次のようになり

#include "func.h" 

int main(int argc, char * argv[]) 
{ 
    // call function p1Thing() through the struct function pointer p1() 
    myFuncList.p1 (1); 
    // call function p2Thing() through the struct function pointer p2() 
    myFuncList.p2 (2); 
    return 0; 
} 

としてあなたはこの関数呼び出しは、間接的な機能は、内のオフセットで指定された構造体を通じて呼び出す今ある見ることができるように

; 10 : myFuncList.p1 (1); 

    00000 6a 01  push 1 
    00002 ff 15 00 00 00 
    00  call DWORD PTR _myFuncList 

; 11 : myFuncList.p2 (2); 

    00008 6a 02  push 2 
    0000a ff 15 04 00 00 
    00  call DWORD PTR _myFuncList+4 
    00010 83 c4 08  add  esp, 8 

; 12 : return 0; 

    00013 33 c0  xor  eax, eax 

:上記main()のためのVisual Studio 2005から放出されたsemblerは以下が指定されたアドレスを介して計算されたコールを示すようになります。構造体。

このアプローチの良い点は、データ領域から関数を呼び出す前に正しい関数アドレスが置かれていれば、関数ポインタを含むメモリ領域に何でもできます。したがって、実際には、正しいアドレスでエリアを初期化する機能と、エリアをクリアする2つの機能を持つことができます。したがって、関数を使う前に関数を呼び出して領域を初期化し、関数の終了後に関数を呼び出して領域をクリアします。これはmain()を修正よう

// file scope visible struct containing the actual or real function addresses 
// which can be used to initialize the externally visible copy. 
static FuncList myFuncListReal = { 
    p1Thing, 
    p2Thing 
}; 

// NULL addresses in externally visible struct to cause crash is default. 
// Must use myFuncListInit() to initialize the pointers 
// with the actual or real values. 
FuncList myFuncList = { 
    0, 
    0 
}; 

// externally visible function that will update the externally visible struct 
// with the correct function addresses to access the static functions. 
void myFuncListInit (void) 
{ 
    myFuncList = myFuncListReal; 
} 

// externally visible function to reset the externally visible struct back 
// to NULLs in order to clear the addresses making the functions no longer 
// available to external users of this file. 
void myFuncListClear (void) 
{ 
    memset (&myFuncList, 0, sizeof(myFuncList)); 
} 

だから、あなたが何かをすることができます:

myFuncListInit(); 
myFuncList.p1 (1); 
myFuncList.p2 (2); 
myFuncListClear(); 

しかし、あなたが本当にやりたいだろうかmyFuncListInit()への呼び出しは、場所の近くではないでしょうソースのどこかになることです関数は実際に使用されます。

もう1つの興味深いオプションは、データ領域を暗号化し、プログラムを使用するために、正しいポインタアドレスを取得するためにデータを適切に復号化するために正しいキーを入力する必要があります。

+0

なぜp1とp2は静的ですか? staticキーワードを削除してもmainのアセンブラは変更されません。 – robert

+1

@ franz1の関数 'p1'と' p2'は、ファイルスコープに対する可視性の範囲を減らすために静的です。言い換えれば、関数 'p1'と' p2'はファイル 'func.c'の外の関数としては見えません。そして、それらがアクセスできる唯一の方法は、外部から見える構造体' myFuncList'の関数ポインタです。'static(static)'を削除すると 'main()'が構造体 'myFuncList'を介して' main() 'にアクセスしても' main() 'のアセンブラには影響しません。 –

+0

@ franz1関数の名前をp1からp1Thingに変更し、p2をp2Thingに変更して、構造体メンバが関数を指すポインタ変数であることを明確にしました。別のエンティティである関数名とメンバ名に同じテキストを使用すると、あなたに混乱を招くのではないかと思いました。 –

7

関数呼び出しを難読化する方法の問題は、コンパイラーが難読化を見るのに十分スマートではないことに依存しています。ここでの考え方は、呼び出し元が呼び出される関数への直接参照を含むのではなく、別の関数から関数へのポインタを取り出すことです。

ただし、現代のコンパイラはこれを行いますが、最適化を適用すると、難読化が再び解除されます。コンパイラが行うのはおそらくGET_FN_PTRの単純なインライン展開であり、インラインで展開すると、最適化する方法がはっきりと分かります。それは、ポインタに結合されて呼び出される単なる束です。定数式は、コンパイル時には非常に簡単です(しばしば行われます)。

コードを難読化する前に、そうするのが正当な理由があり、ニーズに適した方法を使用する必要があります。

0

C/C++の「難読化」は、主にコンパイルされたコードのサイズに関連しています。短すぎる場合(例えば、500-1000の組立ライン)、すべての中レベルのプログラマはそれを解読し、数日間または数時間必要なものを見つけることができる。