作者:
畢月烏
·
2015/05/27 10:06
0x00 前言
雖然現在的應用開發越來越趨向於web應用,大型軟體也大量使用了現有的框架,隨著現有框架和引擎的完善,絕大多數安全問題已經被解決。但是遇到一些定製需求時,開發人員還是不得不從底層一點點進行設計。這時,沒有安全經驗的開發人員很容易犯下錯誤,導致嚴重的安全隱患。本文以一款自主引擎的大型網路遊戲為例,展示開發中容易被忽略的隱患。
Lua作為一種功能強大又輕量級的指令碼語言,可以非常容易地嵌入其他語言的程式中,越來越多的遊戲引擎使用了Lua來實現具體的邏輯過程。這使得我們避開復雜的逆向工程直接分析遊戲功能的邏輯成為可能。Lua指令碼的一些特性甚至讓我們可以直接除錯遊戲中的指令碼,比如使用著名的Decoda Lua偵錯程式。如果配合簡單逆向資源包等手段,我們可以輕鬆獲取遊戲中的指令碼程式碼(有可能需要反編譯),這使得我們的分析過程進一步簡化,由黑盒變成了半白盒。
首先我們對遊戲進行了簡單的逆向來簡化指令碼分析的難度。透過逆向工程得知,遊戲引擎對Lua指令碼的依賴度非常高,C++只是用於基本的類和方法以及遊戲渲染,幾乎所有邏輯都是由指令碼完成。同時為了更加方便我們的分析工作,對遊戲的資原始檔包進行了簡單的逆向,成功解包出了遊戲客戶端的Lua指令碼,至此,準備工作全部完成。
下列例子按照被發現的時間排序:
- 隱身
- 任意傳送
- 大併發刷金幣
- 使用不存在的道具
- 遠端程式碼執行
0x01 隱身(意料之外的“正常功能”)
這個嚴格來說不算是漏洞,只是一個非常有意思的BUG。
在開發功能的時候,開發人員通常只會考慮實現功能和功能內部基本的安全性。這樣並沒有錯,但是卻有一種情況是這種開發模式無法防禦的:正常功能特性被濫用。一個典型的案例就是下面這個隱身的BUG。紅框中玩家模型處於不可見狀態,無法透過點選模型選中,在選中他之前也無法發現他的存在。
這種隱身從邏輯角度來看肯定是不正常的,但不是從程式碼角度來看,它卻又是正常的,所以在測試中也很難被發現。我們來看看實現隱身的程式碼:
這段程式碼其實只做了一件事情,那就是頻繁的修改人物外觀的顯示狀態,那麼為什麼人物會消失呢?這就得從基礎說起了:遊戲的渲染機制是每次模型外觀出現改變,將模型刪除,使用新的引數重新建立模型。這樣,在刪除模型和重新建立之間有一個時間差,這段時間內對應的玩家模型是不存在的!
在正常情況下這並不是什麼大問題,但是如上面那一段程式碼,如果極其頻繁地修改模型,就會導致模型根本沒有機會顯示出來,而這種問題如果在設計階段沒有引起注意,到了開發階段就很難被發現。
0x02 任意傳送(危險的未完成功能)
這是本文中最有意思的漏洞了。某一次大更新後,例行解包客戶端看看更新了什麼內容,無意中看到一個伺服器指令碼(不要驚訝,我也不知道為什麼,但是客戶端裡確實有一部分伺服器指令碼~)中增加了一個名為OnCanJoinNormalMap的遠端呼叫函式,看名字似乎是跟進入地圖有關。
難道這是傳說中的任意傳送!小夥伴們驚呆了!實際測試一下,真的可以傳送到任意指定座標!
在一陣驚喜過後,腦洞大開的小夥伴們還寫了一個完整的利用工具來實現真正意義上的任意傳送(雖然不到一週時間漏洞就修了~):
修復以後的指令碼變成了這樣:
從註釋內容來看,這個介面似乎在上線時根本沒有實際使用,這也是開發人員經常犯的錯誤之一——上線一些根本沒有使用但是卻已經實現的介面。由於這些介面出於開發階段,可能並沒有完備的防護機制,比如上圖這個介面實現,沒有任何過濾。這些介面一旦被意外找到,事情就開始變得不可控了。
0x03 使用道具技能(不同操作共用底層實現帶來的風險)
我們知道,遊戲裡通常都有一些可使用的道具,但是很少有人關心過物品使用這一過程是如何實現的,其中可能存在什麼問題。
下面我們來看看物品使用的過程到底發生了什麼。
首先點選物品的函式是這樣的:
函式結尾的OnUseItem才是使用物品的關鍵,那麼這個函式又是如何實現的呢?
可以看到一些物品的處理是由UseItem函式完成,這個函式不再是lua寫的函式,而是C++函式了,部分程式碼如下:
#!c++
KItem::UseItem(DWORD dwBox, DWORD dwX, KTarget& rTarget)
{
......
pItem = GetItem(dwBox, dwX);
......
if (pItem->m_dwSkillID != 0 && pItem->m_dwScriptID == 0)
{
eRetCode = UseItemSkill(pItem, rTarget);
KG_PROCESS_ERROR_RET_CODE(eRetCode == uircSuccess, eRetCode);
}
.....
}
這裡有一個不太尋常的呼叫:UseItemSkill,而從OnUseItem函式中我們也可以看到skill的影子。
從程式碼中可以看出item物件存在item.dwSkillID, item.dwSkillLevel兩個屬性,難道說這是使用物品實際上是一種技能釋放?也就是說我們可以透過直接呼叫技能釋放介面來使用一些我們根本沒有的物品?
測試證明,確實是這樣的。
一個20級身上空空如也的小號,在執行了OnAddOnUseSkill(4894,1)後獲得了必須裝備某道具才能獲得的效果(4894為該物品對應的技能ID)。
在使用物品的方法中存在校驗物品數量的程式碼,但是釋放技能卻不需要校驗物品存在,透過直接釋放物品對應的技能,我們成功繞過了物品的校驗,使用了玩家身上並不存在的物品。
在一些設計中,不同的功能可能會存在同一套底層的實現,如果在底層實現中沒有對呼叫來源進行充分驗證,就有可能繞過前端,直接呼叫底層實現方法來繞過原本的保護機制。
0x04 大併發刷金幣(大併發帶來的風險以及缺乏保護的未公開介面)
大併發的問題其實很常見,也在很早的時候就有過分析。但是在一些正常情況下不可能存在併發的位置,程式設計師很有可能忽略了對大併發的防護。
遊戲中有一個幫會福利,每週幫會可以使用幫會資金的一部分作為“工資”發給幫會成員,而幫會資金的來源,可以是任務獎勵,也可以是玩家捐贈。
這裡出現了一進(捐贈)一出(發工資),如果捐贈的介面存在併發問題,那麼我們就有可能獲取到額外的金幣。
透過遊戲介面的按鈕,找到了捐贈的介面,於是開始了邪惡的計劃:
這是原本的幫會資金和個人財富
執行一下我們的邪惡程式碼
身上的錢變成了負的90萬!而幫會資金卻增加了90萬!我們成功進行了一次透支操作,接下來只需要用其他號把幫會里的資金取出就可以消費了~活脫脫的信用卡套現!
這是一個非常容易被忽略的地方。由於正常玩家操作都是透過介面點選,輸入金額進行操作,根本不可能出現併發請求,作為一個不公開的內部介面,程式設計師對此處毫不設防。但是介面被挖掘出來,用指令碼來實現,大併發的問題就凸顯出來。
0x05 遠端程式碼執行(永遠不要相信使用者輸入的資訊)
這恐怕是所有問題中最嚴重的了,也是很難被發現的一處。在遊戲引擎中,經常需要透過指令碼進行一些操作,比如建立介面元素後可能需要指令碼完成初始化,這次的問題就出現在了初始化指令碼上。
通常大型網遊為了資訊傳遞更加方便,聊天欄是允許傳送物品連結的,這樣一來就會引入一段程式碼專門用於連結的生成,通常會使用指令碼對生成的連結進行初始化,比如下面這個函式:
看似很正常,但是我們發現其中出現了“script=”,這裡應該就是附帶的初始化指令碼了。原本的指令碼程式碼只是設定控制元件的屬性,但是熟悉sql注入的朋友可能會發現一個問題:這裡的指令碼程式碼是直接用引數拼接而成,似乎沒有過濾。我們可以嘗試構造一個特殊的內容截斷原本的指令碼,執行我們自己的程式碼。
遊戲中玩家聊天資訊傳送是一個自定義表結構,閱讀接收資訊的指令碼得知,MakeEventLink函式的4個引數中szText, szName, szLinkInfo均是直接來源於接收到的聊天資料。接下來我們開始構造攻擊語句
使用引號閉合語句,寫入我們自己的程式碼,這裡要注意閉合最後多餘的一個引號,否則會導致語法錯誤無法執行。
效果好象不錯的樣子
由於問題出在客戶端接收聊天資訊的程式碼裡,所以這個漏洞可以被遠端利用,也就是說在許可權足夠的情況下甚至可以呼叫os庫遠端格盤,不愧是居家旅行,殺人滅口必備漏洞!
0x06 總結
開發過程中有太多容易被忽視的安全問題,其中絕大多數都是過分“信任”造成的。信任同事的程式碼,信任自己的程式碼,信任呼叫來源的合法性,信任使用者的操作,正是這些原本不應該的信任給攻擊者留下了攻擊的空間。開發原本就是一個創造性的工作,我們更應該去懷疑而不是信任。懷疑一切也許並不能提高開發效率,但在關鍵時刻卻可以挽救整個系統。
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!