ksnctfにチャレンジ 第30問目です。
※ksnctfは常駐型のCTFサイトです。 ※問題のページはコチラです。
Alpha Mixed Cipher
タイトルとヒント、文字列から metasploit frameworkなどにも搭載されている シェルコードのASCII表示変換ということはすぐにわかります。
ということでこの文字列を直接機械語として読みだせばいいはずです。
#include <sys/mman.h>
#include <stdint.h>
char code[] = "VTX630VXH49HHHPhYAAQhZYYYYAAQQDDDd36FFFFTXVj0PPTUPPa301089IIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIn9hkokN944VDxtfQkbh2CG6QyYE4nkpqp0lKBVtLlKpvULnksvs8LKQnwPnk5ffXPOWhd5JSQIUQN1iokQu0nk2LfD4dNk2egLNkBt4h1h6a9zlKBjR8NkBzQ07qJKysdt3ynkvTLKuQHnp1yoTqYP9lLlk49PD4WwYQxODMeQxGjKHtGK1lWTa8T5iqLKaJetS1JKCVnkTL0KlKqJwlvaJKlKvdlKFaM8LIrdtdglu18CH26hgYztlI8elIYRrHnnrntNzLQBZHooyoYoIoMY3ugtMk1nN8xbqclG7lFDF2kXnniokOyoniG5eXcXRLplGP2a58ecp26NqtrHT543e5BRK8slfDgzmYzFSfyo2uVdmYzbF0oK98lbrmoLLGUL14qBkXqqyoyoio3X0obXrx5pbHsQRGqusrphbmBE2S3C01iKk8QL5tDJOyXcrHQe0XgPepqxTvpovX42U8CIaqBKQSE8sFRTdsEde81OVRaX60bHPFblsqw76QIYoxRl7TGeNiKQtqXR0R2s0Q1BkOxP4qiP0PkOceWxAA";
int main(){
mprotect((void *)((uint32_t)code & ~4095), 4096, PROT_READ|PROT_WRITE|PROT_EXEC);
(*(void(*)()) code)();
return 0;
}
これを -m32 で実行すればいいんでしょ と思って実行してもSEGVで上手く行きません。
ちょっと調べたところ VTX630 から始まっているので ここ や ここ などから分かるように Windows向けのシェルコードなようで、最初の VTX〜 は EIPの値を取得するお決まりのコードみたいです。
で、
シェルコードを exe に変換できる Shellcode 2 EXE を仮に使って
Windowsで走らせてみましたが、各所でSEGVが発生します。
ASCIIコードで表示することを実現したりヌルバイトを避けるために
元のコードより一見無駄な処理なども追加されているので読み下すのも大変そうです… 一旦保留
正体は分かっているのでもう少し調べてみます。 VTX630〜がEIPの取得ですがmetasploitの出力をみると それ以降も定型文が続いているようで それぞれ調べてみると
シェルコードを実行するにあたって 実行時デコーダ+シェルコード本体 という構成になっていているようです。
この実行時デコーダが正しくシェルコードを実行するためには ベースアドレスがわからないとだめで、どうにかしてベースアドレスを 求めて特定のアドレスやレジスタに保持させる必要があります。 また、Windowsの場合はSEHを用いてベースアドレスを自動で求めることができるのは 前述の通りです。
よって今回の場合
Win SEHを用いてベースアドレスを取得
"VTX630VXH49HHHPhYAAQhZYYYYAAQQDDDd36FFFFTXVj0PPTUPPa301089"
このSEH利用上記コードにてベースアドレスを取得した場合
ECXにベースアドレスが格納されているので
ECXに手動でベースアドレスを入力した時と同じ処理である
"IIIIIIIIIIIIIIIII7QZ"
を使用してデコーダ本体
"jXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JB"
をロード
という手順になっており 以下シェルコード本体が続くわけです。 よって
n9hkokN944VDxtfQkbh2CG6QyYE4nkpqp0lKBVtLlKpvULnksvs8LKQnwPnk5ffXPOWhd5JSQIUQN1iokQu0nk2LfD4dNk2egLNkBt4h1h6a9zlKBjR8NkBzQ07qJKysdt3ynkvTLKuQHnp1yoTqYP9lLlk49PD4WwYQxODMeQxGjKHtGK1lWTa8T5iqLKaJetS1JKCVnkTL0KlKqJwlvaJKlKvdlKFaM8LIrdtdglu18CH26hgYztlI8elIYRrHnnrntNzLQBZHooyoYoIoMY3ugtMk1nN8xbqclG7lFDF2kXnniokOyoniG5eXcXRLplGP2a58ecp26NqtrHT543e5BRK8slfDgzmYzFSfyo2uVdmYzbF0oK98lbrmoLLGUL14qBkXqqyoyoio3X0obXrx5pbHsQRGqusrphbmBE2S3C01iKk8QL5tDJOyXcrHQe0XgPepqxTvpovX42U8CIaqBKQSE8sFRTdsEde81OVRaX60bHPFblsqw76QIYoxRl7TGeNiKQtqXR0R2s0Q1BkOxP4qiP0PkOceWxAA
ここがシェルコードの本体のようです。 また実行時デコーダですが シェルコードをASCII化する際の処理は
for (int j=0;j<sizeof(evil);j++)//evil你自己的shllcode,用的话需修改源码
//evilは君のシェルコードに書き換えなよ
{
input=evil[j];
// encoding AB -> CD 00 EF 00
A = (input & 0xf0) >> 4;
B = (input & 0x0f);
F = B;
// E is arbitrary as long as EF is a valid character
i = rand() % strlen(valid_chars);
while ((valid_chars[i] & 0x0f) != F) { i = ++i % strlen(valid_chars); }
E = valid_chars[i] >> 4;
// normal code uses xor, unicode-proof uses ADD.
// AB ->
D = unicode ? (A-E) & 0x0f : (A^E);
// C is arbitrary as long as CD is a valid character
i = rand() % strlen(valid_chars);
while ((valid_chars[i] & 0x0f) != D) { i = ++i % strlen(valid_chars); }
C = valid_chars[i] >> 4;
printf("%c%c", (C<<4)+D, (E<<4)+F);
}
こうなっているので 実行時デコーダはこの逆を行っているはずなので これを再現します。
for(i=0;i+1<sizeof(sc);i=i+2) {
ch1 = sc[i];
ch2 = sc[i+1];
D = (ch1 & 0x0f);
E = (ch2 & 0xf0) >> 4;
F = (ch2 & 0x0f);
A = D^E;
B = F;
printf("\\x%X%X", A, B);
}
こんな感じに再現できます。 これで シェルコードの本体のデコード済みコードは
\xD9\xEB\x9B\xD9\x74\x24\xF4\x31\xD2\xB2\x77\x31\xC9\x64\x8B\x71\x30\x8B\x76\x0C\x8B\x76\x1C\x8B\x46\x08\x8B\x7E\x20\x8B\x36\x38\x4F\x18\x75\xF3\x59\x01\xD1\xFF\xE1\x60\x8B\x6C\x24\x24\x8B\x45\x3C\x8B\x54\x28\x78\x01\xEA\x8B\x4A\x18\x8B\x5A\x20\x01\xEB\xE3\x34\x49\x8B\x34\x8B\x01\xEE\x31\xFF\x31\xC0\xFC\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x01\xC7\xEB\xF4\x3B\x7C\x24\x28\x75\xE1\x8B\x5A\x24\x01\xEB\x66\x8B\x0C\x4B\x8B\x5A\x1C\x01\xEB\x8B\x04\x8B\x01\xE8\x89\x44\x24\x1C\x61\xC3\xB2\x08\x29\xD4\x89\xE5\x89\xC2\x68\x8E\x4E\x0E\xEC\x52\xE8\x9F\xFF\xFF\xFF\x89\x45\x04\xBB\x7E\xD8\xE2\x73\x87\x1C\x24\x52\xE8\x8E\xFF\xFF\xFF\x89\x45\x08\x68\x6C\x6C\x20\x41\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x88\x5C\x24\x0A\x89\xE6\x56\xFF\x55\x04\x89\xC2\x50\xBB\xA8\xA2\x4D\xBC\x87\x1C\x24\x52\xE8\x61\xFF\xFF\xFF\x68\x6F\x78\x58\x20\x68\x61\x67\x65\x42\x68\x4D\x65\x73\x73\x31\xDB\x88\x5C\x24\x0A\x89\xE3\x68\x75\x58\x20\x20\x68\x36\x6F\x38\x72\x68\x79\x61\x6B\x43\x68\x76\x74\x33\x34\x68\x5F\x32\x48\x50\x68\x46\x4C\x41\x47\x31\xC9\x88\x4C\x24\x15\x89\xE1\x31\xD2\x52\x53\x51\x52\xFF\xD0\x31\xC0\x50\xFF\x55\x08\x51\x5F
とわかりました。
これをまた Shellcode 2 EXE で実行ファイルにすると
これで必要な部分だけの実行ファイルができたので 無事に実行できました。