web-dev-qa-db-ja.com

メタモルフィックコードの例

私は Polymorphic および Metamorphic コードの概念を理解していますが、両方のWikipediaページを最近読みました(これまでにこれまで行ったことがない理由があります)。今、私は自分のためにいくつかの変成コードを書いてみたいと思っています。

私は言語の達人ではありません。 PHP、MySQL、c/c ++、Java、Bashスクリプト、Visual Basic 6、VBScripting、Perl、JavaScriptを知っています。

誰でもこれらの言語のいずれかで変成コードの例を提供できますか?プログラムの出力が "Hello World"のみの場合でも、実際の例を見て、これがどのように発生しているかを例を通して理解したいと思います(私は、これらの手法を精神的思考だけでどのように達成できるかを理論化するのに苦労しています)。どの言語でも実際に実行できますが、これらは推奨される言語です。

さらに、インターネットを検索すると、c/c ++の限られた数の例のみが返されます(完全な実際の例でもなく、コードの部分的なスニペットでもありません)。メタモルフィックコードを作成するために必要な/柔軟性?

35
jwbensley

以下は、Cで記述されたメタモルフィックコードとして分類すると思われるものの例です。移植可能なCコードを書く経験があまりないので、他のプラットフォームでコンパイルするには変更が必要になる場合があります(私はm Windowsで古いバージョンのBorlandを使用)。また、一部のマシンコード生成が含まれるため、ターゲットプラットフォームがx86であることに依存しています。理論的には、どのx86 OSでもコンパイルできるはずです。

仕組み

プログラムが実行されるたびに、ランダムに変更された自分自身のコピーが、異なるファイル名で生成されます。また、変更されたオフセットのリストを出力するので、実際に何かが行われているのを確認できます。

変更プロセスは非常に単純です。ソースコードは、事実上何もしないアセンブリ命令のシーケンスで解釈されるだけです。プログラムを実行すると、これらのシーケンスが検出され、ランダムに別のコードに置き換えられます(明らかに何もしません)。

オフセットのリストをハードコーディングすることは、他の人がコンパイルできるようにする必要があるものには現実的ではないため、オブジェクトコードの検索で簡単に識別できるようにシーケンスが生成されます。 。

各シーケンスは、特定のレジスターに対するプッシュ操作、そのレジスターを変更する一連の命令、そしてレジスターをその初期値に復元するポップ操作から始まります。単純にするために、元のソースでは、シーケンスはすべてPush EAX、8つのNOPs、およびPOP EAXです。ただし、以降のすべての世代のアプリでは、シーケンスは完全にランダムになります。

コードの説明

私はコードを複数の部分に分割したので、それを段階的に説明することができます。自分でコンパイルする場合は、すべてのパーツを結合する必要があります。

まず、かなり標準的なものが含まれます:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

次に、さまざまなx86オペコードの定義があります。これらは通常、他の値と組み合わせて完全な命令を生成します。たとえば、Push定義(0x50)自体はPush EAXですが、0〜7の範囲のオフセットを追加することにより、他のレジスタの値を導出できます。POPMOVについても同様です。

#define Push 0x50
#define POP  0x58
#define MOV  0xB8
#define NOP  0x90

最後の6つは、いくつかの2バイトオペコードのプレフィックスバイトです。 2番目のバイトはオペランドをエンコードし、後で詳しく説明します。

#define ADD  0x01
#define AND  0x21
#define XOR  0x31
#define OR   0x09
#define SBB  0x19
#define SUB  0x29

const unsigned char prefixes[] = { ADD,AND,XOR,OR,SBB,SUB,0 };

JUNKは、ジャンクオペレーションのシーケンスをコード内の任意の場所に挿入するマクロです。前に説明したように、最初はPush EAXNOPPOP EAXを書き込むだけです。 JUNKLENは、シーケンス内のNOPsの数であり、シーケンスの全長ではありません。

また、気づかない場合のために、__emit__は、リテラル値をオブジェクトコードに直接挿入する疑似関数です。別のコンパイラを使用している場合は、移植する必要があるかもしれません。

#define JUNK __emit__(Push,NOP,NOP,NOP,NOP,NOP,NOP,NOP,NOP,POP)
#define JUNKLEN 8

コードがロードされるいくつかのグローバル変数。グローバル変数は悪いですが、私は特に優れたコーダーではありません。

unsigned char *code;
int codelen;

次に、オブジェクトコードをメモリに読み込む単純な関数があります。私は気にしないので、私はそのメモリを決して解放しません。

JUNKマクロ呼び出しがランダムな位置に挿入されていることに注意してください。コード全体で、これらの多くが表示されます。それらはほとんどどこにでも挿入できますが、実際のCコンパイラ(C++ではなく)を使用している場合は、変数宣言の前または中間に配置しようとすると文句が表示されます。

void readcode(const char *filename) {
  FILE *fp = fopen(filename, "rb");    JUNK;
  fseek(fp, 0L, SEEK_END);             JUNK;
  codelen = ftell(fp);
  code = malloc(codelen);              JUNK;
  fseek(fp, 0L, SEEK_SET);
  fread(code, codelen, 1, fp);         JUNK;
}

アプリケーションが変更された後、アプリケーションを再度書き出す別の単純な関数。新しいファイル名の場合、元のファイル名の最後の文字を、毎回増分される数字に置き換えるだけです。ファイルが既に存在するかどうか、およびオペレーティングシステムの重要な部分を上書きしていないかどうかを確認する試みは行われません。

void writecode(const char *filename) {
  FILE *fp;
  int lastoffset = strlen(filename)-1;
  char lastchar = filename[lastoffset];
  char *newfilename = strdup(filename);  JUNK;
  lastchar = '0'+(isdigit(lastchar)?(lastchar-'0'+1)%10:0);
  newfilename[lastoffset] = lastchar;
  fp = fopen(newfilename, "wb");         JUNK;
  fwrite(code, codelen, 1, fp);          JUNK;
  fclose(fp);
  free(newfilename);
}

この次の関数は、ジャンクシーケンスのランダムな命令を書き出します。 regパラメータは、作業中のレジスタを表しています。これは、シーケンスの両端でプッシュおよびポップされるものです。 offsetは、命令が書き込まれるコード内のオフセットです。そしてspaceは、シーケンスに残ったバイト数を示します。

スペースの量によっては、書き出すことができる命令に制限される場合があります。それ以外の場合は、NOPMOV、またはその他のいずれかをランダムに選択します。 NOPは1バイトです。 MOVは5バイトです。MOVオペコード(regパラメータが追加されています)、およびレジスタに移動された数を表す4つのランダムバイトです。

2バイトシーケンスの場合、最初のものはランダムに選択されたプレフィックスの1つにすぎません。 2番目は、0xC0から0xFFの範囲のバイトです。ここで、最下位3ビットはプライマリレジスタを表します。つまり、これはregパラメータの値に設定する必要があります。

int writeinstruction(unsigned reg, int offset, int space) {
  if (space < 2) {
    code[offset] = NOP;                         JUNK;
    return 1;
  }
  else if (space < 5 || Rand()%2 == 0) {
    code[offset] = prefixes[Rand()%6];          JUNK;
    code[offset+1] = 0xC0 + Rand()%8*8 + reg;   JUNK;
    return 2;
  }
  else {
    code[offset] = MOV+reg;                     JUNK;
    *(short*)(code+offset+1) = Rand();
    *(short*)(code+offset+3) = Rand();          JUNK;
    return 5;
  }
}

これで、これらの命令の1つを読み取るための同等の関数ができました。シーケンスの両端のregおよびPush操作からPOPがすでに識別されていると仮定すると、この関数は、指定されたoffsetの命令が1つであるかどうかを検証できます私たちのジャンク操作とプライマリレジスタが指定のregパラメータと一致することを確認します。

有効な一致が見つかった場合は命令長を返し、それ以外の場合はゼロを返します。

int readinstruction(unsigned reg, int offset) {
  unsigned c1 = code[offset];
  if (c1 == NOP)
    return 1;                     JUNK;
  if (c1 == MOV+reg)
    return 5;                     JUNK;
  if (strchr(prefixes,c1)) {
    unsigned c2 = code[offset+1]; JUNK;
    if (c2 >= 0xC0 && c2 <= 0xFF && (c2&7) == reg)
      return 2;                   JUNK;
  }                               JUNK;
  return 0;
}

この次の関数は、ジャンクシーケンスを検索して置き換えるメインループです。まず、8バイト後に同じレジスタでPushオペコードとそれに続くPOPオペコード(またはJUNKLENに設定されたもの)を探します。

void replacejunk(void) {
  int i, j, inc, space;
  srand(time(NULL));                                 JUNK;

  for (i = 0; i < codelen-JUNKLEN-2; i++) {
    unsigned start = code[i];
    unsigned end = code[i+JUNKLEN+1];
    unsigned reg = start-Push;

    if (start < Push || start >= Push+8) continue;   JUNK;
    if (end != POP+reg) continue;                    JUNK;

レジスターがESPであることが判明した場合は、生成されたコードでESPを使用することはないため、安全にスキップできます(ESPでのスタック操作には、努力に値しない特別な考慮が必要です)。

    if (reg == 4) continue; /* register 4 is ESP */

見た目が良いと思われるPushとPOPの組み合わせを一致させたら、その間の説明を読みます。期待しているバイトの長さに正常に一致した場合、置き換え可能な一致と見なします。

    j = 0;                                           JUNK;
    while (inc = readinstruction(reg,i+1+j)) j += inc;
    if (j != JUNKLEN) continue;                      JUNK;

次に、7つのレジスタの1つをランダムに選択し(ESPを考慮しない前に説明したとおり)、シーケンスの両端にあるそのレジスタのPushおよびPOP操作を書き込みます。

    reg = Rand()%7;                                  JUNK;
    reg += (reg >= 4);
    code[i] = Push+reg;                              JUNK;
    code[i+JUNKLEN+1] = POP+reg;                     JUNK;

次に、writeinstruction関数を使用して、その間のスペースを埋めるだけです。

    space = JUNKLEN;
    j = 0;                                           JUNK;
    while (space) {
      inc = writeinstruction(reg,i+1+j,space);       JUNK;
      j += inc;
      space -= inc;                                  JUNK;
    }

そして、ここにパッチしたばかりのオフセットを表示します。

    printf("%d\n",i);                                JUNK;
  }
}                                                                             

最後にmain関数があります。これは、前述の関数を呼び出すだけです。コードを読み取り、ジャンクを置き換えてから、もう一度書き込みます。 argv[0]引数には、アプリケーションのファイル名が含まれます。

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

  readcode(argv[0]);     JUNK;
  replacejunk();         JUNK;
  writecode(argv[0]);    JUNK;

  return 0;
}

これですべてです。

いくつかの最後のメモ

このコードを実行するときは、明らかに、ユーザーが元のコードと同じ場所にあるファイルを書き出すための適切な権限を持っていることを確認する必要があります。次に、新しいファイルが生成された後、ファイル拡張子が重要なシステムを使用している場合は、通常、名前を変更するか、必要に応じて実行属性を設定する必要があります。

最後に、生成されたコードを直接実行して最高のものを期待するのではなく、デバッガを介して実行することをお勧めします。生成されたファイルを元の実行可能ファイルにコピーした場合、デバッガーは元のソースコードを表示したまま、ファイルをステップスルーできることを発見しました。次に、コードで[〜#〜] junk [〜#〜]と示されている箇所に到達すると、常にアセンブリビューにポップインして、生成されたコードを確認できます。

とにかく、私の説明がかなり明確になっているといいのですが、これはあなたが探していた類の例でした。ご不明な点がございましたら、コメントでお気軽にお問い合わせください。

ボーナス更新

おまけとして、スクリプト言語の変成コードの例も含めようと思いました。これはCの例とはかなり異なります。この場合、バイナリ実行可能ファイルではなく、ソースコードを変更する必要があるためです。

この例では、phpのgoto関数を多用しました。すべての行はラベルで始まり、次の行のラベルを指すgotoで終わります。このように、各行は本質的に自己完結型であり、私たちはそれらを幸福にシャッフルし、プログラムを以前とまったく同じように動作させることができます。

条件とループ構造はもう少し複雑ですが、2つの異なるラベルのいずれかにジャンプする条件の形式で書き直す必要があります。コードにコメントマーカーを含めて、ループが試行され、追跡が容易になるようにします。

ideone.comのサンプルコード

すべてのコードはそれ自体のシャッフルされたコピーをエコーするだけなので、出力をソースフィールドにカットアンドペーストして再度実行するだけで、ideoneで簡単にテストできます。

さらに変更したい場合は、コードが実行されるたびに、すべてのラベルと変数を別のランダムな文字列のセットに置き換えるなど、かなり簡単に実行できます。しかし、私は物事をできるだけ単純にしておくことが最善であると考えました。これらの例は、概念を示すことのみを目的としています。実際には、検出を回避しようとしているわけではありません。 :)

50

公的に利用可能な変成コードサンプルは、いくつかの要因によって制限されます:

  • 1)専門知識:メタモルフィックコーディングは、コンピュータプログラミングにおける非常に高度な手法です。サンプリングに適した、一貫性のあるクリーンなメタモルフィックコードをコーディングできるプログラマーの数は、ごくわずかです。

  • 2)金銭的インセンティブ:変成コーディングは、商用アプリケーションでの使用に制限があります。このため、メタモルフィックコードを作成するのに十分なスキルを持つプログラマーの数は、メタモルフィックコーディング技術を作成/学習する専門的な露出/インセンティブを持っていません。

  • 3)合法性:メタモルフィックコーディングには、強力なウイルスの作成に大きな用途があります。したがって、メタモルフィックコードを作成した責任ある専門家は、ハッカーがコードを使用して悪意のある攻撃を強化できる可能性があるため、サンプルを自由に配布する倫理的な問題を抱えています。逆に、メタモルフィックコードを作成するのに十分な能力を備えたハッカーは、能力に基づいて非常に短い容疑者のリストに含まれるため、攻撃の1つが明らかになった場合、スキルを宣伝するインセンティブがありません。

  • 4)秘密性:最後に、そしておそらく最も現実的な理由で変成コードを見つけるのが難しいのは、変成プログラミングで能力を発揮し、サイバー犯罪のために当局に逮捕されない可能性が高く、政府のセキュリティ機関、民間のセキュリティ会社、またはウイルス対策企業によって採用される可能性が高く、プログラマーのその後の研究/知識は、競争力を維持するために機密保持契約の対象となります。

なぜC/C++

あなたはC/C++ポリ/メタモルフィックプログラミングのコード例。ハードウェアに近い言語のみがポリ/メタモルフィックになることができると推測。これは、poly/metamorphicコードの最も厳密な定義に当てはまります。解釈された言語はポリ/メタモーフィックな動作をする可能性がありますが、静的にコンパイルされたインタープリターに依存して実行されるため、「ランタイムシグネチャ」の大部分は変更できません。コンパイルされた低レベル言語のみが、非常に変更可能な「ランタイムシグネチャ」を持つ計算の柔軟性を提供します。

ここにいくつかの「ポリモーフィック」がありますPHP私が書いたコード。PHPはコンパイル言語ではなくインタプリタ言語であるため、真のポリモーフィズムは不可能です。

PHPコード:

<?php
// Programs functional Execution Section
system("echo Hello World!!\\n");
// mutable malicious payload goes here (if you were devious)

// Programs Polymorphic Engine Section
recombinate();
?>
<?php

function recombinate() {
  $file      = __FILE__;                    //assigns file path to $file using magic constant
  $contents  = file_get_contents($file);    //gets file contents as string
  $fileLines = explode("\n", $contents);    //splits into file lines as string array
  $varLine   = $fileLines[2];               //extracts third file line as string
  $charArr   = str_split($varLine);         //splits third line into char array
  $augStr    = augmentStr($charArr);        //recursively augments char array
  $newLine   = implode("",$augStr);         //rebuilds char array into string
  $fileLines[2] = $newLine;                 //inserts string back into array
  $newContents  = implode("\n",$fileLines); //rebuilds array into single string
  file_put_contents($file,$newContents);    //writes out augmented file
  sleep(1);                                 //let the CPU rest
  $pid = pcntl_fork();                      //forks process
  if($pid) {                                //if in parent:
    exit(0);                                //exit parent process
  }                                         //WARNING: creates 'Zombie' child process
  else {                                    //else in child process
    system("Nohup php -f " .$file . " 2> /dev/null"); //executes augmented file
    exit(0);                                //exits exit child process
  }
}

function augmentStr($inArr) {
  if (mt_Rand(0,6) < 5) {               //determines mutability
    /*$startIndex & $endIndex define mutable parts of file line as Xs
     * system("echo XXXXX ... XXXXX\\n");
     * 01234567890123            -7654321
     */
    $startIndex  = 13;
    $endIndex    = count($inArr)-7;
    $targetIndex = mt_Rand($startIndex,$endIndex);     //choose mutable index
    $inArr[$targetIndex] = getSafeChar(mt_Rand(0,62)); //mutate index
    $inArr = augmentStr($inArr);               //recurse
  }
  return $inArr;
}

function getSafeChar($inNum) {      //cannot use escaped characters
  $outChar;                 //must be a standard PHP char
       if ($inNum >=  0 && $inNum <= 9 ) { $outChar = chr($inNum + 48); }
  else if ($inNum >= 10 && $inNum <= 35) { $outChar = chr($inNum + 55); }
  else if ($inNum >= 36 && $inNum <= 61) { $outChar = chr($inNum + 61); }
  else if ($inNum == 62)                 { $outChar = " ";              }
  else                                   { $outChar = " ";              }
  return $outChar;
}

?>

警告:ゾンビプロセス を作成します。コードを実行する前にゾンビプロセスを強制終了する方法を知っています

情報検索手法:

この記事 には、Wikipediaよりも具体的な情報が含まれています。ただし、この記事には本当のソースコードは含まれていません。私のアドバイスが必要な場合は、サンプルのソースコードが見つかる可能性はほとんどありませんが、作成するのに十分なアカデミックドキュメントを見つけることができる場合があります独自の変成コード。これを開始することを検討してください( google scholar ):

学術論文/論文を読むときは、ドキュメントの最後にある出典を必ず確認してください。これらの出典にも貴重な情報があります。

知識の探求で最高の幸運を!

26
recursion.ninja

この回答はまだ完成していません。この質問の回答が完了するまで、私は時間をかけて展開していきます

スクリプトの例-PHP

PHPスクリプトジェームスホルダーネスが提供するスクリプトのコピーを自分で作成しました。これにより、デモを通じて変成スクリプトがどのように機能するかを自分で確認できます。コードの完全な記述はここにあります。 http://null.53bits.co.uk/index.php?page=php-goto-replicator

単純に、最初にスクリプトを実行した後、コードの行を新しいランダムな順序で、ランダムなファイル名で新しいファイルに自分自身をコピーし、スクリプトファイルの新しいコピーと元のファイルを実行する新しいプロセスをフォークします。コピー出口。これで、スクリプトの新しいコピーが実行されました。これは、元のファイルのコピーですが、ファイル名がランダムで、コードの行が別の順序になっています。これは永続的なプロセスです。並べ替えて複製し、新しいインスタンス(プロセス)を実行して前のインスタンス(プロセス)を強制終了します。

James HoldernessのPHP答えを少し、拡張して自己複製およびモーフィングの動作するコード例に拡張することを目指しました。

これは私が思いついた生のPHPコードです。

<?php goto a01;
a01: $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';    goto a02;
a02: $randomString = __DIR__."/";                                                       goto a03;
a03: $i = 0;                                                                            goto a04;
a04: if ($i < 10)                                                                       goto a05; else goto a07;
a05:   $randomString .= $characters[Rand(0, strlen($characters) - 1)];                  goto a06;
a06:   $i++;                                                                            goto a04;
a07: $randomString .= ".php";                                                           goto a08;
a08: $ARGS=Array("-f",$randomString);                                                   goto a09;
a09: $handle_out = fopen("$randomString", "w");  goto l01;
l01: $filename = __FILE__;                       goto l02;
l02: $contents = file_get_contents($filename);   goto l03;
l03: $lines = explode("\n",$contents);           goto l04;
l04: $collection = array();                      goto l05;
l05: $pattern = '%^[^:]+:.*goto [^;]+;$%';       goto l06;
l06: $i = 0;                                     goto l07;
l07: if ($i < count($lines)-1)                   goto l08; else goto l23;
l08:   $line = $lines[$i];                       goto l09;
l09:   $line = trim($line);                      goto l10;
l10:   if (substr($line,0,2) != '//')            goto l11; else goto l22;
l11:     if (preg_match($pattern, $line) === 1)  goto l12; else goto l13;
l12:       $collection[] = $line;                goto l22;
l13:       shuffle($collection);                 goto l14;
l14:       $j = 0;                               goto l15;
l15:       if ($j < count($collection))          goto l16; else goto l19;
l16:         echo $collection[$j]."\n";          goto l17;
l17:         fwrite($handle_out, $collection[$j]."\n");    goto l18;
l18:         $j++;                               goto l15;
l19:       $collection = array();                goto l20;
l20:       fwrite($handle_out, $line."\n");      goto l21;
l21:       echo $line."\n";                      goto l22;
l22:   $i++;                                     goto l07;
l23: fclose($handle_out);                        goto f01;
f01: $pid = pcntl_fork();                        goto f02;
f02: if ($pid == -1)                             goto f03; else goto f04;
f03:   die("Could not fork a new child\n");      goto f03;
f04: if ($pid)                                   goto f05; else goto f06;
f05:   exit(0);                                  goto f05;
f06: $sid = posix_setsid();                      goto f07;
f07: if ($sid < 0)                               goto f08; else goto f09;
f08:   die("Child posix_setsid error\n");        goto f08;
f09: sleep(10);                                  goto f10;
f10: pcntl_exec(PHP_BINARY, $ARGS);
l24: exit(0);                                    goto l24;
?>
2
jwbensley