xakep panel

Jedno z dwóch zadań na tym CTFie przy którego rozwiązywaniu miałem ochotę dowiedzieć się gdzie mieszka autor i zrobić mu krzywdę

Zadanie

ecsc19_hakierpanel.png

W zadaniu dostaliśmy plik html (w załączniku, wraz z innymi plikami dotyczącymi zadania) przeznaczony na IE8, w którym cały JS był zakodowany archaicznym JScript.Encode. Bez większych problemów można znaleźć dekoder tego czegoś, jednak po próbie użycia go nie dostawaliśmy nic sensownego i nie wiedziałem co z tym zrobić. Przy kolejnym podejściu do zadania spojrzałem w kod dekodera, okazuje sie że oczekuje on pliku w UTF-8 , a dostarczony w zadaniu był w UTF-16. Po przekonwertowaniu (adminutf8.htm w zipie) dekodowanie przebiegło bez problemów (wynik w pliku admin-decoded.htm w zipie). Mając już jakiś JS można było zabrać się za reversing. Po sprowadzeniu tego kodu do ludzkiej formy, okazywało się, że nie mozna zdekodować stringów, bo funkcja на_русский używa dziwnego wyrażenia w evalu które działa inaczej w obecnych przegladarkach niż działało w IE8. Na szczęście M$ udostępnia obrazy VirtualBoxa z IE, można było więc wynik tej funkcji sprawdzić. Po zdekodowaniu stringów i doprowadzeniu kodu do sensownej postaci otrzymujemy to, co w deobf.html w zipie:

<!doctype html>
<html lang="en">
<head>
    <meta http-equiv="x-ua-compatible" content="IE=8"/>
	<!--[if gte IE 9]><!-->
	<script language="javascript">
	   alert("Compatibility error: Application needs IE8 to work properly!")
	   window.location.href = "https://yandex.ru/search/?text=%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C%20%D0%B2%D0%B8%D0%BD%D0%B4%D0%BE%D0%B2%D1%81";
	</script>
	<!--<![endif]-->
	<script language="JScript.Encode">

		function Element.prototype.submit(){
			this.signature.value = this.password.value.encrypt();
			if(this.user.value != "Ваня" || this.signature.value != "TT321MTlmflN18OEIve08jzWBKMq7XGJukuRBv45g18=")
			{
				window.ooo="Хакер!!!!", alert(window.ooo)
				this.submit();
			}
			else {
				alert("OK - доступ разрешен!");
				/* TODO */
				window.location.href = "/admin.php";
			}
		}

		function String.prototype.encrypt()
		{
			var szyfr = new ActiveXObject("System.Security.Cryptography.RijndaelManaged");
			var _encode = new ActiveXObject('System.Text.UTF8Encoding');
			var sha256 = new ActiveXObject("System.Security.Cryptography.SHA256Managed");
			sha256.ComputeHash_2(_encode.GetBytes_4(Date+''));
			szyfr.Key = sha256.hash;
			var md5 = new ActiveXObject("System.Security.Cryptography.MD5CryptoServiceProvider");
			md5.ComputeHash_2(_encode.GetBytes_4(String+''));
			szyfr.IV = md5.haSH;
			var enc_document = new ActiveXObject("Msxml2.DOMDocument");
			enc_document.loadXML("<root/>")
			enc_document.documentElement.dataType = "bin.base64"
			enc_document.documentElement.nodeTypedValue =
				szyfr.CreateEncryptor().TransformFinalBlock(
					_encode.GetBytes_4(this), 0, this.length);
			return enc_document.documentElement.text;
		}
</script>
</head>
<body>
	<center>
		<b>Admin panel</b>
		<form id="login-form" action="javascript:send()">
			<input name="user" type="text"/>
			<input name="password" type="password"/>
			<input name="signature" type="hidden"/>
			<input type="submit" value="send"/>
		</form>
	</center>
</body>
<script>
	function send() {
		document.getElementById("login-form").submit();
	}
</script>
</html>

Tu zaczyna się część na której zmarnowałem wiele godzin. Mamy string zaszyfrowany Rijandelem, przy czym jeśli się nie mylę, to wielkość bloku to 16 bajtów, wiec jest to AES. Chciałem bazując na jej kodzie napisać funkcję deszyfrującą, co zacząłem od fragmentu dekodującego base64. Jednak gdy próbowałem dekodować base64 jakiegoś tekstu i wypisywać przez console.log. dostawałem duzo chińskich znaczków, a zwrócony obiekt miał typ unknown i nie miał atrybutu length, wiec nie mogłem go wykorzystać w funkcji dekodującej. Godziny przeszukiwania googla i chińskich forów nic nie dały - wygląda na to, że tej metody ludzie używali jedynie do kodowanie base64, a nie dekodowania. Oczywiście napisane od 0 funkcje do obsługi base64 dawały inne wyniki niż ta, wiec to też ślepa uliczka.

Gdzie prawdopodobnie leżał problem? Jako iż korzystamy z API .net, prawdopodobnie zwracany jest obiekt, i na konsolke dostajemy jakąś jego reprezentację czy inny adres, cholera wie to ten IE stworzył.

Jak należy zrobić zadanie? Na pałę i bez zastanawiania się. Skoro nie mamy atrybutu legth, ale znamy długość base64, to wbijmy go na sztywno. Nie przejmujmy sie tym, że nie umiemy wyprintować obiektu, po prostu go używajmy, przepisując bezmyślnie funkcjęszyfrującą na deszyfrującą. Efekt (w pliku solve.htm):

<!doctype html>
<html lang="en">
<head>
    <meta http-equiv="x-ua-compatible" content="IE=8"/>
	<!--[if gte IE 9]><!-->
	<script language="javascript">
	   alert("Compatibility error: Application needs IE8 to work properly!")
	   window.location.href = "https://yandex.ru/search/?text=%D1%81%D0%BA%D0%B0%D1%87%D0%B0%D1%82%D1%8C%20%D0%B2%D0%B8%D0%BD%D0%B4%D0%BE%D0%B2%D1%81";
	</script>
	<!--<![endif]-->
	<script language="JScript.Encode">
		function String.prototype.decrypt()
		{
			var xmlDom = new ActiveXObject("Microsoft.XMLDOM");
			var el = xmlDom.createElement("tmp");
			el.dataType = "bin.Base64"
			el.text = this;
			var s = el.nodeTypedValue;
			var szyfr = new ActiveXObject("System.Security.Cryptography.RijndaelManaged");
			var _encode = new ActiveXObject('System.Text.UTF8Encoding');
			var sha256 = new ActiveXObject("System.Security.Cryptography.SHA256Managed");
			sha256.ComputeHash_2(_encode.GetBytes_4(Date+''));
			szyfr.Key = sha256.hash;
			var md5 = new ActiveXObject("System.Security.Cryptography.MD5CryptoServiceProvider");
			md5.ComputeHash_2(_encode.GetBytes_4(String+''));
			szyfr.IV = md5.haSH;
			var enc_document = new ActiveXObject("Msxml2.DOMDocument");
			enc_document.loadXML("<root/>")
			enc_document.documentElement.dataType = "bin.base64"
			enc_document.documentElement.nodeTypedValue =
				szyfr.CreateDecryptor().TransformFinalBlock(
					s, 0, 32);
			return enc_document.documentElement.text;
		}

		function Element.prototype.submit(){
			console.log("TT321MTlmflN18OEIve08jzWBKMq7XGJukuRBv45g18=".decrypt());
		}

</script>
</head>
<body>
	<center>
		<b>Admin panel</b>
		<form id="login-form" action="javascript:send()">
			<input name="user" type="text"/>
			<input name="password" type="password"/>
			<input name="signature" type="hidden"/>
			<input type="submit" value="send"/>
		</form>
	</center>
</body>
<script>
	function send() {
		document.getElementById("login-form").submit();
	}
</script>
</html>

Po wykonaniu w IE otrzymujemy: ecsc19{iet_an0ther_js_stand4rd}