notepad
ps.在做这道题之前请注意如下内容:
If “notepad.exe” in #flareon4 on Win10 crashes just patch 0x01013C54 to make it “push 0x6e2bca17”
ps.2 以及,flareon上的原装题目大概率只适用于win7及以下部分版本的windows,建议部分无法正常打开的题目替换为buu上的移植版食用
打开一看,除了一开始会显示某些莫名其妙的时间之外,似乎就是一个普通的32位记事板?
虽然不知道它在干什么,但是当我们点进去的时候,我们会看到start调用了一些东西:
strcpy(v27, "%USERPROFILE%");
strcpy(v8, "\\flareon2016challenge");
strcpy(v10, "ImageHlp.dll");
strcpy(v9, "CheckSumMappedFile");
strcpy(v15, "user32.dll");
strcpy(v12, "MessageBoxA");
FlareOn4是2017年的,我们要把上一届比赛(16年)的题目给弄下来放自己的用户文件夹里头。试着再度运行,但是除了什么跟刚才一样之外都没发生……等下,什么时候多出来一个key.bin
?
虽然但是,我们回头先看看程序本体,主函数里面出现了成吨类似的结构:
v16[0] = sub_1015310((int)v5, 0x63D6C065);
动态调试可以发现其实际上是对kernel32.dll
库的调用,每次将一个函数的地址压栈。而我们将整个调用链拉出来会发现,start部分似乎就是将上述文件夹中符合条件(并且这个条件多半和文件的更新日期,也就是题目文件编译的日期有关系)的程序提出来,塞点什么。
Stack[00000840]:000DFEF0 aUser32Dll_0 db 'user32.dll',0
Stack[00000840]:000DFEFB db 0
Stack[00000840]:000DFEFC dd offset kernel32_FindFirstFileA
Stack[00000840]:000DFF00 dd offset kernel32_FindNextFileA
Stack[00000840]:000DFF04 dd offset kernel32_FindClose
Stack[00000840]:000DFF08 dd 0
Stack[00000840]:000DFF0C dd offset kernel32_CreateFileA
Stack[00000840]:000DFF10 dd offset kernel32_CreateFileMappingA
Stack[00000840]:000DFF14 dd offset kernel32_MapViewOfFile
Stack[00000840]:000DFF18 dd offset kernel32_CloseHandle
Stack[00000840]:000DFF1C dd offset kernel32_WriteFile
Stack[00000840]:000DFF20 dd offset kernel32_GetFileSize
Stack[00000840]:000DFF24 dd offset kernel32_FlushViewOfFile
Stack[00000840]:000DFF28 dd offset kernel32_LoadLibraryA
Stack[00000840]:000DFF2C dd offset kernel32_GetProcAddress
Stack[00000840]:000DFF30 dd 0
Stack[00000840]:000DFF34 dd offset kernel32_GetModuleHandleA
Stack[00000840]:000DFF38 dd offset kernel32_UnmapViewOfFile
Stack[00000840]:000DFF3C dd 0
Stack[00000840]:000DFF40 dd 0
Stack[00000840]:000DFF44 dd 0
Stack[00000840]:000DFF48 dd 0
Stack[00000840]:000DFF4C dd offset kernel32_ExpandEnvironmentStringsA
Stack[00000840]:000DFF50 dd offset kernel32_FileTimeToSystemTime
Stack[00000840]:000DFF54 dd offset kernel32_GetTimeFormatA
Stack[00000840]:000DFF58 dd offset kernel32_GetDateFormatA
Stack[00000840]:000DFF5C dd offset kernel32_ReadFile
问题来了,是哪几个程序呢?比较简单粗暴的办法是挨个试一遍,最后,发现challenge1、DudeLocker、khaki和unknown在题目的notepad运行之后,都会有一些莫名其妙的弹窗,而唯独unknown的弹窗是”What’s wrong with my keyfile?”。
不用猜,unknown肯定是最后一个打开的程序,而第一个肯定是notepad.exe。至于剩下几个的顺序,一种办法是挨个试一遍,三个相异元素的全排列而已(实际上你直接按首字母排序的话,顺序运行下来就是一遍过)。还有一种是按时间查找,但是因为部分题目使用的是buu的移植版,使用上一个打开的程序的弹窗里的时间可能还是对不上号。
至于最后一种,每次运行完上一个程序,key.bin里肯定会多出些什么,直接将里面每次多出的内容到还没打开的程序里挨个找一遍,绝对会找到匹配的结果,以DudeLocker为例:
反正怎么顺手怎么来。最后的flag一定是:
谁家好人会用自己出过的题目当题目内容,还放在C盘占空间的啊kora
shell
看到代码的一瞬间,我有点害怕这玩意会不会有什么php特性:
<?php $o__o_ = base64_decode(
"QAYLHhIJbzIQC..."
);
$o_o = isset($_POST["o_o"]) ? $_POST["o_o"] : "";
$o_o = md5($o_o) . substr(MD5(strrev($o_o)), 0, strlen($o_o));
for ($o___o = 0; $o___o < 2268; $o___o++) {
$o__o_[$o___o] = chr((ord($o__o_[$o___o]) ^ ord($o_o[$o___o])) % 256);
$o_o .= $o__o_[$o___o];
}
if (MD5($o__o_) == "43a141570e0c926e0e3673216a4dd73d") {
if (isset($_POST["o_o"])) {
@setcookie("o_o", $_POST["o_o"]);
}
$o___o = create_function("", $o__o_);
unset($o_o, $o__o_);
$o___o();
} else {
echo '<form method="post" action="shell.php"><input type="text" name="o_o" value=""/><input type="submit" value=">"/></form>';
} ?>
但好在事实上并没有。不过这么长的密文看上去有点哈人,还拿字符串正反两遍的md5的合体做key,64位,看着更哈人了。不过就没啥招解决吗?
注意看这一句代码:
$o_o .= $o__o_[$o___o];
如果说这个for要正常运行不至于抛出索引越界(毕竟key只有64位,但这for的上限足足2268),那这个.=
只剩下一种可能:拿加密后的字符当key的末尾继续加密。也就是说,第65位开始都可以用前文直接爆。
可问题在于:在生成后续的key之前,前文也必须得被加密啊,所以根本问题还是怎么爆出原文和key。
一种很浅显的办法就是遍历所有可能用到的php函数,因为密文解密的结果实际上就是php代码:
@setcookie("o_o", $_POST["o_o"]);
create_function("", $o__o_);
但是逆向手可能很难知道所有可能的CTF常用php函数,所以赛场上得去请教web手或者挨个试一遍。
另一种更简单的办法,MD5本身只有16个字符的字符集,我们直接循环前64位,对于每一位,根据之前的算法来看,从第一次根据key加密过后,之后每64位的加密都是以前64位为key加密,也就是说,我们爆破每一位key的时候,只需要向后以64为间隔循环异或,检测值是否为阳间字符就行了。(思路参考Thomasd的博客和mkhdznfq的博客)
from base64 import b64decode
f = open('output.txt', 'w')
crypted = b64decode("QAYLHhIJbzIQC...")
def check(offset, tmp):
for sit in range(offset, 2268, 64): # 以64为间隔开始遍历,判断是否为正常字符
tmp = (crypted[sit] ^ tmp) % 256 # 继续以当前加密字符为key向后遍历
if 32 <= tmp < 127 or tmp == 10 or tmp == 13: # 若为正常字符
continue
return -1
return 1
for i in range(64): # 剩下的事情就是遍历md5字符集了
output = 'md5set[' + str(i) + ']=['
for j in md5charset:
if check(i, ord(j)) == 1:
output += '\'' + j + '\', '
output = output[:-2] + ']'
print(output, file=f)
f.close()
但是如果是纯暴力,最后爆出来的产物还是会产生一些不定结果。但既然都暴力了那为什么不爆到底呢?递归生成树,挨个试一遍!
然而最后发现根本跑不通。究其原因还是64位的超长key带来的巨大运算量让人难以接受(足足10^28种可能性)。那剩下的只能是先手动调整key,看看能不能产生一些可能可以看见的结果:
from base64 import b64decode
from hashlib import *
crypted = b64decode(
"QAYLHhIJbzIQCl...")
key = "..."
key = [char for char in key]
output = ""
for i in range(len(crypted)):
output += chr((crypted[i] ^ ord(key[i])) % 256)
key += output[i]
print(output)
这个过程是相当漫长与折磨的。就算你脑子里有一万种php指令,调整key也得半天。所以我们需要把可能的明文也打印出来,看看到底啥玩意是可能整理出一个可用的cookie的明文,然后再找对应的key:
from base64 import b64decode
f = open('output.txt', 'w')
f2 = open('output_origin.txt', 'w')
crypted = b64decode("QAYLHhIJbzIQC...")
md5charset = "0123456789abcdef"
def check(offset, tmp):
for sit in range(offset, 2268, 64): # 以64为间隔开始遍历,判断是否为正常字符
tmp = (crypted[sit] ^ tmp) % 256 # 继续以当前加密字符为key向后遍历
if 32 <= tmp < 127 or tmp == 10 or tmp == 13: # 若为正常字符
continue
return -1
return 1
def exchange(tmp):
if 32 <= tmp < 127:
return chr(tmp)
else:
return '\\x' + str(tmp)
for i in range(64): # 剩下的事情就是遍历md5字符集了
output = 'md5set[' + str(i) + ']=['
output_origin = 'original_set[' + str(i) + ']=['
for j in md5charset:
if check(i, ord(j)) == 1:
output += '\'' + j + '\', '
output_origin += '\'' + exchange((crypted[i] ^ ord(j)) % 256) + '\', '
output = output[:-2] + ']'
output_origin = output_origin[:-2] + ']'
print(output, file=f)
print(output_origin, file=f2)
f.close()
f2.close()
然后再把生成的可能明文和可能key一个个比对,看看什么样的key组合可以凑出一个算是cookie的明文。这个过程需要比较繁琐的手工,但没办法,谁叫电脑也不一定跑得动呢?
手操得出key=db6952b84a49b934acb436418ad9d93d237df05769afc796d067bccb379f2cac
,再塞回去得到cookie:
$d='';
$key = "";
if (isset($_POST['o_o']))
$key = $_POST['o_o'];
if (isset($_POST['hint']))
$d = "www.p01.org";
if (isset($_POST['t'])) {
if ($_POST['t'] == 'c') {
$d = base64_decode('SDcGHg1feV...');
$key = preg_replace('/(.)../', '$1', $key);
}
if ($_POST['t'] == 's') {
$d = base64_decode('VBArMg1H...');
$key = preg_replace('/.(.)./', '$1', $key);
}
if ($_POST['t'] == 'w') {
$d = base64_decode('DycdGg1hYj...');
$key = preg_replace('/..(.)/', '$1', $key);
}
while(strlen($key) < strlen($d))
$key = $key.$key;
$d = $d ^ $key;
}
if (strlen($d))
echo $d;
else
echo '<form action="shell.php" method="post"><input type="hidden" name="o_o" value="'.$key.'"><input type="radio" name="t" value="c"> Raytraced Checkboard<br> <input type="radio" name="t" value="s"> p01 256b Starfield<br> <input type="radio" name="t" value="w"> Wolfensteiny<br><input type="submit" value="Show"/></form>';
但是这里的preg_replace
又在干什么?其实通过在线编译,我们可以知道这里的每一项preg_replace
都分别在取key中每3个字符的第一/二/三个,分别组成一个新的key用于进行异或:
剩下的事情就好办了,爆!
啊?!
所以这个POST的key传进来的时候还带变的是吗?这下多爆三个密文了,这么玩是吧.jpg,更悲惨的是这回不是md5了,这下任何字符都有可能了。
而且,还有一件可怕的事情,我们到现在依旧不知道flag是藏在代码里还是就是哪一个环节的key。
不过嘛,思路还是有的。注意这里:
if (isset($_POST['hint']))
$d = "www.p01.org";
我们点进去一看,发现这是个压行老哥的博客,专注压行二十年。但我们不止要查看相关的代码,从直觉上看,上述密文拼接起来有足足998个字符,而整个网页字数相近且符合出题日期,并且含有有效代码的文章有:DWITTER SON1K、WOLFENSTEINY、TEA STORM等。而它们之中的代码都是html。顺着这条线索,我们推测原文当中应当含有“<body”之类的,html特有的字段。并且,由于第三部分的长度比第1、2段甚至多出整整42个字符,我们甚至可以直接开始暴力循环密文后42位,看看有没有什么捞的着的东西:
from base64 import b64decode
code = [[] for i in range(3)]
code[2] = b64decode('DycdGg1hY...')
origin = "</body>"
origin = [ord(char) for char in origin]
# 密文最短只有316位
for i in range(316, len(code[2]) - len(origin)):
tmp = ""
for j in range(len(origin)):
tmp += chr(code[2][i + j] ^ origin[j])
print("offset %i decode is %s" % (i, tmp))
最后结果也就三个能看的,还都是乱码。但是,秉持着不会做就瞎蒙乱猜的原则,这三个能看的结果当中,也就一个能和网页沾上边的:部分密文^'</body>'=@a-.m3Q
且慢,如果我们看一看原比赛的网址:
__@__a__-__.__m__3__Q
flare-on.com
* * * *
W……T……F……什么顶级抽象脑洞
然后我们有端联想一下,这玩意的末位如果真是“@+比赛官网”,那我们不就可以通过暴力向前遍历完整版密文找到key可能的长度,进而暴力遍历可能的字符找到flag吗?所以我们直接对着三段密文挨个试一遍:
from base64 import b64decode
code = [[] for i in range(3)]
code[0] = b64decode('SDcGHg...')
code[1] = b64decode('VBArMg1H...')
code[2] = b64decode('DycdGg1h...')
possible_key = [[] for i in range(3)] # @flare-on.com,但根据原算法,一份key拆三份用
possible_key[0] = 'froc'
possible_key[1] = 'leno'
possible_key[2] = '@a-.m'
f = open("burp_second_output.txt", 'w')
for i in range(3):
f.write("in code %i:\n" % i)
for j in range(len(code[i]) - len(possible_key[i])):
tmp = ""
possible_tag = 1
for k in range(len(possible_key[i])):
c = (code[i][j+k] ^ ord(possible_key[i][k])) % 256
tmp += chr(c)
if c < 32 or c > 126:
possible_tag = 0
if possible_tag == 1:
f.write(" offset %i decode is %s\n" % (j, tmp))
于是我们可以清晰地看到一些字段中浮现了html相关字段,以及这位p01老哥的部分代码字段。其中以这里尤为明显:
考虑到这个箭头应该不太可能凭空出现(实际上那是html的注释标志),我们姑且认为flag长度在39左右。这样的话,每部分key还有8~9位未知。于是我们开始尝试爆破,再结合wolfensteiny文章的源代码,原文的算法之类的,以及之前的暴力代码多跑几遍一顿瞎猜:
from base64 import b64decode
f = open('output2.txt', 'w')
f2 = open('output2_origin.txt', 'w')
code = [[] for i in range(3)] # 继续拆成三个解
code[0] = b64decode('SDcGHg1f...')
code[1] = b64decode('VBArMg1HY...')
code[2] = b64decode('DycdGg1hYjl8...')
keylen = [13, 13, 13]
def check(offset, enc, tmp, keyl): # 输入当前偏移量、待解密字符串、当前待判断key位,当前key长度
for sit in range(offset, len(enc), keyl):
c = (enc[sit] ^ tmp) % 256
if 32 <= c <= 126 or c == 10 or c == 13:
continue
else:
return -1
return 1
def exchange(tmp):
if 32 <= tmp <= 126:
return chr(tmp)
else:
return '\\x' + str(tmp)
for i in range(9): # key剩余位数第i位
for k in range(3): # 第k个密文串/key
output = 'possible_key[' + str(3*i+k) + ']=['
output_origin = 'original_set[' + str(3*i+k) + ']=['
for j in range(32, 127): # ascii表第j个可用字符
if check(i, code[k], j, keylen[k]) == 1:
output += '\'' + chr(j) + '\', '
output_origin += '\'' + exchange((code[k][i] ^ j) % 256) + '\', '
output = output + ']'
output_origin = output_origin + ']'
print(output, file=f)
print(output_origin, file=f2)
f.close()
f2.close()
考虑到剩下的密文也极有可能是html代码,那么开头的字符也很有可能是三个重复的html头,并且按出题人的习惯还会接两个不同ascii码的换行,我们照着这个思路一个位一个位的比对:
# th3_xOr_is_waaaay_too_w34k@
t_rsaatwk
<html>
hx__ayo3@
<html>
3Oiwa_o4
<html>
这不但验证了我们之前对key尾的猜想,还把前面完完整整的吐出来了。所以最后的结果应该是:
th3_ xOr_ is_ waaaay_ too_ w34k@flare-on.com
顺带,你把这个key放到php运行器上解密,你会发现这些明文都是p01老哥的杰作……套了些不明所谓的玩意。
ps.1 你确定这题真的不放在misc或者crypto?
ps.2 在最后的爆破环节我一开始把长度当40看爆了半天,还调了一整个晚上的算法,最后才发现其实是我一开始看的39,我人傻了(各种意义上)
ps.3 出题人你(wo)没(cxx)有(ni)心(mx)
ps.4 buu导题的时候导错了flag导致中间的’O’变成’0’才能过,忘周知
zsud
一上来发现像是迷宫题,但又不那么正统,我们的视野直接被封死了,目前唯一可以得到信息的途径就是h(elp)和l(ook):
Game commands:
h[elp] - See this help
q[uit] - Exit the game
Area commands:
l[ook] [object] - Look at the room [or at an optional object)
n[orth] - Move north
s[outh] - Move south
e[ast] - Move east
w[est] - Move west
u[p] - Move up
d[own] - Move down
Personal commands:
say <someone> <words...> - Say <words...> to <someone>
wear <inventory-item> - Put <inventory-item> on
remove <thing> - Take <thing> off
Inventory commands:inv[entory] - Check your inventory
get <object> [location] - Get object [from within optional location])
drop <object> - Put object down
方向键并不是wasd而是东南西北,但不管怎么说,我们都应该四处转转。往南走是outside直接死路,往北走的话,里面会有一些奇怪的结构,于是我们直接画一张地图,顺带把有关键道具的地方给标注了:
但是我们对于现在要做的事情一头雾水,遂喂die和ida,找到一条鲸鱼以及它的所在函数和地址:
但打开程序跟进网页之后也只剩下一条鲸鱼:
其它什么也没有,感觉脑子要喂鲸鱼了。但是我们发现,打开时会出现一个clrhost
的调试信息,搜一下是个能在汇编程序中打开一个CLR环境,并通过一个自启动的控制台跑C#代码的玩意。相关内容可以参考CLRhost本体以及lidandan2016
的文章。
我们还发现某些函数调用了一些空全局变量作为函数,可能在程序运行过程中会有一些对.NET调用库的解码也说不定?用CheatEngine一开果真看到了不对劲的whiskey_tango_flareon.dll
。
虽然但是,回到解谜游戏本体,我们似乎发现从drawers里拿走钥匙之后,再前往Mandiant offices,key上不但有新内容,我们还发现有新路可以走了,新路连到的work area似乎是一个无限大且方向延伸为6个的空间:
> l key
You BANKbEPxukZfP2EikF8jN04iqGJY0RjM3p++Rci2RiUFvS9RbjWYzbbJ3BSerzwGc9EZkKTvv1JbHOD6ldmehDxyJGa60UJXsKQwr9bU3WsyNkNsVd/XtN9/6kesgmswA5Hvroc2NGYa91gCVvlaYg4U8RiyigMCKj598yfTMc1/koEDpZUhl9Dy4ZhxUUFHbiYRXFARiYNSiqJAEYNB0r93nsAQPNogNrqg23OYd3RPp4THd8G6vkJUXltRgXv7px2CWdOHuxKBVq6EduMFSKpnNB7jAKojNf1oGECrtR5pHdG1LhtTtH6lFE7IVTEKOHD+TMO1VUh0Bpa37WhIAKEwpuyp5+Tspyh0GidHtYcNWfzLNBXymmrhzvta2nJ+FtI6KWXgAAMJdUCy6YrGbWFoR2ChpeWlZLf7cQ1Awh27y6hOV19R6IKOpQzCOQLNjUplm4SOltwRU0pH6BYCeoYXbyRl3kk92uXoBsBXwxdo9QoLBOAdJmKnN95VBT03a+jS3ku3YLwXR29GIlsCfdkVKr4J1d/Xal//e+Bqq1xMEucIdnNSwr4hlOtdpLrPyfnCVkBcadlRC6hGitbptCknTCUniXCCOE1NkWSVi3v5VrXkPGAvw/iRu7F2BimC+o3tIdWPpxkcfks6zVQSiFJjVzrt28+QUb28+YRaCkPhfZALYKQLU3DR5YJw64sL40tykTI68evyRF/Fnp4VTNlWQHXPJ+Y6yCHZnrb8NdIRDPfm1wxOQJbdeaEZSa3AgqI2wW0pPBnf69vVAq4qjxyrI1LPL9hzd7cBfqnohjyDy/t78TZOh0hX++W6zkMl0Ez6I2CHxop3vzg1/9iQig0WAglmdqiAhKbDFSM7kGPf5Reyphx27uzxHAllP7LrX1vF7o9v4vcCrHE7dJpuisSWhsx3rtJsBA15mdMAbuj1ErOpWLMbXCYfhpSj6GLOHOU/PqeDoktZs9BLS+V11PcxaVVwHBGfCimMe61mSFD0hhYJXgTxbwKDvIS...
----------
> n
...
You go north
The key emanates some warmth...
the Mandiant offices
This is where the magic happens. To the west, open work areas, cubicles, offices, and adjustable desks stretch out and intersect in a dazzling maze as far as the eye can see.
Exits: North South East
但是这个key到底是用来开啥的尚且未知,我们拿着钥匙稍微走走,会发现部分操作会使得钥匙变热并且让密文部分解密,部分输出Hmm...
并导致解密停止。
> n
You go north
The key emanates some warmth...
the northeast hallway
This hallway links the snack/lunch area with the IT access junction.
Exits: South East
> l key
You can start to zXykaHk29xWQzs7kNrmhu4oN0TsxM7iyJ4tmttuCijPJ2G0VA3xCFLvUnmYb/P3Ae+sO/wdNm1OpZX5AltsADOBqgr2uErcM9BeKqlOCmfH2SPfDXoZN7p5PoZe9RPv0reF1C6Rse23MtetFeffgvPztw/ZRnxIiAg9jKhL8……
思来想去,应该是要让钥匙全程红温才能把这一大串密文解密完成。问题来了,我们该怎么走才能让钥匙全程热乎呢?
一种方法当然是暴力走无数遍,直觉来看,表层肯定是顺时针或者逆时针把整个有限部分转一遍(测试是从大厅开始顺时针,然后宿舍先女后男),然后再去无限房摸路,但是那样未免也太耗时间(参考Thomas佬的题解,他的暴力代码整整走了一个钟头)。
另外一种就是直接通过IDA的东西提取dll,然后从里面找寻路的办法。我们先从CE里头看上面提到的dll的函数包含:
再从IDA里头找到对应的MZ头:
然后把所有看着挺像的东西dump出来塞进DNSpy里:
private static string Decrypt2(byte[] cipherText, string key)
{
byte[] bytes = Encoding.UTF8.GetBytes(key);
byte[] array = new byte[16];
byte[] iv = array;
string result = null;
using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
rijndaelManaged.Key = bytes;
rijndaelManaged.IV = iv;
ICryptoTransform transform = rijndaelManaged.CreateDecryptor(rijndaelManaged.Key, rijndaelManaged.IV);
using (MemoryStream memoryStream = new MemoryStream(cipherText))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
using (StreamReader streamReader = new StreamReader(cryptoStream))
{
result = streamReader.ReadToEnd();
}
}
}
}
return result;
public static int Smth(string arg)
{
using (PowerShell powerShell = PowerShell.Create())
{
try
{
byte[] cipherText = Convert.FromBase64String(arg);
string script = four.Decrypt2(cipherText, "soooooo_sorry_zis_is_not_ze_flag");
powerShell.AddScript(script);
Collection<PSObject> collection = powerShell.Invoke();
foreach (PSObject value in collection)
{
Console.WriteLine(value);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception received");
}
}
return 0;
}
虽然知道我们的钥匙经历了AES加密,Key是soooooo_sorry_zis_is_not_ze_flag
,但IV未知(八成是输入的路线转换成的东西)。这时候,从CE看运行时内存,我们又会发现一些对其它指令的解码,且基本以function打头(如下是字节码放进赛博厨子烤的结果):
function Invoke-MoveDirection($char, $room, $direction, $trailing) {
$nextroom = $null
$movetext = "You can't go $direction."
$statechange_tristate = $null
$nextroom = Get-RoomAdjoining $room $direction
if ($nextroom -ne $null) {
$key = Get-ThingByKeyword $char 'key'
if (($key -ne $null) -and ($script:okaystopnow -eq $false)) {
$dir_short = ([String]$direction[0]).ToLower()
${N} = ${sC`Ri`Pt:MS`VcRt}::("{1}{0}" -f'nd','ra').Invoke()
这不就是我们在程序控制台里看到的东西吗?在这里,我们可以看到key的初定义,看到各种输出,甚至是——不对,上面那个不就是行动轮的代码吗?顺藤摸瓜找到全局变量direction的定义:
$directions = @('n', 'north', 's', 'south', 'e', 'east', 'w', 'west', 'u', 'up', 'd', 'down')
$directions_short = @{'n' = 'north'; 's' = 'south'; 'e' = 'east'; 'w' = 'west'; 'u' = 'up'; 'd' = 'down'}
$directions_enum = @{'n' = 0; 's' = 1; 'e' = 2; 'w' = 3; 'u' = 4; 'd' = 5}
$prepositions = @{'north' = 'north of'; 'south' = 'south of'; 'east' = 'east of'; 'west' = 'west of'; 'uu' = 'above'; 'down' = 'below'}
很好,我们现在只需要在程序的静态资源里头找到一串只有0~5的序列就行了。dword列表一翻,很快的找,然后简单转换一下变成路线:
way = '30022111023022333540540540140240123540123123123123540'
dir = 'nsewud'
oput = ""
for i in way:
oput += dir[ord(i) - 48]
print(oput)
按照路线走就会传送回大门,然后你的key变成了这样:
> l key
You can start to make out some words but you need to follow the RIGHT_PATH!@66696e646b6576696e6d616e6469610d0a // findkevinmandia
好,这就去找Kevin。不过在此之前,这里还有一个函数——
function Invoke-Say($char, $room, $trailing) {
$resp = "It doesn't talk back"
$ar = $trailing.Split()
if ($ar.Length -lt 2) {
return "Syntax: say <someone> <words...>"
}
$to_whom = $ar[0]
$words = $ar[1..99999]
$thing = Get-ThingByKeyword $room $to_whom
if ($thing.Name -eq "Kevin Mandia") {
$resp = "Kevin says a friendly 'hello' and then looks back down at his computer. He's busy turbo-hacking."
$key = Get-ThingByKeyword $room 'key'
$helmet = $null
foreach ($thing in $char.Wearing) {
if ($thing.Keywords -contains "helmet") {
$helmet = $thing
}
}
if (($key -ne $null) -and ($helmet -ne $null)) {
$md5 = New-Object System.Security.Cryptography.MD5CryptoServiceProvider
$utf8 = New-Object System.Text.UTF8Encoding
$hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($key.Desc)))
$Data = [System.Convert]::FromBase64String("EQ/Mv3f/1XzW4FO8N55+DIOkeWuM70Bzln7Knumospan")
$Key = [System.Text.Encoding]::ASCII.GetBytes($hash)
# Adapated from the gist by harmj0y et al
$R={$D,$K=$Args;$H=$I=$J=0;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Length])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}}
$x = (& $r $data $key | ForEach-Object { "{0:X2}" -f $_ }) -join ' '
$resp = "`nKevin says, with a nod and a wink: '$x'."
$resp += "`n`nBet you didn't know he could speak hexadecimal! :-)"
}
}
return $resp
}
看样子我们得在完成一系列步骤后去老板办公室把头盔带上,再把key撇了。于是我们照做试试看:
> get helmet
You get A football helmet.
> wear helmet
You put the helmet on your head. It looks objectively awesome.
> drop key
You drop a key
> say kevin hi
Kevin says, with a nod and a wink: '6D 75 64 64 31 6E 67 5F 62 79 5F 79 30 75 72 35 33 6C 70 68 40 66 6C 61 72 65 2D 6F 6E 2E 63 6F 6D'.
Bet you didn't know he could speak hexadecimal! :-)
flag到手!