2021-西湖论剑-逆向(部分)-Writeup

阅读量523181

|评论2

|

发布时间 : 2021-12-10 16:30:24

 

TacticalArmed

出处: 2021 西湖论剑
原题链接: 链接:https://pan.baidu.com/s/14runS0J5A_PVjuN6Ior5Aw
提取码:eaaf
时间: November 20, 2021
考点: SMC, TEA, before_main, ida_dbg, 反调试
难度: 困难

初步分析

  1. PE32 的文件,拖到 IDA 里打开,很容易就能找到 main 函数
  2. 然后开始调试,发现很奇怪,一开始 IDA 是能调试的,但是过了几条指令之后 IDA 就啥都干不了了,考虑在 main 函数之前有其它操作
  3. main 函数后面的逻辑很清晰
    1. 使用 malloc & VirtualProtect 开辟了一段 rwx 的内存
    2. 读取输入
    3. 进入多重 while 循环,最内层填充了上面的内存空间(填入 shellcode),然后运行
    4. 进行比较
  4. 下面按照考点对分析过程进行归类

考点分析

TLSCallback0

  1. 发现一个 TLSCallback0 ,里头起了个线程
  2. 线程注册了一个 exception handler 同时触发了对应的异常,回调函数里覆写了一些 data 段的数据,可能是填充 key 或者 cipher

initterm

  1. .rdata 段找到一个结构体
  2. 里面自定义的函数最终执行了如下的逻辑
  3. 猜测这里就是反调试了,网上搜索相关知识,发现这是通过改变 Dr 寄存器的状态来反调试的方法,Dr7 会在这个循环每次执行后赋值为 0,而它实际上可以理解为是调试功能的“使能”标志位,所以这个线程在跑起来之后 IDA 的调试功能就没法用了

    相关知识的具体介绍

    获取线程上下文的反调试技术

    活着的虫子

  4. 绕过也很简单,直接把调用 initterm 时传入的结构体里的函数指针删掉即可

shellcode 运行与 dump

  1. 结合伪代码可以很容易地定位到执行 shellcode 的地方
  2. 每次执行的其实都只是一条指令
  3. 所以现在的问题就是如何把指令 dump 出来。这里使用 IDASDK 提供的 ida_dbg 工具集,而我之前又在它之上封了一个更方便使用的库,欢迎大佬们使用
    GitHub – r4ve1/IDA-autodbg
  4. 然后在 IDA 中运行此脚本,再运行程序,即可把所有执行过的 shellcode 的汇编 dump 出来了
import idc
import autodbg as ac
import sark
import ida_dbg

class MyHook(ac.Actions):
  def __init__(self):
    super().__init__()
    self.call_addr = 0x40146D
    self.bpt_actions = [ac.B(self.call_addr, self.callback)]
    self.exit_actions = [ac.E(self.exit_cbk)]
    self.insn = """"""

  def callback(self):
    ida_dbg.refresh_debugger_memory()
    var_addr = ida_dbg.get_reg_val("ebp")-8
    ea = idc.get_wide_dword(var_addr)
    for i in range(0x10):
      idc.del_items(ea+i)
    idc.create_insn(ea)
    l = sark.Line(ea)
    self.insn += str(l)+"\n"
    # print(self.insn)
    self.request_continue()

  def exit_cbk(self):
    with open("insn.txt", "w")as f:
      f.write(self.insn)

a = ac.AutoCracker(MyHook())
a.hook()
  1. 所执行的 shellcode 大致如图所示(仅展示其中一轮加密)
  2. 其中大量移位 & 亦或的操作核对后发现是 TEA 加密,密钥就是 TLSCallback0 里面复制的那个全局变量,delta 改为了 -0x7E5A96D2
  3. 同时也能发现轮数改为了 33
  4. 还有一个很关键的,就是每加密一组明文后,**sum** 并没有置为 0,这在解密的时候也要照顾到,要不就只能解密一个分组了

最终脚本

#include <cstdint>
#include <cstdio>
#include <iostream>
#include <string>
#define DELTA -0x7E5A96D2
#define ROUND 33

uint32_t g_sum = 0;
using namespace std;

void tea_decrypt(uint32_t* v, uint32_t* k, uint32_t init_sum) {
  uint32_t v0 = v[0], v1 = v[1], sum = init_sum, i;    /* set up */
  uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
  for (i = 0; i < ROUND; i++) {                        /* basic cycle start */
    v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
    sum -= DELTA;
  } /* end cycle */
  v[0] = v0;
  v[1] = v1;
}

int main() {
  int8_t cipher[] = {0xED, 0x1D, 0x2F, 0x42, 0x72, 0xE4, 0x85, 0x14, 0xD5, 0x78,
                     0x55, 0x03, 0xA2, 0x80, 0x6B, 0xBF, 0x45, 0x72, 0xD7, 0x97,
                     0xD1, 0x75, 0xAE, 0x2D, 0x63, 0xA9, 0x5F, 0x66, 0x74, 0x6D,
                     0x2E, 0x29, 0xC1, 0xFC, 0x95, 0x97, 0xE9, 0xC8, 0xB5, 0x0B};
  uint32_t key[] = {0x7CE45630, 0x58334908, 0x66398867, 0x0C35195B1};
  uint32_t sum = 0;
  for (int i = 0; i < sizeof(cipher); i += 8) {
    auto ptr = (uint32_t*)(cipher + i);
    sum += DELTA * ROUND;
    tea_decrypt(ptr, key, sum);
  }
  string plain((char*)cipher, sizeof(cipher));
  cout << plain << endl;
}

flag kgD1ogB2yGa2roiAeXiG8_aqnLzCJ_rFHSPrn55K

 

ROR

出处: 2021 西湖论剑
原题链接: 链接:https://pan.baidu.com/s/14runS0J5A_PVjuN6Ior5Aw
提取码:eaaf
时间: November 20, 2021
考点: Z3
难度: 简单

分析

  • main 函数很清晰
  • 仔细过一遍加密部分,发现其实就是对明文的各个 bit 位进行重组,然后拿新产生的数去做查表加密
  • 自然而然想到了 z3。但是 z3 无法解决查表的逆操作,这一步调 Python 的方法去做就行了

最终脚本

import z3
p = [z3.BitVec("p%d" %i, 8)for i in range(40)]
buffer = [0]*256
k=[0]*8
k[0] = 128
k[1] = 64
k[2] = 32
k[3] = 16
k[4] = 8
k[5] = 4
k[6] = 2
k[7] = 1
charset=[  0x65, 0x08, 0xF7, 0x12, 0xBC, 0xC3, 0xCF, 0xB8, 0x83, 0x7B, 
  0x02, 0xD5, 0x34, 0xBD, 0x9F, 0x33, 0x77, 0x76, 0xD4, 0xD7, 
  0xEB, 0x90, 0x89, 0x5E, 0x54, 0x01, 0x7D, 0xF4, 0x11, 0xFF, 
  0x99, 0x49, 0xAD, 0x57, 0x46, 0x67, 0x2A, 0x9D, 0x7F, 0xD2, 
  0xE1, 0x21, 0x8B, 0x1D, 0x5A, 0x91, 0x38, 0x94, 0xF9, 0x0C, 
  0x00, 0xCA, 0xE8, 0xCB, 0x5F, 0x19, 0xF6, 0xF0, 0x3C, 0xDE, 
  0xDA, 0xEA, 0x9C, 0x14, 0x75, 0xA4, 0x0D, 0x25, 0x58, 0xFC, 
  0x44, 0x86, 0x05, 0x6B, 0x43, 0x9A, 0x6D, 0xD1, 0x63, 0x98, 
  0x68, 0x2D, 0x52, 0x3D, 0xDD, 0x88, 0xD6, 0xD0, 0xA2, 0xED, 
  0xA5, 0x3B, 0x45, 0x3E, 0xF2, 0x22, 0x06, 0xF3, 0x1A, 0xA8, 
  0x09, 0xDC, 0x7C, 0x4B, 0x5C, 0x1E, 0xA1, 0xB0, 0x71, 0x04, 
  0xE2, 0x9B, 0xB7, 0x10, 0x4E, 0x16, 0x23, 0x82, 0x56, 0xD8, 
  0x61, 0xB4, 0x24, 0x7E, 0x87, 0xF8, 0x0A, 0x13, 0xE3, 0xE4, 
  0xE6, 0x1C, 0x35, 0x2C, 0xB1, 0xEC, 0x93, 0x66, 0x03, 0xA9, 
  0x95, 0xBB, 0xD3, 0x51, 0x39, 0xE7, 0xC9, 0xCE, 0x29, 0x72, 
  0x47, 0x6C, 0x70, 0x15, 0xDF, 0xD9, 0x17, 0x74, 0x3F, 0x62, 
  0xCD, 0x41, 0x07, 0x73, 0x53, 0x85, 0x31, 0x8A, 0x30, 0xAA, 
  0xAC, 0x2E, 0xA3, 0x50, 0x7A, 0xB5, 0x8E, 0x69, 0x1F, 0x6A, 
  0x97, 0x55, 0x3A, 0xB2, 0x59, 0xAB, 0xE0, 0x28, 0xC0, 0xB3, 
  0xBE, 0xCC, 0xC6, 0x2B, 0x5B, 0x92, 0xEE, 0x60, 0x20, 0x84, 
  0x4D, 0x0F, 0x26, 0x4A, 0x48, 0x0B, 0x36, 0x80, 0x5D, 0x6F, 
  0x4C, 0xB9, 0x81, 0x96, 0x32, 0xFD, 0x40, 0x8D, 0x27, 0xC1, 
  0x78, 0x4F, 0x79, 0xC8, 0x0E, 0x8C, 0xE5, 0x9E, 0xAE, 0xBF, 
  0xEF, 0x42, 0xC5, 0xAF, 0xA0, 0xC2, 0xFA, 0xC7, 0xB6, 0xDB, 
  0x18, 0xC4, 0xA6, 0xFE, 0xE9, 0xF5, 0x6E, 0x64, 0x2F, 0xF1, 
  0x1B, 0xFB, 0xBA, 0xA7, 0x37, 0x8F]
realCipher=[  101,  85,  36,  54, 157, 113, 184, 200, 101, 251, 
  135, 127, 154, 156, 177, 223, 101, 143, 157,  57, 
  143,  17, 246, 142, 101,  66, 218, 180, 140,  57, 
  251, 153, 101,  72, 106, 202,  99, 231, 164, 121]
solver=z3.Solver()
for i in range(0,0x28,8):
  for j in range(8):
    c1 = ((k[j] & p[i + 3]) << (8 - (3 - j) % 8)) | ((k[j] & p[i + 3]) >> ((3 - j) % 8)) | ((k[j] & p[i + 2]) << (8 - (2 - j) % 8)) | ((k[j] & p[i + 2]) >> ((2 - j) % 8)) | ((k[j] & p[i + 1]) << (8 - (1 - j) % 8)) | ((k[j] & p[i + 1]) >> ((1 - j) % 8)) | ((k[j] & p[i]) << (8 - -j % 8)) | ((k[j] & p[i]) >> (-j % 8))
    c2 = ((k[j] & p[i + 7]) << (8 - (7 - j) % 8)) | ((k[j] & p[i + 7]) >> ((7 - j) % 8)) | ((k[j] & p[i + 6]) << (8 - (6 - j) % 8)) | ((k[j] & p[i + 6]) >> ((6 - j) % 8)) | ((k[j] & p[i + 5]) << (8 - (5 - j) % 8)) | ((k[j] & p[i + 5]) >> ((5 - j) % 8)) | ((k[j] & p[i + 4]) << (8 - (4 - j) % 8)) | ((k[j] & p[i + 4]) >> ((4 - j) % 8))
    c3=c1|c2
    ans=charset.index(realCipher[i+j])
    solver.add(c3==ans)
sat=solver.check()
print(sat)
mod=solver.model()
flag=''
for c in p:
  tmp=mod[c].as_long()
  flag+=chr(tmp)
print(flag)

 

虚假的粉丝

出处: 2021 西湖论剑
原题链接: 链接:https://pan.baidu.com/s/14runS0J5A_PVjuN6Ior5Aw
提取码:eaaf
时间: November 20, 2021
难度: …

这题太 NT 了

分析

  1. 题目给的附件里有很多文件,看一下 exe
  2. 让用户输入三个数,然后校验
  3. 让用户输入一个字符串,校验第一个和最后一个字符
  4. 然后就是这个循环了,运行一下发现这里就是播放音乐,然后读取文件里的内容并打印在屏幕上
  5. 文件感觉是字符画一类的东西
  6. 5315 这个文件全都是密文。所以这里的入手点就是猜到那个字符串
  7. 考虑到 faded 的作者叫 Alan Walker,这里就从这个名字下手了。。。
  8. 因为要输出的是字符画,所以大部分应该都是可视字符,所以还算有一个其他的方式判断当前是不是正确的 key
def dec():
  with open("f\ASCII-faded 5315.txt", "rb")as f:
    cipher = f.read()
  key = b"Al4N_wAlK3R"
  plain = [cipher[i] ^ key[i % len(key)]for i in range(len(cipher))]
  for i, p in enumerate(plain):
    if chr(p) not in string.printable:
      print(i % len(key))

  with open("plain.txt", "wb")as f:
    f.write(bytes(plain))
  1. 得到字符画,又废了老大劲才读懂 flag …

本文由r4ve1原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/260393

安全客 - 有思想的安全新媒体

分享到:微信
+14赞
收藏
r4ve1
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66