逆向小白之解決Focusky的離線登入問題

patanka發表於2020-12-10

前言

Focusky是一款非常優秀的演示製作工具,類似於Prezi,但功能更強大,對中文的支援更好。所以已經成了我做演示的第一選擇,但有一個問題一直困擾我:公司使用的內部網路是不能連線到網際網路的,而Focusky的匯出等功能必須要登入才能使用,這就給我的講義的分享造成了障礙。培訓部門一直要求我把講義分享出來,我每次發個PDF稿過去,結果都被批評為不願分享。於是就想找個辦法解決一下這個問題。


提示:以下內容僅供技術學習參考,請支援優質國產正版軟體。

一、初步思路

最開始想到的思路是抓包分析,然後在內網上搭一個服務模擬響應,從而解決內網登陸問題,但是想想覺得這個方案難以實現,內網管理太嚴格,不可能隨便讓你架個服務的,還要修改DNS之類,在本機搭也很麻煩。(其實還是自己不太會 _

於是決定還是逆向破解一下好了,把需要登入驗證的地方繞開就好了呀。於是按這個思路開動。

二、逆向嘗試

1.初探

先祭出x64dbg,準備對Focusky.exe 和 fs.exe進行跟蹤,跟了好一會才發現不太對勁,搞了半天fs.exe不是正常的可執行檔案。仔細觀察了一番,發現了Adobe AIR的資料夾,原來這是用Adobe AIR技術開發的ActionScript3應用,就是俗稱Flash的東東。

檢視了fs.exe的檔案頭,發現了CWS的字樣,這不就是swf檔案麼。看樣子要用到SWF逆向了。

2.再探

SWF逆向對我這個小白來說也是新東西,當然是先搜啊搜啊的呀,下了不少所謂的各種工具,最後發現還是JPEXS Free Flash Decompiler 最管用。這個軟體很良心,完全開源,功能強勁。

把fs.exe字尾改成swf,用FFDec開啟,果然是swf的喔。但是跟想象中不一樣的,沒有發現什麼資源和程式碼,只有一個DefineBinaryData和一些少量的指令碼。
在這裡插入圖片描述
後來在反編譯的指令碼里看到:

 private function init() : void
  {
     context = new LoaderContext();
     context.allowCodeImport = true;
     loader = new Loader();
     loader.contentLoaderInfo.addEventListener("complete",onComplete);
     loader.contentLoaderInfo.addEventListener("ioError",onError);
     loader.loadBytes(new FocuskyAirContentData(),context);
  }

果然又巢狀了一層。

3.三探

於是把DefineBinaryData匯出成一個swf檔案再開啟,這下一切都出現了。指令碼和資源都一目瞭然了。

這樣很快就可以找到匯出EXE的PublishWindow的指令碼位置,可以找到幾個判斷的地方,比如:
在這裡插入圖片描述
對應的P-Code是:
在這裡插入圖片描述
這時只要點選edit p-code按鈕就可以對這個p-code修改了,改成iffalse ofs0059就可以實現不用登陸也可以了。

我以為只要儲存這個改過的SWF檔案,然後匯入fs.exe(實際也是swf檔案)就應該可以了。事實證明我太天真了,做完這些,再啟動Focusky就啟動不了啊。囧

不知為什麼會這樣,難道也有什麼自校驗之類的?還是FFDec修改儲存的SWF檔案有錯誤?總之該怎麼辦呢?

4.初捷

後來就想啊,這些檔案不是都要載入到記憶體來執行的麼,改檔案不行那改記憶體行不行呢?試試唄

又祭出了x64dbg,載入後在記憶體中搜尋對應的P-Code的hexdata
在這裡插入圖片描述
在這裡插入圖片描述
找到了一個唯一的地址,把修改後的P-code程式碼替換,這裡只改一個位元組就行了。

然後再執行試一下,奏效!不再提示需要登陸了!

當然,還不是很完美,因為會彈出要升級的視窗。不過這個思路已經通了,只要再找到類似的判斷點改掉就行了。

很快記憶體中需要修改的地方就都就位了,接下來就是要考慮怎麼打這個記憶體補丁了,畢竟不能每次都祭出x64dbg麼,還是需要簡單點才好啊。

5.試手

打記憶體補丁我只會一種方法,那就是用Dll劫持。試了一下version.dll,果然可以利用。於是用上Aheadlib,開啟VS,開始寫程式碼呀。

與固定記憶體地址的打補丁稍有區別的是,由於每次執行記憶體中這個SWF檔案的載入地址都是變化的,所以需要先搜尋指定的特徵碼,找到後再改。特徵碼當然就是P-Code對應的hexdata了,查詢演算法本來想試一試自己實現一下sunday的演算法,不過後來發現了一個更簡單的方法。

這個程式碼段所在的記憶體區域的大小是固定的,雖然地址每次都變,但大小一直不變。並且需要修改的地方相對於這個區域的偏移是固定的。這樣就只需要搜尋這個大小的記憶體區域,屬性為PRV、RW的就可以直接打補丁了。

以下是部分程式碼:

DWORD WINAPI PatchThread()
{
		DWORD dwProcessId;
		dwProcessId = GetCurrentProcessId();

		HANDLE hProcess;
		hProcess = OpenProcess(PROCESS_ALL_ACCESS,false,dwProcessId);

		ULONGLONG dwAddrStart = 0x0;
		ULONGLONG dwAddrEnd = 0x7FFFFFFFFFFF;
		ULONGLONG dwAddrCur = dwAddrStart;
		ULONGLONG dwOffset = 0x3409F4; 

		MEMORY_BASIC_INFORMATION mbi;
		memset(&mbi, 0, sizeof(MEMORY_BASIC_INFORMATION));
		while(true){
			dwAddrCur = dwAddrStart;
			while(dwAddrCur<dwAddrEnd){
				if (VirtualQueryEx(hProcess, (LPCVOID)dwAddrCur, &mbi, sizeof(mbi)) > 0)
				{
					if (MEM_COMMIT == mbi.State && MEM_PRIVATE == mbi.Type && 
						PAGE_READWRITE == mbi.Protect && 
						mbi.RegionSize == 0x17B0000//the region size that include the code to patch
						)
						{
							//find it, do patch
							ULONGLONG AddrPatch;
							AddrPatch = (ULONGLONG)mbi.BaseAddress+dwOffset;
							if(CheckTheMem(){
								DoPatch();
								Debug("Patch is ok! \n");
								return 0;
							}
							return 0;
						}else{
							dwAddrCur += mbi.RegionSize ;
						}
				}else{
					dwAddrCur=dwAddrStart;
					Debug("VirtualQueryEx error:%d",GetLastError());
				}
			}
		Debug("All memory is checked. \n") ;
		Sleep(200);
		}
	return 0;
}
// 入口函式
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		DisableThreadLibraryCalls(hModule);
		CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)PatchThread,NULL,0,NULL);
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
	}
	return TRUE;
}

很快這個用於打補丁的version.dll就寫好了,放到Focusky的目錄,試一下。正常啟動,試著匯出exe,不會提示需要登陸了,終於可以離線也可匯出EXE了。成功!

6.再戰

本來到這裡我的初始目標已經實現了,解決了Focusky的離線使用問題。但是,呵呵,人都是有點好奇心的麼。我就想啊,我能不能乾脆讓它登入好了,就是離線也能識別成登入狀態,這樣其他需要登入的功能不是也能使用了不是麼。

於是仔細琢磨了一下,發現好像是可以實現的啊,只要把login的指令碼改動一下,把需要聯網獲取的使用者狀態變成本地獲取不就可以了麼。

於是就開始動手實踐了,當然過程比較漫長啦。畢竟我對ActionScript一點都不熟,再加上沒辦法直接寫AS程式碼,而是需要寫P-Code的程式碼,就是哪些Push,setlocal 的東西,所以還是花費了一定的時間。最後的使用者登入程式碼大概改成類似這個樣子:

      public function login(u:String, p:String) : void
      {
         _logined = true;
         var file:File = File.applicationDirectory.resolvePath("user.xml");
         stream = new FileStream();
         stream.open(file,"read");
         data= new XML(stream.readUTFBytes(stream.bytesAvailable));
         readData(data);
         stream.close();
         Global.focusky.gSignalManager.signaled("SIGIN_IN_SUCCESS",this);
      }

當然這是用AS3程式碼示意的,實際是一堆類似下面的東西:

	getlocal_0
	pushscope
	newactivation
	dup
	setlocal_3
	pushscope
	getlocal_0
	getlocal_3
	swap
	setslot 3
	findproperty Qname(ProtectedNamespace("com.wonderidea.focusky.air.user:User"),"_logined")
	swap

user.xml是一個XML格式的文字,這個文字里麵包含了使用者的基本資訊,格式當然是抓的我自己的登入使用者資訊。示例如下:

	<ID>4582165</ID>
	<Email>master@focusky.com</Email>
	<userName>master</userName>
	…………

這些修改還是一樣通過version.dll來打記憶體補丁。最後的結果也是不錯的,可以保持和正常聯網登入一樣的效果。當然那些需要聯網使用的資源是用不了的。

總結

整個解決離線登入問題的過程還是很有意思的,第一次接觸了ActionScript,感覺這還真是個好東東。只是已經很小眾了,有點可惜。

這種打補丁的方式有一個不穩定的弊端,那就是有可能你打補丁的修改和原程式的讀取同時發生,這時就會出錯了,程式表現得很怪異。所以我通常需要把Focusky的自動登入關掉,然後把打補丁的時間延後到程式穩定啟動後。在我的機器上是延後了500ms左右。這樣基本上算是比較穩定。

最後,本文只為解決自己使用的痛點。請大家支援國產優質軟體。

寫作不易,如果覺得不錯的,請關注評論一下。

相關文章