2021 绿城杯 Re 逆向部分 题解

robots

 

ak 了 re,mark 一下

原题链接: 链接:https://pan.baidu.com/s/1lA9Y003K3AhWedge8NuQUw
提取码:1111

 

easy_re

  • 签到题,本身没啥好说的,把几个花指令 nop 掉,就可以 F5 看伪代码
  • 可以看到就是一个亦或加密,所以无需关心密钥生成,直接 dump 密钥流即可
  • 动调起来后,输入一串 a,让程序运行到比较处时 dump 此时的 fake_cipher ,然后与字符 a 亦或即可得到密钥流
      cipher = [245, 140, 141, 228, 159, 165,  40, 101,  48, 244,
                235, 211,  36, 169, 145,  26, 111, 212, 106, 215,
                11, 141, 232, 184, 131,  74,  90, 110, 190, 203,
                244,  75, 153, 214, 230,  84, 122,  79,  80,  20,
                229, 236]
      fake_plain = b'a'*len(cipher)
      print(fake_plain)
      fake_cipher = [242, 129, 141, 226, 133, 167, 124,  97,  97, 243,
                     191, 212, 115, 229, 150,  76,  55, 208,  38, 131,
                     8, 213, 235, 244, 219,  19,   3, 105, 242, 152,
                     173,  76, 200, 131, 177,   4,  42,  25,   9,  69,
                     182, 240]
      keys = [fake_cipher[i] ^ fake_plain[i]for i in range(len(cipher))]
    
      flag = ''
      for i in range(len(cipher)):
        flag += chr(cipher[i] ^ keys[i])
      print(flag)
    
  • flag{c5e0f5f6-f79e-5b9b-988f-28f046117802}

 

babyvxworks

  • 一个 VxWork 上的可执行文件,直接拖到 IDA 32 里面就能分析
  • 根据交叉引用定位到主函数,发现有不少花指令
  • jzjnz 连着用就相当于 jmp 了,所以直接把这一大片全部 patch 成 nop 即可
  • 然后就能查看伪代码了。根据语义可以重命名一下函数
  • 关键就在于 enc 函数了,修改一下它的函数原型,改成 void 返回值,可以发现就是一个简单的递归
  • 由于是单字节加密,所以直接爆破即可
      cipher = [188, 10, 187, 193, 213, 134, 127, 10, 201, 185, 81, 78, 136, 10,
                130, 185, 49, 141, 10, 253, 201, 199, 127, 185, 17, 78, 185, 232, 141, 87]
    
      def brute():
        for i in range(len(cipher)):
          for c in range(0x20, 0x7f):
            tmp = c
            for _ in range(30):
              tmp ^= 0x22
              tmp += 3
            if tmp & 0xff == cipher[i]:
              print(chr(c), end='')
              break
    
      brute()
    
  • flag{helo_w0rld_W3lcome_70_R3}

 

抛石机

  • 个人认为是很恶心的题…考点完全不在逆向分析而是做一些奇奇怪怪的工作…希望国内比赛这种题可以少一点
  • 程序逻辑很简单,基本就是输入一个 uuid,每两个 byte 作为一个十六进制数,然后填充到 buffer 里,之后调用函数进行校验
  • 然后把 buffer 的数据转换成 double ,放到一元二次方程里进行运算
  • 随便输入一个 uuid,即可找到 uuid 和 buffer 中填充数据的对应关系,这时就确定了四个 double 都是前四个字节是 0,后四个字节是 uuid 中的数据
  • 然后就是爆破了…跑了挺久,服了
      package main
    
      import (
          "encoding/binary"
          "fmt"
          "math"
          "runtime"
          "sync"
      )
    
      func Float64frombytes(bytes []byte) float64 {
          bits := binary.LittleEndian.Uint64(bytes)
          return math.Float64frombits(bits)
      }
    
      func Equation1(src float64) bool {
          ans := 149.2*src + src*-27.6*src - 129.0
          return -0.00003 <= ans && ans <= 0.00003
      }
    
      func Equation2(src float64) bool {
          ans := src*-39.6*src + 59.2*src + 37.8
          return -0.00003 <= ans && ans <= 0.00003
      }
    
      func main() {
          wg := sync.WaitGroup{}
          var a uint64
          runtime.GOMAXPROCS(runtime.NumCPU())
          eq1Solves := make([][]byte, 0)
          eq2Solves := make([][]byte, 0)
          for a = 0x0; a <= 0xffffffff; a++ {
              tA := a << 32
              f := math.Float64frombits(tA)
              wg.Add(1)
              go func() {
                  tmp := make([]byte, 8)
                  if Equation1(f) {
                      binary.LittleEndian.PutUint64(tmp, tA)
                      fmt.Print("1st equation solve: ")
                      fmt.Println(tA)
                      eq1Solves = append(eq1Solves, tmp)
                  } else if Equation2(f) {
                      binary.LittleEndian.PutUint64(tmp, tA)
                      fmt.Print("2nd equation solve: ")
                      fmt.Println(tA)
                      eq2Solves = append(eq2Solves, tmp)
                  }
                  wg.Done()
              }()
          }
          wg.Wait()
          fmt.Println(eq1Solves)
          fmt.Println(eq2Solves)
      }
    
  • 跑出来之后根据题目中的其他限制条件,如大小关系,解出来值的范围可以确定唯一的 flag
  • flag{454af13f-f84c-1140-1ee4-debf58a4ff3f}
(完)