Mas0n
to be reverse engineer🐧
翻车鱼

第七届湖湘杯网络安全技能大赛

第七届湖湘杯网络安全技能大赛

感觉出题人是做病毒分析的…做的两题全是shellcode。规则定的很严,不乏py被禁赛的,不多做评论。赛时做了两题,做这类病毒分析题还是比较生疏的。正好当作学习了…

Hideit

拿到的文件打开报后门…

ida跑起来会发现,调用VirtualAlloc加载了shellcode

跑起来会有一个

https://cdn.shi1011.cn/2021/11/15ed829387df425fc3cc9404e1e89b71.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

等到程序正常运行后,此时Ctrl+S能看到一个奇怪的区段TestEv1l.dll,dump下来的程序是没有IAT的,所以干脆直接动调了。

TestEv1l.dll区段,手动去创建函数,能看到主逻辑如下

int __fastcall sub_2A44F9A1BB0(unsigned __int8 *a1)
{
  __int64 v2; // rbx
  unsigned int v3; // er9
  int v4; // esi
  unsigned int v5; // er10
  unsigned int v6; // edi
  unsigned int v7; // er11
  __int64 v8; // r8
  __int64 v10; // [rsp+40h] [rbp-C0h] BYREF
  char v11; // [rsp+48h] [rbp-B8h]
  __int64 v12; // [rsp+50h] [rbp-B0h] BYREF
  int v13[4]; // [rsp+60h] [rbp-A0h]
  __int128 v14[2]; // [rsp+70h] [rbp-90h] BYREF
  char v15; // [rsp+90h] [rbp-70h]
  int v16[12]; // [rsp+A0h] [rbp-60h] BYREF
  __int64 v17; // [rsp+D0h] [rbp-30h]
  int v18; // [rsp+D8h] [rbp-28h]
  int v19; // [rsp+DCh] [rbp-24h]
  char v20[512]; // [rsp+E0h] [rbp-20h] BYREF
  char v21[560]; // [rsp+2E0h] [rbp+1E0h] BYREF
  int v22; // [rsp+528h] [rbp+428h] BYREF
  int v23; // [rsp+530h] [rbp+430h] BYREF
  __int64 v24; // [rsp+538h] [rbp+438h] BYREF

  v2 = 0i64;
  v15 = 0;
  memset(v14, 0, sizeof(v14));
  v24 = 0i64;
  if ( !off_2A44F9A3000(-2147483646i64, aSoftwareClasse, &v24) )
  {
    v23 = 0;
    memset();
    v22 = 66;
    if ( !off_2A44F9A3008(v24, aKeysSecret, 0i64, &v23, v21, &v22) )
      off_2A44F9A3020(0i64, 0i64, v21, 0xFFFFFFFFi64, v14, 260, 0i64, 0i64);
  }
  printf("First secret here:");
  v10 = 0i64;
  v11 = 0;
  scanf("%s", &v10);
  v12 = 0i64;
  strcpy(&v12, &v10);
  v13[0] = 114;
  v13[1] = 514;
  v13[2] = 19;
  v13[3] = 19;
  memset();
  v3 = HIDWORD(v12);
  v4 = 32;
  v5 = v12;
  v6 = HIDWORD(v12);
  v7 = 0;
  do
  {
    v7 -= 1640531527;
    v8 = (v7 >> 2) & 3;
    v5 += ((v7 ^ v3) + (v6 ^ v13[v8])) ^ (((16 * v6) ^ (v3 >> 3)) + ((v6 >> 5) ^ (4 * v3)));
    v3 += ((v7 ^ v5) + (v5 ^ v13[v8 ^ 1])) ^ (((16 * v5) ^ (v5 >> 3)) + ((v5 >> 5) ^ (4 * v5)));
    v6 = v3;
    --v4;
  }
  while ( v4 );
  if ( v5 == 288407067 && v3 == 1668576323 )
  {
    v17 = 0i64;
    v18 = v10 | ((BYTE1(v10) | (WORD1(v10) << 8)) << 8);
    v19 = BYTE4(v10) | ((BYTE5(v10) | (HIWORD(v10) << 8)) << 8);
    sub_2A44F9A1000(v16, a1);
    sub_2A44F9A1150(v16, v14, v20);
    while ( byte_2A44F9A31D0[v2] == v20[v2] )
    {
      if ( ++v2 >= 32 )
        return printf("You find last secret!");
    }
  }
  return 0;
}

逻辑很简单,首先是tea加密的check

#include <stdio.h>
#include <stdint.h>
#include <windows.h>

void dec() {
    int key[4];

    __int64 v2; // rbx
    unsigned int v3; // er9
    int i; // esi
    unsigned int v5; // er10
    unsigned int v6; // edi
    unsigned int v7; // er11
    __int64 v8; // r8

    key[0] = 0x72;
    key[1] = 0x202;
    key[2] = 0x13;
    key[3] = 0x13;
    v3 = 0x63747443;
    //v5 == 0x1130BE1B && v3 == 0x63747443
    i = 32;
    v5 = 0x1130BE1B;
    v6 = v3;
    v7 = 0;

    for (int j = 0; j < 32; ++j) {
        v7 -= 0x61C88647;
    }
    do
    {

        v8 = (v7 >> 2) & 3;

        v3 -= ((v7 ^ v5) + (v5 ^ key[v8 ^ 1])) ^ (((16 * v5) ^ (v5 >> 3)) + ((v5 >> 5) ^ (4 * v5)));
        v6 = v3;
        v5 -= ((v7 ^ v3) + (v6 ^ key[v8])) ^ (((16 * v6) ^ (v3 >> 3)) + ((v6 >> 5) ^ (4 * v3)));

        v7 += 0x61C88647;
        
        --i;
    }
    while ( i );
    printf("0x%x, 0x%x", v5, v3);
    printf("\n");
}


int main()
{
    dec();
    return 0;
}
// 0x69746f64, 0x74697374

拿到明文dotitsit

下面的sub_2A44F9A1000sub_2A44F9A1150就是一个ChaCha20加密

拿到key:0N3@aYI_M3l0dy_KurOm1_W_Suk1dqy0

很容易想到之前得到的dotitsit就是nonce

# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: test23.py
@Time: 2021-11-14 16:06
@Desc: It's all about getting better.
"""
from Crypto.Cipher import ChaCha20
import struct

key = b"0N3@aYI_M3l0dy_KurOm1_W_Suk1dqy0"
enc = bytes.fromhex("EB8E5CA562B41C845C59FC0D433CAB20D8933313A19E39007614B504589D06B8")
# print(struct.pack("2L", 0x69746f64, 0x74697374))
flag = ChaCha20.new(key=key, nonce=b"dotitsit").decrypt(enc)
print(flag)

shell

进去一样是加载了shellcode,这里注意到Hook回调

int __fastcall sub_7FF712681560(_DWORD *a1)
{
  __int64 v1; // rax
  DWORD64 v2; // rax
  __int64 v3; // rax
  __m128i si128; // xmm1
  __int64 i; // rax
  __int64 j; // rax
  char Buffer[8]; // [rsp+30h] [rbp-58h] BYREF
  SIZE_T NumberOfBytesRead; // [rsp+38h] [rbp-50h] BYREF
  char v10[48]; // [rsp+40h] [rbp-48h] BYREF

  LODWORD(v1) = *a1;
  if ( *a1 == 0x80000003 )
  {
    v3 = qword_7FF712685630;
    if ( qword_7FF712685630 )
    {
      Context.ContextFlags = 0x10000B;
      if ( !GetThreadContext(hThread, &Context) )
      {
        GetLastError();
        sub_7FF712681010("GetThreadContext failed: %llx\n");
      }
      ReadProcessMemory(hProcess, (LPCVOID)(qword_7FF712685638 + 0x40A0), v10, 0x2Aui64, &NumberOfBytesRead);
      si128 = _mm_load_si128((const __m128i *)"xxxxxxxxxxxxxxxx");
      for ( i = 0i64; i < 32; i += 16i64 )
        *(__m128i *)&v10[i] = _mm_xor_si128(_mm_loadu_si128((const __m128i *)&v10[i]), si128);
      for ( j = 32i64; j < 42; ++j )
        v10[j] ^= 0x78u;
      WriteProcessMemory(hProcess, (LPVOID)(qword_7FF712685638 + 0x40A0), v10, 0x2Aui64, &NumberOfBytesRead);
      v3 = qword_7FF712685630;
    }
    v1 = v3 + 1;
    qword_7FF712685630 = v1;
    goto LABEL_21;
  }
  if ( (_DWORD)v1 == 0xC000001D )
  {
    Context.ContextFlags = 0x10000B;
    if ( !GetThreadContext(hThread, &Context) )
    {
      GetLastError();
      sub_7FF712681010("GetThreadContext failed: %llx\n");
    }
    ReadProcessMemory(hProcess, (LPCVOID)(Context.Rip + 1), Buffer, 1ui64, &NumberOfBytesRead);
    if ( Buffer[0] == 0x12 )
    {
      v2 = qword_7FF712685638 + 0x1035;
    }
    else
    {
      if ( Buffer[0] != 0x48 )
        goto LABEL_10;
      v2 = qword_7FF712685638 + 0x1242;
    }
    Context.Rip = v2;
LABEL_10:
    LODWORD(v1) = SetThreadContext(hThread, &Context);
    if ( !(_DWORD)v1 )
    {
      GetLastError();
      LODWORD(v1) = sub_7FF712681010("SetThreadContext failed: %llx\n");
    }
LABEL_21:
    *(_QWORD *)&dwContinueStatus = 0x10001i64;
  }
  return v1;
}

上火绒剑,dump下来

https://cdn.shi1011.cn/2021/11/5d15a05a839accaed26ad3771421a66f.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

没法正常解析

换种思路,CE直接附加,hook点下个断,停下来之后直接暴力搜输入的字符串

https://cdn.shi1011.cn/2021/11/d2a64c9de457cc4cc469e9be23f4b410.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

汇编搜int 3

https://cdn.shi1011.cn/2021/11/280d476019b1bb5e0621b6ac2e517d63.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

定位到

https://cdn.shi1011.cn/2021/11/0e2067caa04f924e5d2953124afcca13.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

简单分析下汇编,结合Hook的跳转,动调一下,就一个简单的位操作

https://cdn.shi1011.cn/2021/11/a60bb2d5fae2bea4d4874f96197427a4.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5
# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: test22.py
@Time: 2021-11-14 13:21
@Desc: It's all about getting better.
"""

from z3 import *

flag = [BitVec(f"flag[{i}]", 8) for i in range(42)]

enc = list(bytes.fromhex("1E151B1C074D1F1B12174B44475812475858475F5454584259575001495153573D6B3E6F3D6D6C3E692C"))
print(enc)
res = [0] * 42
for i in range(42):
    res[i] = flag[i] ^ 0x78


for i in range(42):
    tmp = (res[i] & i) ^ 0xff
    tmp2 = (tmp & res[i]) ^ 0xff
    tmp3 = (tmp & i) ^ 0xff
    tmp4 = (tmp2 & tmp3) ^ 0xff
    res[i] = tmp4


sol = Solver()
for i in range(42):
    sol.add(enc[i] == res[i])

assert sol.check() == sat
mol = sol.model()
print("".join([chr(mol.eval(i).as_long()) for i in flag]))

后记

shell这题,静态分析也是可以的,只不过需要自己手动修复一下偏移,patch一下花指令。相对而言此题算法相对简单,汇编量较小,侥幸而解;如若遇到大量汇编执行的情况,修复文件结构也许更具优势了吧。

本文链接:https://blog.shi1011.cn/ctf/1822
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

  • Scr1pt

    关于第二道题shellcode,想问下回调函数是哪个呀?或者说那个断点打在哪里呢?

    2年前 回复

翻车鱼

第七届湖湘杯网络安全技能大赛
感觉出题人是做病毒分析的…做的两题全是shellcode。规则定的很严,不乏py被禁赛的,不多做评论。赛时做了两题,做这类病毒分析题还是比较生疏的。正好当作学习了... Hideit 拿到的…
扫描二维码继续阅读
2021-11-15