이제는 고인이 되어버린 Flash 관련된 문제
Flash가 아직 서비스 중일때는 쉽게 풀 수 있는 문제였을거 같은데
Flash가 안되는 상황에서 풀려니 조금 난이도가 있었다.
문제의 목표는 validation code를 찾는것이다.
문제페이지에 접속하면 이런 화면이 나온다.
원래는 웹페이지 안에 플래시가 삽입되어서 보여지는듯 했으나
플래시 지원이 종료되면서 흔적만 남은 모습이다..
Flash는 보안 취약점으로 인한 악성코드 유포 도구로 사용되어서 2020년 12월 31일 부로 지원이 종료되었다.
해당 페이지에서 f12를 눌러 개발자도구를 실행시켜 본다.
요소 탭을 확인해보면 중간에 <script> 태그가 있다.
<script> 태그 안의 내용을 보면, value 값이 dbbcd6ee441aa6d2889e6e3cae6adebe 와 다르면
Authentication Failed 가 뜨고,
같으면 Authentication Success 가 뜨도록 만들어 놓은것을 볼 수 있다.
dbbcd6ee441aa6d2889e6e3cae6adebe 는 md5 해시값이다.
온라인 decrypt 사이트에서 쉽게 원래값을 찾아낼 수 있다.
(https://www.md5online.org/md5-decrypt.html)
md5 해시의 원래 값은 4141955195AA 이다.
참고로 4141955195AA 는 정답이 아니다.
제출하면 다시 하라는 문구가 나온다.
왜냐하면 4141955195AA 는 Flash에서 어떤 작업을 수행한 후에 나오는 값이기 때문이다.
결국 Flash를 손대야 한다.
다시한번 f12를 눌러 개발자도구 요소 탭으로 가서
이번엔 <embed> 태그를 살펴본다.
RootMe.swf 파일을 불러오고 있는게 보이는데,
swf 는 small web format 의 약자로
어도비 플래시에서 멀티미디어, 벡터 그래픽, 액션스크립트 등을 처리하는 데 사용하는 파일형식이다.
마우스를 가져다대면 저렇게 주소가 나오는데
(https://challenge01.root-me.org/web-client/ch20/RootMe.swf)
주소로 접근하면 swf 파일을 다운로드 할 수 있다.
swf 파일을 디컴파일하려면 특정한 도구를 사용해야 한다.
이번에는 FFDEC(JPEXS Free Flash Decompiler) 라는 도구를 사용해 본다.
(다운로드 : https://github.com/jindrapetrik/jpexs-decompiler/releases)
FFDEC를 실행시키면 이런 화면이 나온다.
다운로드 받은 RootMe.swf 파일을 드래그 앤 드롭하면 이렇게
swf 파일에 내장되어있는 액션스크립트를 확인할 수 있다.
액션스크립트는 플래시에서 사용하는 스크립트 언어를 말한다.
package
{
import flash.display.Loader;
import flash.display.Sprite;
import flash.utils.ByteArray;
public class RootMe extends Sprite
{
private static const KEY:String = "rootmeifyoucan";
private const EmbeddedSWF:Class = RootMe_EmbeddedSWF;
public function RootMe()
{
var _loc2_:Loader = null;
super();
var _loc1_:ByteArray = new this.EmbeddedSWF();
if(_loc1_.length != 0)
{
XOR(_loc1_,KEY);
_loc2_ = new Loader();
_loc2_.loadBytes(_loc1_);
addChild(_loc2_);
}
}
private static function XOR(param1:ByteArray, param2:String) : void
{
var _loc3_:Number = 0;
var _loc4_:Number = 0;
while(_loc4_ < param1.length)
{
param1[_loc4_] ^= param2.charCodeAt(_loc3_);
_loc3_++;
if(_loc3_ >= param2.length)
{
_loc3_ = 0;
}
_loc4_++;
}
}
}
}
위에있는것이 RootMe.swf 파일의 전체 액션스크립트이다.
스크립트를 천천히 살펴보자.
먼저 KEY 값으로 rootmeifyoucan 이라는 문자열을 저장하고,
그다음 EmbeddedSWF 에는 RootMe_EmbeddedSWF 라는것을 저장하고 있다.
그다음 RootMe 함수에서는
_loc1_ 변수에 아까봤던 EmbeddedSWF 를 담고,
아래쪽에서 _loc1_ 과 KEY를 XOR 하고 있다.
XOR 함수는 아래쪽에 정의되어있는데 단순히 XOR 하는 역할이다.
여기서 KEY와 XOR 대상이 되는 EmbeddedSWF 가 대체 무엇인지 먼저 찾아봐야 한다.
EmbeddedSWF 는 바로 위에서 찾을 수 있다.
swf 파일안에 binaryData 형태로 숨어있는것을 볼 수 있다.
우클릭 - Export selection - raw 를 선택해서 해당 데이터를 추출이 가능하다.
추출하면 이렇게 bin 파일이 생성된다.
CyberChef(https://gchq.github.io/CyberChef)에서 쉽게 XOR 작업을 할 수 있다.
Input에 추출한 1_RootMe_EmbeddedSWF.bin 를 드래그 앤 드롭해주고,
XOR 에서 Key 값에 rootmeifyoucan 을 넣어주면 결과가 나온다.
결과를 보면 맨 앞에 CWS 라는 글자가 보이는데,
파일 맨 처음에 CWS가 오는것은 SWF 파일의 특징이다.
그러니까 RootMe.swf 파일 안에 또다른 swf 파일이 숨어있었던 것이다.
다운로드 버튼을 눌러 새로운 swf 파일을 저장해준다.
새로 다운로드 받은 download.swf 파일을 다시 FFDEC로 넣어주면
똑같이 액션스크립트를 볼 수 있다.
package
{
import com.hurlant.crypto.hash.MD5;
import com.hurlant.util.Hex;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.external.ExternalInterface;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.ByteArray;
public class Main3 extends Sprite
{
private var _ltn9P:Sprite;
private var _akkO7:Sprite;
private var _61MyZ:Sprite;
private var _vHNiT:Sprite;
private var _9AkQo:uint;
private var _YrqkY:uint;
private var _Q371e:uint;
private var _CKDjY:uint;
private var _e4G79:String;
private var _v6QST:TextField;
private var _k201H:TextField;
private var _txBr6:TextField;
private var _7RSrP:TextField;
public function Main3()
{
this._v6QST = new TextField();
this._k201H = new TextField();
this._txBr6 = new TextField();
this._7RSrP = new TextField();
super();
this._ltn9P = new Sprite();
this._akkO7 = new Sprite();
this._61MyZ = new Sprite();
this._vHNiT = new Sprite();
this._e4G79 = new String("");
this._9AkQo = 11266775;
this._YrqkY = 11146309;
this._Q371e = 8049718;
this._CKDjY = 7884889;
this._ltn9P.graphics.beginFill(this._9AkQo);
this._akkO7.graphics.beginFill(this._YrqkY);
this._61MyZ.graphics.beginFill(this._Q371e);
this._vHNiT.graphics.beginFill(this._CKDjY);
this._ltn9P.graphics.drawRect(0,0,50,50);
this._akkO7.graphics.drawRect(60,0,50,50);
this._61MyZ.graphics.drawRect(0,60,50,50);
this._vHNiT.graphics.drawRect(60,60,50,50);
this._ltn9P.graphics.endFill();
this._akkO7.graphics.endFill();
this._61MyZ.graphics.endFill();
this._vHNiT.graphics.endFill();
this._ltn9P.useHandCursor = true;
this._ltn9P.buttonMode = true;
this._ltn9P.mouseChildren = false;
this._akkO7.useHandCursor = true;
this._akkO7.buttonMode = true;
this._akkO7.mouseChildren = false;
this._61MyZ.useHandCursor = true;
this._61MyZ.buttonMode = true;
this._61MyZ.mouseChildren = false;
this._vHNiT.useHandCursor = true;
this._vHNiT.buttonMode = true;
this._vHNiT.mouseChildren = false;
this.addChild(this._ltn9P);
this.addChild(this._akkO7);
this.addChild(this._61MyZ);
this.addChild(this._vHNiT);
this._v6QST.text = "1";
this._k201H.text = "2";
this._txBr6.text = "3";
this._7RSrP.text = "4";
this._v6QST.x = 10;
this._v6QST.y = 0;
this._k201H.x = 70;
this._k201H.y = 0;
this._txBr6.x = this._v6QST.x;
this._txBr6.y = 60;
this._7RSrP.x = 70;
this._7RSrP.y = this._txBr6.y;
var _loc1_:TextFormat = new TextFormat();
_loc1_.color = 0;
_loc1_.font = "Verdana";
_loc1_.size = 40;
_loc1_.align = "left";
this._v6QST.setTextFormat(_loc1_);
this._k201H.setTextFormat(_loc1_);
this._txBr6.setTextFormat(_loc1_);
this._7RSrP.setTextFormat(_loc1_);
this._ltn9P.addChild(this._v6QST);
this._akkO7.addChild(this._k201H);
this._61MyZ.addChild(this._txBr6);
this._vHNiT.addChild(this._7RSrP);
this._ltn9P.addEventListener(MouseEvent.CLICK,this._1pFaN);
this._akkO7.addEventListener(MouseEvent.CLICK,this._1pFaN);
this._61MyZ.addEventListener(MouseEvent.CLICK,this._1pFaN);
this._vHNiT.addEventListener(MouseEvent.CLICK,this._1pFaN);
}
private function _1pFaN(param1:MouseEvent) : void
{
var _loc3_:uint = 0;
var _loc2_:String = new String("");
switch(param1.currentTarget)
{
case this._ltn9P:
_loc2_ = (this._9AkQo >> 16 & 255).toString(16).toUpperCase();
this._e4G79 += _loc2_;
break;
case this._akkO7:
_loc2_ = (this._YrqkY >> 8 & 255).toString(16).toUpperCase();
this._e4G79 += _loc2_;
break;
case this._61MyZ:
_loc2_ = (this._CKDjY & 255).toString(16).toUpperCase();
this._e4G79 += _loc2_;
break;
case this._vHNiT:
if(this._e4G79.length > 1)
{
this._e4G79 = this._e4G79.slice(0,-1);
}
}
var _loc4_:MD5 = new MD5();
var _loc5_:ByteArray = Hex.toArray(Hex.fromString(this._e4G79.split("").reverse().join("")));
var _loc6_:ByteArray = _loc4_.hash(_loc5_);
if(ExternalInterface.available)
{
ExternalInterface.call("console.log","press");
}
if(this._e4G79.length >= 12)
{
ExternalInterface.call("l1",Hex.fromArray(_loc6_));
this._e4G79 = "";
}
}
}
}
download.swf 파일의 전체 액션스크립트이다.
난독화도 살짝 되어있어서 보기 힘들지만
몇군데 포인트만 확인해주면 된다.
가장먼저 확인할 부분이다.
_v6QST, _K201H, _txBr6, _7RSrp 라는 이름으로 TextField 를 생성하고 있다.
그리고 아래쪽에서 해당 TextField에 각각 1, 2, 3, 4 라는 값을 집어넣고 있다.
이건 그냥 1, 2, 3, 4가 쓰여져 있는 네모난 버튼을 생성한 것이라고 생각하면 된다.
아래쪽에서는 _v6QST, _K201H, _txBr6, _7RSrp를 사용자가 마우스로 클릭할 경우
_Itn9P, _akk07, _61MyZ, _vHNiT 라는 이벤트로 받아들이고 있는 모습이 보인다.
그다음 기억할 부분은 이 부분이다.
_e4G79 변수에 빈 스트링을 저장하고,
_9AkQo 변수에 11266775, _YrqkY 변수에 11146309
_Q371e 변수에 8049718, _CKDjY 변수에 7884889 를 저장하고 있다.
마지막으로 확인할 부분인 이 switch 문이다.
위에서 봤던 _Itn9P, _akk07, _61MyZ, _vHNiT 가 각각의 case로 들어가 있고,
각각의 case 마다 역시 위에서 봤던
_9AkQo, _YrqkY, _CKDjY 변수에 저장했던 값으로 뭔가 계산을 하고 있는 모습이다.
일단 따라서 계산을 해본다.
각각의 case는 버튼 1, 2, 3, 4를 눌렀을때 일어나는 일들이다.
this._9AkQo >> 16 & 255 는 16진수로 AB
this._YrqkY >> 8 & 255 는 16진수로 14
this._CKDjY & 255 는 16진수로 59이다.
마지막에 있는 _vHNiT 케이스는 맨 뒤에있는 한글자를 잘라내는 역할을 한다.
사용자가 1, 2, 3, 4 버튼을 누르면 각 버튼에 맞는 case 가 실행되어서
그 결과가 _e4G79 변수에 저장되고,
reverse() 를 이용해 _e4G79를 거꾸로 써준 뒤에
최종적으로 거꾸로 쓴것의 md5 해시값이 dbbcd6ee441aa6d2889e6e3cae6adebe 이면 인증에 성공하는 것이다.
그럼 이 문제에서 마지막으로 _e4G79 에 거꾸로 저장되었던 값은 4141955195AA 이니
버튼을 적절하게 눌러서 AA5915591414 가 나오게 하면 된다.
1 | AB |
2 | 14 |
3 | 59 |
4 | 맨 뒤 글자 삭제 |
간단하게 각 버튼을 눌렀을때 일어나는 일들은 표와 같다.
위 표를 참고해서 사용자가 버튼을 어떻게 눌러야하는지 추측해보면
누른 버튼(누적) | 출력값 |
1 | AB |
14 | A |
141 | AAB |
1414 | AA |
14143 | AA59 |
141432 | AA5914 |
1414324 | AA591 |
14143243 | AA59159 |
141432434 | AA5915 |
1414324343 | AA591559 |
14143243432 | AA59155914 |
141432434322 | AA5915591414 |
141432434322 인것을 알 수 있다.
해당 값을 제출하면, 문제를 풀 수 있다.
'워게임 > Root Me' 카테고리의 다른 글
[Root Me] JSON Web Token - Introduction - 웹해킹 / JWT (70) | 2022.07.06 |
---|---|
[Root Me] ELF C++ - 0 protection - 리버싱 / IDA / GDB (64) | 2022.06.30 |
[Root Me] Insecure Code Management - 웹해킹/ Directory Listing / git (42) | 2022.06.25 |
[Root Me] PE x86 - 0 protection - 리버싱 / Ollydbg (56) | 2022.06.22 |
[Root Me] ELF x86 - Basic - 리버싱 / IDA (57) | 2022.06.18 |