yutopp's blog

サンドバッグになりたい

読み方 of "セキュリティキャンプ2012に参加しました"

一つ前の記事で,セキュリティキャンプ2012についての感想文をexeに詰めて置いておいたのですが,そのままにしておくのも悲しいので,ザックリと解き方を書いておこうと思います.
未だに不慣れなので,変な箇所がありましたら是非教えて下さい><

先に,出てくる文章を貼っておきますね。

セキュリティキャンプ2012 ソフトウェア・セキュリティ・クラスに参加してきました!
とってもとっても楽しく、刺激的なキャンプでした。

応募用紙は、締め切りの日の午前1時過ぎからtwitterで"#moudamedaJP"などと呟きながら、朝までずっと書いてました。
応募用紙締め切りの前日まで東京にいたりと色々あり (来年応募しよう...) などと勝手に思っていたのですが、いざ考えてみると高校の時に一度応募して落ちてしまったことなどを思い出して、
やっぱり今年行きたい!とテンションが上がってきたのでウオオオオオと書いてました。
内容はいろいろ書いた気がします。一人では気付けないことも沢山あるので、沢山の知識を持った凄い方々が集まるセキュキャンで、脆弱性やバイナリについて学びたかったのでございました。
深夜の勢いで書いたので色々アレでしたが、応募して良かったです。

そして、受かった後はkadaiがでます。kadai。
私はkadaiの解答がほんとmoudamedaだったので、もっと頑張ろうと思いました...

さて、スケジュールはこんな感じでした!
1日目:開校式や共通講義。サイバー犯罪についてなどの特別講義がありました。オリエンテーションやグループワークなども。
2日目:ついにクラス別講義です。解析ツールの使い方などを学び、アセンブラ読経やマルウェア解析を行いました。
3日目:マルウェア解析についての発表。脆弱性あふれるプログラムの修正と、各々で修正されたプログラムを攻撃し合いました。夜はヒューリスティック検知エンジンについて学び、その改良を行いました。その後、BoFとチューターの方の発表がありました。
4日目:ヒューリスティック検知エンジンについての続き、発表。その後CTFです!
5日目:グループワークや各クラスの発表、CTFの模範解答の発表。閉校式。本の争奪戦。

簡単にまとめても重量感ありますね・・・。それだけ内容が濃かったです。
講義は本当にどれも楽しく、ためになりました。これを礎にこの先、頑張りたいです。
また、普段お会いすることの出来ないような方々からお話を伺うことが出来てとても幸せでした!うおお。
twitterで交流があった方とも実際にお会いできて楽しかったです!

問題のCTFではあまり役に立てませんでした・・・。いや、とても楽しかったですけどね!
課題の問題が解けなかった悔しさがあったので、ひたすらソフトウェアの分野の問題を解いていました。5問しか解けなかったので悔しいでござる・・・。

セキュリティキャンプの良いと思ったところは、自分のクラスの分野の知識の足りない所だけでなく、他のクラスの方々からも自分の知らない知識について沢山学ぶ事ができるところですね!
全てのクラスの教材が頂ける上に、CTFでは全ての分野の問題が出題されるので、他のクラスの方から問題の解説を受けるとoh...となりました。調べることが沢山増えましたね!

頂けた本の中で一番嬉しかったのは、"12ステップで作る 組込みOS自作入門"です!さっそく読みます。

要するに内容も濃いし楽しいし最高でした!モチベーションもめちゃくちゃ高まりました。知識の無さも痛感しました...
俺俺ライブラリもどんどん開発したいですね。あとはアウトプットを増やしたいです。

余談:
朝起きるのがめちゃくちゃ辛かったです。あとは これはPyhtonという言語で、アパ水とスリッパ。

うああ,読み返すと普通に恥ずかしいですね!
まあ,ではではいってみましょー

読み方

お手元にOllyDbgとダウンロードしてきたa.zipをご用意下さい.
ちなみに,Windows 7(x64)にて,OllyDbg 1.10を使っています.
では,

main関数まで

  1. a.zipを解凍して下さい.普通のzipファイルです d=(^o^)=b
  2. a.exeは実行しても何も表示してくれないので,a.exeをOllyDbgで開きます.
  3. メモリウィンドウを開いてみてみると,UPXでパックされていますね!なのでアンパックしましょう(今回は手動で).f:id:yutopp:20120906140343p:plain
  4. プログラムの実行(F9)か,ステップオーバー(F8)を数回かカチカチしてntdllを抜けると,a.exeの中身です.いきなりPUSHADが見つかるので,メモリにブレークポイントを仕掛けます。(左下のカラムのHex dumpと書いてある所で,Ctrl+gを押し,出てきたダイアログにespと入力しokを選択すると,スタックと同じアドレスの位置に移動してくれるので,任意の場所を右クリックし,BreakpointメニューのHardware, on accessのByteを選択します.このスクリーンショットでは灰色っぽくなっているところに仕掛けてあります.)f:id:yutopp:20120906140356p:plainf:id:yutopp:20120906140400p:plain
  5. おもむろにプログラムを実行させます(F9).するとPOPADとJMPがある良い感じのところでブレークポイントが引っかかりますね!うまいことアンパックされているはずです.(途中で止まってしまう場合も,スクリーンショットの場所にたどり着くまで何回か試してみて下さい!)f:id:yutopp:20120906140434p:plain
  6. 次回から楽できるようにJMP命令の位置にプレークポイントを仕掛けときましょう.JMP命令のある行を選択し,F2を押します(右クリック,BreakpointにあるToggleです).赤くなればうまくいってます.f:id:yutopp:20120906134332p:plain
  7. マニュアルアンパックっぽいサムシングも終わったので,JMP命令をステップオーバーで進めます.すると本来の中身が展開されているはずです.(あってるのか・・・?)f:id:yutopp:20120906140657p:plain
  8. 段々読みにくくなってきました.そこで,右クリック,AppearanceのHighlightingにあるJumps'n'callsを選択します.カラフルになってコードが追いやすくなると思います.
  9. 次は,main関数を探します.mainにはargc,argv,envp(環境変数)の3つの引数が渡されるので,コマンドラインに関連するっぽい部分とPUSH 3つを探してみます.その付近のcallがmainの呼び出しのはずなので,ステップイン(F7)します.f:id:yutopp:20120906140914p:plain
  10. では,mainの中身を読み進めましょう!f:id:yutopp:20120906140743p:plain

mainから最後まで

mainの中身のネタばらしをすると,GetLocalTimeで特定の日付の時だけ特別な動きをするようになってることと,各ルーチンで謎の演算をしていること,callに対してPOP EAXとJMP EAXで戻っていること,それっぽい文字列の"seccamp 2012!!!"付近のコードはダミーだということくらいです d=(^o^)=b
上のスクリーンショットの例だと,003D1C44から003D1C59の間の小細工と,GetLocalTimeの分岐に続くPOP EAX // ADD EAX, 2 // JMP EAX がキモです.
003D1C4F E8 0A000000 CALL a.003D1C5E の下にある,003D1C54 B8 4258FFE0 MOV EAX,E0FF5842 を見ればすぐ分かると思います.

  1. とりあえず,特定の日付の時だけに飛ぶルーチンにいつでも飛べるように,機械語を書き換えてしまいましょう.GetLocalTimeの下,MOVZXの行を選択し,アセンブル(スペースキー)します(書き換えるアドレスは個々で違うと思うので,適宜読み替えて下さい.赤い四角で囲んだところがキーです).f:id:yutopp:20120906143808p:plain
  2. JMP命令をステップオーバーします.
  3. すると,戻り先のアドレスが2Byteずれたことによって,MOV EAX,E0FF5842 が POP EAX // JMP EAX に変わります.もう終わりが近いですね,JMP命令をステップオーバーします.f:id:yutopp:20120906150045p:plain
  4. 残りは,VirtualAllocが確保しているアドレスを監視,展開された内容をダンプし,utf-8の文字列として読み込むだけです!f:id:yutopp:20120906150902p:plain

おわりに

という訳で簡単ながらも遊んでみました.これからは,複雑なプログラムの内容も読み解けるように努力していきたいです!
おまけに,このプログラムを作るのに使ったソースコードを載せておきますね!
VC10でコンパイル出来ます.

encode.cpp

#include <fstream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <numeric>

int main()
{
	std::ifstream ifs( "src.txt", std::ios::binary );
	if ( !ifs ) {
		return -1;
	}
	std::istreambuf_iterator<char> begin = ifs, end;

	std::vector<char> bin;
	std::copy( begin, end, std::back_inserter( bin ) );

	std::for_each( bin.begin(), bin.end(), []( char& c ) { c = c ^ 0xcc; } );

	std::ofstream ofs( "encoded.csv", std::ios::binary );
	ofs << std::hex;
	std::for_each( bin.cbegin(), bin.cend(), [&ofs]( char const& c ) mutable {
		ofs << "0x" << static_cast<int>( c ) << ", ";
	} );
	ofs << "0x0";
}

a.cpp

#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
#include <cstdint>

#include <Windows.h>

unsigned char const src[] = {
	#include "encoded.csv"
};

void a() { std::cout << "a!!" << std::endl; }
void b() { std::cout << "b!!" << std::endl; }
void c() { std::cout << "c!!" << std::endl; }
void d() { std::cout << "d!!" << std::endl; }
void e() { std::cout << "e!!" << std::endl; }
void f() { std::cout << "f!!" << std::endl; }
void g() { std::cout << "g!!" << std::endl; }
void h() { std::cout << "h!!" << std::endl; }
void i() { std::cout << "i!!" << std::endl; }
void j() { std::cout << "j!!" << std::endl; }

int key = 53, sign = 0;

int main()
{
	__asm {
		push ok;
		call f_hoge;
		_emit 0xB8;	// mov eax
		_emit 0x42;     // d=(^o^)=b
		pop eax;
		jmp eax;
	}

	goto l_last;

f_hoge:
	SYSTEMTIME st;
	::GetLocalTime( &st );

	if ( st.wYear == 2012 && st.wMonth == 8 && st.wDay == 18 ) {
		goto f_hoge_ok;
	}

//f_hoge_ng:
	__asm {
		mov eax, 0xdeadbeef;
		shr eax, 4;
		mov key, eax;
	}

	__asm {
		pop eax;
		jmp eax;
	}

f_hoge_ok:
	__asm {
		mov eax, 100;
		shr eax, 2;
		add eax, eax;
		lea eax, [eax + eax * 2];
		add key, eax;
		inc key;
	}

	__asm {
		pop eax;
		add eax, 2;
		jmp eax;
	}

	
//dummy:
	goto f_hoge;
	std::cout << "seccamp 2012!!!" << std::endl;

	int num = 0;
	std::cin >> num;
	if ( num == 1 )
		a();
	else if ( num == 1 )
		b();
	else if ( num == 2 )
		c();
	else if ( num == 3 )
		d();
	else if ( num == 4 )
		e();
	else if ( num == 5 )
		f();
	else if ( num == 6 )
		g();
	else if ( num == 7 )
		h();
	else if ( num == 8 )
		i();
	else if ( num == 9 )
		j();

	{
		std::vector<int> v;
		for( int i=0; i<0xdeadbeef; ++i ) {
			v.push_back( i );
		}

		std::copy( v.cbegin(), v.cend(), std::ostream_iterator<char>( std::cout, ", " ) );
	}

	{
		struct h
		{
			int fact(int n)
			{
				return n == 0 ? 1 : n * fact( n - 1);
			}
		};

		h i;
		i.fact( 500 );
	}

ok:
	char *const p = reinterpret_cast<char *>( ::VirtualAlloc( nullptr, sizeof( src ), MEM_COMMIT, PAGE_READWRITE ) );
	if ( !p ) {
		return -1;
	}

	::Sleep( 500 );

	for( std::size_t i=0; i<sizeof( src ); ++i ) {
		p[i] = src[i] ^ key;
	}

	std::fill( p, p + sizeof( src ), 0 );
	::VirtualFree( p, sizeof( src ), MEM_DECOMMIT );

	__asm mov eax, 0;

l_last:
	int i;
	__asm {
		mov i, eax
	}

	return i;
}

では!