Visual C++で記号プログラミング

hotpepsi
2010-12-22

hotpepsiです。

x86のWindowsで動作するhello.cを生成するプログラムを書いてみました。

#include <windows.h>
#include <stdio.h>
static const char *Hexnums[]={"' '-' '","'!'-' '","'#'-'!'","'#'-' '",
 "'$'-' '","'%'-' '","'&'-' '","'('-'!'","'('-' '", "')'-' '","'*'-' '",
 "'+'-' '","','-' '","'-'-' '","'.'-' '","'/'-' '","'='-'-'"};
#define H(n) Hexnums[(n)&0xF]
#define S Hexnums[4]
static void PutHex32(FILE *fp, unsigned int value) {
fprintf(fp,"((((((%s)<<(%s))|(%s))<<(%s))|\n(((%s)<<(%s))|(%s)))<<(%s))|\n"
"(((((%s)<<(%s))|(%s))<<(%s))|\n(((%s)<<(%s))|(%s))),\n",
H(value>>28),H(4),H(value>>24),H(8),H(value>>20),H(4),H(value>>16),Hexnums[0x10],
H(value>>12),H(4),H(value>>8),H(8),H(value>>4),H(4),H(value));
}
int main(int argc, char *argv[]) {
 FILE *fp = stdout;
 fprintf(fp, "__[]={\n");
 PutHex32(fp, 0x6C6C6548);  // Hell
 PutHex32(fp, 0x77202C6F);  // o, w
 PutHex32(fp, 0x646C726F);  // orld
 PutHex32(fp, 0x00000A21);  // !\n
 fprintf(fp, "};\n_[]={\n");
 PutHex32(fp, 0x006A006A);  // push 0 / push 0
 PutHex32(fp, 0x68900E6A);  // push 0xE / push __
 fprintf(fp, "__,\n");
 PutHex32(fp, 0xB890F56A);  // push -11 / mov eax, GetStdHandle
 PutHex32(fp, (unsigned int)GetStdHandle);
 PutHex32(fp, 0xB850D0FF);  // call eax / push eax / mov eax, WriteFile
 PutHex32(fp, (unsigned int)WriteFile);
 PutHex32(fp, 0x90C3D0FF);  // call eax / ret
 fprintf(fp, "};\n");
 return 0;
}

Windows XP SP3で実行させたところ、以下の記号だけのhello.cが生成されました。

__[]={
(((((('&'-' ')<<('$'-' '))|(','-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|(','-' ')))<<('='-'-'))|
((((('&'-' ')<<('$'-' '))|('%'-' '))<<('('-' '))|
((('$'-' ')<<('$'-' '))|('('-' '))),
(((((('('-'!')<<('$'-' '))|('('-'!'))<<('('-' '))|
((('#'-'!')<<('$'-' '))|(' '-' ')))<<('='-'-'))|
((((('#'-'!')<<('$'-' '))|(','-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('/'-' '))),
(((((('&'-' ')<<('$'-' '))|('$'-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|(','-' ')))<<('='-'-'))|
((((('('-'!')<<('$'-' '))|('#'-'!'))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('/'-' '))),
((((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
(((' '-' ')<<('$'-' '))|(' '-' ')))<<('='-'-'))|
(((((' '-' ')<<('$'-' '))|('*'-' '))<<('('-' '))|
((('#'-'!')<<('$'-' '))|('!'-' '))),
};
_[]={
((((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('*'-' ')))<<('='-'-'))|
(((((' '-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('*'-' '))),
(((((('&'-' ')<<('$'-' '))|('('-' '))<<('('-' '))|
(((')'-' ')<<('$'-' '))|(' '-' ')))<<('='-'-'))|
(((((' '-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('*'-' '))),
__,
(((((('+'-' ')<<('$'-' '))|('('-' '))<<('('-' '))|
(((')'-' ')<<('$'-' '))|(' '-' ')))<<('='-'-'))|
((((('/'-' ')<<('$'-' '))|('%'-' '))<<('('-' '))|
((('&'-' ')<<('$'-' '))|('*'-' '))),
(((((('('-'!')<<('$'-' '))|(','-' '))<<('('-' '))|
((('('-' ')<<('$'-' '))|('!'-' ')))<<('='-'-'))|
((((('#'-'!')<<('$'-' '))|('/'-' '))<<('('-' '))|
(((','-' ')<<('$'-' '))|(')'-' '))),
(((((('+'-' ')<<('$'-' '))|('('-' '))<<('('-' '))|
((('%'-' ')<<('$'-' '))|(' '-' ')))<<('='-'-'))|
((((('-'-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
((('/'-' ')<<('$'-' '))|('/'-' '))),
(((((('('-'!')<<('$'-' '))|(','-' '))<<('('-' '))|
((('('-' ')<<('$'-' '))|('!'-' ')))<<('='-'-'))|
(((((' '-' ')<<('$'-' '))|('.'-' '))<<('('-' '))|
((('!'-' ')<<('$'-' '))|('('-'!'))),
((((((')'-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
(((','-' ')<<('$'-' '))|('#'-' ')))<<('='-'-'))|
((((('-'-' ')<<('$'-' '))|(' '-' '))<<('('-' '))|
((('/'-' ')<<('$'-' '))|('/'-' '))),
};

ビルドするには、Win32コンソールアプリケーションのプロジェクトを作成してから、プロジェクトのプロパティを開き、構成プロパティ→リンカ→詳細の「エントリポイント」の欄に _ を設定します。
VC2008以降の場合には、DEPの設定を「イメージはDEPと互換性がない」にする必要があります。

動作原理はLinux/gcc版とほぼ同じです。
記号だけの変数を定義して、変数 _ をエントリポイントの関数とみなして実行させます。
APIの名前を指定するという高度な機能が使えないため、GetStdHandleとWriteFileのアドレスを代入して呼び出すという、アセンブリ言語よりも低レベルな方法を採用しています。