经过一个假期的不断摸索,终于把Crypto交互的部分搞明白了。本篇记录了Crypto交互题的解题过程,以及一道交互题是如何实现其功能的。
 
0x1. 交互题概述 
交互题的实质,就是挂载在服务器的一个端口上的程序。解交互题时,一般给选手一个nc码,包含服务器ip以及端口号,选手在控制台输入nc码,即可进入与服务器的交互。有时候题目附件还包含服务器端的代码,方便选手查看加密过程,但严格限制选手的权限,不允许选手查看flag的内容。
0x2. 解交互题 
0x2.1 编写交互脚本 
对于交互次数较少的题目,当然可以选择不写交互脚本,但如果交互次数较多,每次单独执行一遍exp就会很麻烦,这时候就要编写自动化交互脚本。
交互脚本需要用到python的pwn库,包含各种能实现收、发包的函数。
1 conn=remote('HOST' , PORT) 
 
初始化conn,确认要与哪个服务器的哪个端口交互。
 
一次接收到的内容,不对换行符敏感,内容的类型为bytes
 
一次接收到的内容,以换行符为结束,类型也是bytes
 
向服务器发送指定内容,对于int类型要变换为bytes类型:f'{x}'.encode。
1 conn.sendlineafter(b'aaa' ,b'bbb' ) 
 
在收到特定的字节流后发送指定内容,搭配上一条使用。
0x2.2 特别注意 
第一,要准确把握程序在一个循环的每个阶段输的内容,才能正确编写交互脚本,否则要么报错,要么无法返回正确答案。有的题目非常操蛋,跟鸡拉屎一样的一截一截的发,那就只能一段一段的recvline(),而不是recv().split('\n')。如果没有附件,最好一步步打印输出内容。
交互过程中有两个特殊部分,就是交互最开始和最末尾。最开始不会像交互过程中返回一个Yes或者No,因此要单独执行再进入循环(循环可以用while
True,因为你不知道要执行多少次)。而最末尾输出正确后就直接给flag了,而交互脚本还在解码,所以大概率要报错,因此无论返回值是啥都要先print再继续。
第二,交互脚本只是工具,正确的攻击思想和严密的逻辑才是解答密码题的核心!(打枪没有用的.jpg)
0x3 交互题是怎样炼成的 
0x3.1 概述 
1 socat -d -d tcp-l:|port| ,reuseaddr,fork EXEC:"python -u server.py" 
 
socat直接挂载,一步到位,非常无脑,只使用于自己玩。
出题面临的情况是:服务器没有python,也没有python的那些库函数;运维是一只会敲打键盘的猩猩,除了按照指示在控制台上输入命令啥也不会。总之,要在一个光板机上用最简单的方式搭建题目环境。不过,情况也没那么糟糕,因为主机上一般有一个很重要的应用:Docker。
 
61d7ee0b2cab4295.jpg 
 
docker的logo是一个巨鲸,它上面托运了很多集装箱;可以把鲸鱼看成是货轮,相当于是一个平台,上面放的集装箱可以看成是容器,集装箱容器里面装的就是各种项目,而且集装箱与集装箱之间没有任何联系,它们是相互隔离的。
 
Docker
可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的
Linux
机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似
iPhone 的 app),更重要的是容器性能开销极低。
 
Dockerfile
是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
 
0x3.2 出发了!(日丹诺夫并感) 
交互题server.py(模板),在服务器端运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 from  hashlib import  sha256import  socketserverfrom  secret import  flagimport  signalimport  stringimport  randomimport  osimport  gmpy2 class  Task (socketserver.BaseRequestHandler):    def  _recvall (self ):         BUFF_SIZE = 2048          data = b''          while  True :             part = self.request.recv(BUFF_SIZE)             data += part             if  len (part) < BUFF_SIZE:                 break          return  data.strip()     def  send (self, msg, newline=True  ):         try :             if  newline:                 msg += b'\n'              self.request.sendall(msg)         except :             pass      def  recv (self, prompt=b'[-] '  ):         self.send(prompt, newline=False )         return  self._recvall()     def  proof_of_work (self ):         random.seed(os.urandom(8 ))         proof = '' .join(             [random.choice(string.ascii_letters+string.digits) for  _ in  range (20 )])         _hexdigest = sha256(proof.encode()).hexdigest()         self.send(f"[+] sha256(XXXX+{proof[4 :]} ) == {_hexdigest} " .encode())         x = self.recv(prompt=b'[+] Plz tell me XXXX: ' )         if  len (x) != 4  or  sha256(x+proof[4 :].encode()).hexdigest() != _hexdigest:             return  False          return  True      def  handle (self ):         signal.alarm(60 )         self.send('This is a test of gmpy2' )         x=gmpy2.powmod(2 ,3 ,3 )         self.send(b'gmpy2.powmod(2,3,3)=' +f'{x} ' .encode())                  if  not  self.proof_of_work():             self.send(b'[!] Wrong!' )             return          self.send(b'here is your flag' )         self.send(flag) class  ThreadedServer (socketserver.ThreadingMixIn, socketserver.TCPServer):    pass  class  ForkedServer (socketserver.ForkingMixIn, socketserver.TCPServer):    pass  if  __name__ == "__main__" :    HOST, PORT = '0.0.0.0' , 10001      server = ForkedServer((HOST, PORT), Task)     server.allow_reuse_address = True      print (HOST, PORT)     server.serve_forever() 
 
Dockerfile,在服务器端运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 FROM  python:3.8 LABEL  Description="baby_try"  VERSION='1.0'  COPY  server.py . COPY  secret.py . RUN  pip install --upgrade pip RUN  pip install gmpy2  RUN  chmod  +x server.py EXPOSE  12345 CMD  ["python" , "server.py" ] 
 
docker-compose.yml,简化创建镜像和启动容器的步骤
1 2 3 4 5 6 7 8 9 version:  '3' services:   checkin:      image:  signin      build:  .      ports:       -  "9999:10001"      restart:  always  
 
输入命令一键搭建:docker-compose up -d