今年的 TPCTF 我随 0xFFF 参赛,并且拿下了两道 nanonymous 题目的一血,以下是题解。
nanonymous spam
本题给了一个匿名留言板网站,其中已有一堆 Spam 留言。在最底下有一个发送留言的部分,注意到网站已经给访问者指定了一个昵称。尝试使用不同的 IP 访问该网站,不难发现这个生成的昵称是根据 IP 地址生成的:
1 | 109.110.162.51 => PowSamPenWay |
猜测本题是需要探索出这个昵称的生成算法,然后将那些 Spam 留言的昵称反推回 IP 地址。但是这需要大量的 IP 地址和昵称的对应数据,显然我们很难找一堆 IP 来爆数据,所以我探究其是否可以伪造访问者的 IP 地址。
经过一番尝试,发现可以通过 X-Real-IP
这个 HTTP 头来伪造 IP 地址。并且也发现第一个 Spam 留言者昵称对应的 IP 地址正是 TPCT
这个 Flag 头的 ASCII 码,这证实了猜想:
1 | import requests |
那么我们先从 0.0.0.0
开始递增看一看是否能找到一些规律:
1 | f = open("result.txt", "at") |
结果如下,注意到昵称的第 4-6 个字符每 103 个 IP 地址会有一个周期,进入下一个周期改变了第 1-3 个字符的内容,其余字符则保持不变:
1 | 000 WimNodSerPuc 0.0.0.0 |
那就继续探索下一个周期变化。这个周期很长,所以直到将范围拉到 520 之后才确定其周期长度为 513:
1 | for i in range(0, 520): |
1 | 000 WimNodSerPuc 0.0.0.0 |
进一步探索下一个周期。下一个周期的长度为 313,但同时也注意到,在进入下一个周期后,四个部分的顺序也发生了变化:
1 | for i in range(0, 320): |
1 | 000 WimNodSerPuc 0.0.0.0 |
先对之前三个周期的昵称部分进行了整理,发现它们互相没有交集,所以顺序的变化不影响对下一个周期的探索:
1 | A = ['Nod', 'Tap', 'Liz', 'Mel', 'Fig', 'Rif', 'Rip', 'Pud', 'Foo', 'Haw', 'Wef', 'Kel', 'Gat', 'Hod', 'Mom', 'Lin', 'Fez', 'Rua', 'Fay', 'Pat', 'Ned', 'Taz', 'Sid', 'Mic', 'Nom', 'Hab', 'Rug', 'Men', 'Nok', 'Fun', 'Pox', 'Red', 'Jah', 'Tet', 'Hip', 'Tem', 'Bad', 'Mir', 'Taj', 'Maf', 'Rac', 'Zia', 'Hea', 'Fis', 'Dem', 'Bim', 'Gow', 'Hub', 'Job', 'Nex', 'Jas', 'Lie', 'Sim', 'Poc', 'Ran', 'Voa', 'Gig', 'Jes', 'Nie', 'Lal', 'Lek', 'Pen', 'Cos', 'Col', 'Nao', 'Mop', 'Bac', 'Cis', 'Mor', 'Vim', 'Ceo', 'Gic', 'Mii', 'Dep', 'Len', 'Few', 'Lob', 'Lea', 'Bec', 'Mui', 'Pec', 'Mab', 'Her', 'Tas', 'Tui', 'Kun', 'Vic', 'Too', 'Woe', 'Uav', 'Dam', 'Jin', 'Kaz', 'Yew', 'Cid', 'Jaw', 'Hay', 'Gib', 'Mis', 'Til', 'Six', 'Bot', 'Guo'] |
那就继续:
1 | i, ip = 0, 0 |
然后意识到这应该是最后一环了,只需要把这个周期的昵称部分整理出来就可以了:
1 | D, exists = [], set(A + B + C) |
1 | D = ['Puc', 'Maz', 'Doh', 'Hun', 'Cud', 'Vit', 'Wer', 'Hag', 'Din', 'Feb', 'Gui', 'Rak', 'Vac', 'Kim', 'Pol', 'Som', 'Saa', 'Hac', 'Xie', 'Ses', 'Van', 'Nef', 'Mia', 'Tab', 'Pid', 'Ver', 'Cay', 'Jog', 'Jar', 'Lan', 'Hex', 'Soe', 'Lid', 'Fip', 'Wet', 'Ner', 'Dey', 'May', 'Dua', 'Dez', 'Gut', 'Sag', 'Kor', 'Yon', 'Haa', 'Par', 'Fat', 'Vel', 'Yum', 'Wac', 'Poe', 'Yes', 'Rex', 'Gop', 'Cit', 'Val', 'Xix', 'Bit', 'Mig', 'Mib', 'Gaa', 'Sat', 'Mex', 'Geo', 'Doi', 'Mou', 'Dol', 'Joy', 'Caa', 'Dix', 'Nat', 'Boj', 'Mad', 'Pew', 'Nev', 'Sas', 'Rin', 'Dal', 'Joo', 'Vii', 'Tid', 'Hap', 'Sea', 'Cae', 'Cab', 'Nea', 'Wan', 'Mem', 'Nam', 'Mao', 'Pov', 'Pio', 'Bey', 'Vas', 'Jee', 'Not', 'Lat', 'Sud', 'Bog', 'Hue', 'Rio', 'Got', 'Liu', 'Lax', 'Fec', 'Duc', 'Rec', 'Mas', 'Cig', 'Vox', 'Rov', 'Pow', 'Sil', 'Gac', 'Pet', 'Yay', 'Sad', 'Ram', 'Box', 'Wag', 'Nin', 'Lib', 'Tou', 'Dae', 'Tau', 'Teo', 'Sod', 'Hoy', 'Tip', 'Cer', 'Wee', 'Nov', 'Keg', 'Nit', 'Wok', 'Hin', 'Tue', 'Ron', 'Roi', 'Vos', 'Sao', 'Kia', 'Tix', 'Mip', 'Cub', 'Nah', 'Hot', 'Wic', 'Yar', 'Sic', 'Sar', 'Kok', 'Fee', 'Yuk', 'Hoo', 'Hei', 'Dap', 'Cen', 'Las', 'Guy', 'Jon', 'His', 'Moo', 'Roz', 'Fac', 'Fir', 'Ham', 'Rad', 'Foi', 'Sof', 'Poo', 'Toa', 'Kos', 'Sei', 'Dof', 'Get', 'Bap', 'Kes', 'Die', 'Dad', 'Pea', 'Nus', 'Tit', 'Ros', 'Nay', 'Moa', 'Zen', 'Mam', 'Heb', 'Fab', 'Rib', 'Cao', 'Hey', 'Wot', 'Soo', 'Kai', 'Cem', 'Rom', 'Uaw', 'Zed', 'Noi', 'Sab', 'Tes', 'Gob', 'Jax', 'Nob', 'Bao', 'Tos', 'Tor', 'Mep', 'Pan', 'Har', 'Guv', 'Foa', 'Nih', 'Cim', 'Pig', 'Jot', 'Sop', 'Duh', 'Jia', 'Nil', 'Fib', 'Kei', 'Gad', 'Toy', 'Pim', 'Gel', 'Cet', 'Hal', 'Wen', 'Yah', 'Nup', 'Jai', 'Paw', 'Pos', 'Qed', 'Tel', 'Gay', 'Liv', 'Bus', 'Fop', 'Pia', 'Miu', 'Ked', 'Fea', 'Fob', 'Sel', 'Miz', 'Lor', 'Tay', 'Pot', 'Tac', 'Wei', 'Mug', 'Dat', 'Wal', 'How', 'Yow', 'Pax'] |
那么就可以直接写解码将那些 Spam 留言者的昵称转换回 IP 地址,进一步解出 Flag 了:
1 | targets = ["VicCouNeaGas", "DemHohBojWod", "PowFitGuoRut", "VetTasBesDae", "FasLiuTasJoi", "DevRecWoeDia", "BogHubSorHad", "BagLibYupSix", "MowPetBecZan", "LonRecRipLuk", "KarYapTajGot", "TiaLiuFayDic", "VizDivCitBot", "LeaLatReaSac", "FasLiuVicToc", "KunSadMerMun", "LemLiuGuoReq"] |
nanonymous msg
本题完整的题目描述如下,没有附件也没有环境:
󠇖󠆖󠄟󠇖󠅗󠆫󠅯󠆧󠅮󠄮󠇦󠇛󠄣󠆤󠄧󠇛󠄪󠅣󠇬󠆦󠄪󠆧󠅮󠇛󠄧󠆪󠇛󠅯󠇮󠅮󠇬󠇬󠇛󠇦󠄢󠅮󠇛󠅧󠅢󠄮󠅯󠇛󠇯󠆧󠄪󠇦󠅮󠇮󠆦󠇛󠆤󠅧󠇛󠆧󠆤󠇮󠇦󠅮󠆧󠇛󠅣󠄪󠄪󠇛󠅧󠆧󠆤󠅪󠇛󠅦󠅮󠅧󠄯󠆤󠅣󠇛󠄯󠇦󠅧󠇛󠆥󠆤󠆥󠆥󠇛󠆮󠇮󠄮󠅢󠇬󠇪Work in progress…
然而事实上,这个题目描述是这样的,一段滥用 Unicode 变体选择符的文本:
1 | \uDB40\uDDD6\uDB40\uDD96\uDB40\uDD1F\uDB40\uDDD6\uDB40\uDD57\uDB40\uDDAB\uDB40\uDD6F\uDB40\uDDA7\uDB40\uDD6E\uDB40\uDD2E\uDB40\uDDE6\uDB40\uDDDB\uDB40\uDD23\uDB40\uDDA4\uDB40\uDD27\uDB40\uDDDB\uDB40\uDD2A\uDB40\uDD63\uDB40\uDDEC\uDB40\uDDA6\uDB40\uDD2A\uDB40\uDDA7\uDB40\uDD6E\uDB40\uDDDB\uDB40\uDD27\uDB40\uDDAA\uDB40\uDDDB\uDB40\uDD6F\uDB40\uDDEE\uDB40\uDD6E\uDB40\uDDEC\uDB40\uDDEC\uDB40\uDDDB\uDB40\uDDE6\uDB40\uDD22\uDB40\uDD6E\uDB40\uDDDB\uDB40\uDD67\uDB40\uDD62\uDB40\uDD2E\uDB40\uDD6F\uDB40\uDDDB\uDB40\uDDEF\uDB40\uDDA7\uDB40\uDD2A\uDB40\uDDE6\uDB40\uDD6E\uDB40\uDDEE\uDB40\uDDA6\uDB40\uDDDB\uDB40\uDDA4\uDB40\uDD67\uDB40\uDDDB\uDB40\uDDA7\uDB40\uDDA4\uDB40\uDDEE\uDB40\uDDE6\uDB40\uDD6E\uDB40\uDDA7\uDB40\uDDDB\uDB40\uDD63\uDB40\uDD2A\uDB40\uDD2A\uDB40\uDDDB\uDB40\uDD67\uDB40\uDDA7\uDB40\uDDA4\uDB40\uDD6A\uDB40\uDDDB\uDB40\uDD66\uDB40\uDD6E\uDB40\uDD67\uDB40\uDD2F\uDB40\uDDA4\uDB40\uDD63\uDB40\uDDDB\uDB40\uDD2F\uDB40\uDDE6\uDB40\uDD67\uDB40\uDDDB\uDB40\uDDA5\uDB40\uDDA4\uDB40\uDDA5\uDB40\uDDA5\uDB40\uDDDB\uDB40\uDDAE\uDB40\uDDEE\uDB40\uDD2E\uDB40\uDD62\uDB40\uDDEC\uDB40\uDDEAWork in progress\u2026 |
恰巧之前看到过一个滥用 Unicode 变体选择符进行隐写的工具 emoji-encoder。先阅读一下它的代码,尝试理解其原理并且用 Python 复刻以下 Decoder:
1 | from typing import Optional |
然后就得到了一串让人摸不着头脑的字节。假设这就是 Flag 经过一些转换得到的内容,既然已知 Flag 头是 TPCTF{
,那么直接对比一下二进制吧:
1 | compare = "TPCTF{" |
1 | 11100110 01010100 |
不难注意到,这串字节的低第 1 4 7 8 位分别和明文的低第 2 1 3 5 位相同,貌似存在一个 bit mapping 的关系,但除此之外剩下的部分可能还涉及一些其他变换。经过一番尝试,最终写出了以下代码:
1 | for byte in result: |
This article is authored by luoingly and is licensed under CC BY-NC 4.0