DDCTF2019-Writeup

目录

Windows Reverse1

Windows Reverse2

confused

obfuscating macros

黑盒破解2

北京地铁

MulTzor

[PWN] strike

Wireshark

联盟决策大会

滴~

Web签到题

Upload-IMG

大吉大利,今晚吃鸡~

Breaking LEM 


Windows Reverse1

比较基础的一道逆向,加了UPX,直接用upx -d脱upx后,程序重定位会出问题,导致不好调试。解决方法是关闭地址随机化或带着壳调试。

主函数逻辑是将输入进行一次变换后与明文进行对比。变换如下:

unsigned int __cdecl sub_401000(const char *a1)
{
  _BYTE *v1; // ecx
  unsigned int v2; // edi
  unsigned int result; // eax
  int v4; // ebx

  v2 = 0;
  result = strlen(a1);
  if ( !result )
    return result;
  v4 = a1 - v1;
  do
  {
    *v1 = byte_402FF8[(char)v1[v4]];
    ++v2;
    ++v1;
    result = strlen(a1);
  }
  while ( v2 < result );
  return result;
}

这段代码无论是汇编形式还是反编译形式都有点诡异,还是得调试一下,发现这里就是将输入的每个字节作为偏移,以byte_402FF8为基址进行置换,置换表如下:

.data:00403018 aZyxwvutsrqponm db '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>'
.data:00403018                 db '=<;:9876543210/.-,+*)(',27h,'&%$#"! ',0

由于byte_402FF8到这张表之间还有许多辣鸡数据,所以我们的输入需要先减去(0x403018-0x402FF8),再进行索引,即可完成置换。而这里我们需要将"DDCTF{reverseME}"还原为输入,这个逆过程很简单,随便写个脚本:

table = '''~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"! '''
s = 'DDCTF{reverseME}'

flag = ''
for c in s:
    flag += chr(table.find(c) + 0x20)
print flag

解出来真是服了,没见过这么丑的flag

运行结果:

please input code:ZZ[JX#,9(9,+9QY!
You've got it!!DDCTF{reverseME}

Windows Reverse2

依旧是Win32程序,这次是有ASpack壳,和UPX类似,它也是压缩壳,这种东西用所谓的esp定律十分好找到入口点,不过为了速度,就不手脱了,随便去找个ASpack的脱壳机脱了就行,不过这个程序的ASpack版本较新,所以脱了后运行起来也有点问题,但这个题纯静态就可以解决了。

主程序逻辑和第1题相同,将输入进行变换与明文比较。而在这之前,有个对输入的判断

char __usercall sub_10611F0@<al>(const char *a1@<esi>)
{
  signed int v1; // eax
  signed int v2; // edx
  int v3; // ecx
  char v4; // al

  v1 = strlen(a1);
  v2 = v1;
  if ( !v1 || v1 % 2 == 1 )
    return 0;
  v3 = 0;
  if ( v1 <= 0 )
    return 1;
  while ( 1 )
  {
    v4 = a1[v3];
    if ( (v4 < '0' || v4 > '9') && (v4 < 'A' || v4 > 'F') )
      break;
    if ( ++v3 >= v2 )
      return 1;
  }
  return 0;
}

显然这就是说我们的输入必须为十六进制数,且为长度偶数。然后进到变换函数中

int __usercall sub_1061240@<eax>(const char *a1@<esi>, int a2)
{
  signed int v2; // edi
  signed int v3; // edx
  char v4; // bl
  char v5; // al
  char v6; // al
  unsigned int v7; // ecx
  char v9; // [esp+Bh] [ebp-405h]
  char v10; // [esp+Ch] [ebp-404h]
  char Dst; // [esp+Dh] [ebp-403h]

  v2 = strlen(a1);
  v10 = 0;
  memset(&Dst, 0, 0x3FFu);
  v3 = 0;
  if ( v2 <= 0 )
    return sub_1061000(v2 / 2, a2);
  v4 = v9;
  do
  {
    v5 = a1[v3];
    if ( (unsigned __int8)(a1[v3] - '0') > 9u )
    {
      if ( (unsigned __int8)(v5 - 'A') <= 5u )
        v9 = v5 - '7';
    }
    else
    {
      v9 = a1[v3] - '0';
    }
    v6 = a1[v3 + 1];
    if ( (unsigned __int8)(a1[v3 + 1] - '0') > 9u )
    {
      if ( (unsigned __int8)(v6 - 'A') <= 5u )
        v4 = v6 - '7';
    }
    else
    {
      v4 = a1[v3 + 1] - '0';
    }
    v7 = (unsigned int)v3 >> 1;
    v3 += 2;
    *(&v10 + v7) = v4 | 16 * v9;
  }
  while ( v3 < v2 );
  return sub_1061000(v2 / 2, a2);
}

这段代码将输入的每个字符变为一个数值,0~F分别对应十六进制数的值,并将两个字符作为一组生成新的数值,如s[0]*16 + s[1],乘16意味着左移4位,这完全就是将十六进制字符串转换为数值的函数。接着进入最后那个函数中,看几个关键点吧

v15[0] = Dst[0] >> 2;
v15[1] = (Dst[1] >> 4) + 16 * (Dst[0] & 3);
v15[2] = (Dst[2] >> 6) + 4 * (Dst[1] & 0xF);
v15[3] = Dst[2] & 0x3F;
i = 0;
do
    std::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator+=(
    &v17,
    (unsigned __int8)(byte_1063020[(unsigned __int8)v15[i++]] ^ 0x76));
while ( i < 4 );

可以看到这里将3个字符转换位了4个值,然后依次索引一张表,这不就是base64的编码方式嘛。我们去看那张表

'7452301>?<=:;89&\'$%"# !./,\x17\x14\x15\x12\x13\x10\x11\x1e\x1f\x1c\x1d\x1a\x1b\x18\x19\x06\x07\x04\x05\x02\x03\x00\x01\x0e\x0f\x0cFGDEBC@ANO]Y'

看起来很奇怪,但可以注意到查表时进行了xor 0x76的操作,如果我们将这个表xor 0x76会发现它正好就是标准base64的编码表

也就是说我们直接对"reverse+"进行base64解码就行了,得到的十六进制串就是flag,需要注意这里是大小字符

运行结果:

input code:ADEBDEAEC7BE
You've got it !!! DDCTF{reverse+}

confused

看到mac程序吓了跳,因为没有运行环境,不过这个题也是可以纯静态分析的。进去一堆看不懂的,但貌似是mac的某种GUI程序,IDA函数列表中有几个ViewController开头的函数,应该就是与控件有关的函数了,其中checkCode十分显眼,进入这个函数很快就能意识到它就是输入验证函数,很容易找到check点,进入check函数,其中一个函数对一个结构体进行了初始化,其中有许多成员是函数指针,看得不是很清楚,之后我们能找到一个关键函数

bool __fastcall run(vm_struc *a1)
{
  bool result; // al
  bool count; // [rsp+Fh] [rbp-11h]
  signed int i; // [rsp+10h] [rbp-10h]
  signed int k; // [rsp+14h] [rbp-Ch]

  k = 0;
  i = 0;
  while ( 1 )
  {
    count = 0;
    if ( !k )
      count = i < 9;
    result = count;
    if ( !count )
      break;
    if ( *(unsigned __int8 *)a1->pc == *((unsigned __int8 *)&a1->code_F0 + 16 * i) )
    {
      k = 1;
      (*((void (__fastcall **)(vm_struc *))&a1->mov_reg_imm + 2 * i))(a1);
    }
    else
    {
      ++i;
    }
  }
  return result;
}

虽然F5的代码很烂,但仔细分析下就能发现它是通过一串数据来对结构体中的函数表进行索引执行,很显然,这是个解释器,意识到它是虚拟机保护后,初始化结构体的逆向就很容易了,每个函数都是一个handler,但本题用到的指令不多

__int64 __fastcall init_vm(vm_struc *a1, char *input)
{
  a1->reg0 = 0;
  a1->reg1 = 0;
  a1->reg2 = 0;
  a1->reg3 = 0;
  a1->flag = 0;
  a1->buffer = 0;
  LOBYTE(a1->code_F0) = -16;
  a1->mov_reg_imm = (__int64)mov_reg_imm;
  LOBYTE(a1->code_F1) = -15;
  a1->xor_reg0_reg1 = (__int64)xor_reg0_reg0;

  LOBYTE(a1->code_F2) = -14;
  a1->cmp_reg0_imm = (__int64)cmp_reg0_imm;
  LOBYTE(a1->code_F4) = -12;
  a1->add_reg0_reg1 = (__int64)add_reg0_reg1;
  LOBYTE(a1->code_F5) = -11;
  a1->sub_reg0_reg1 = (__int64)sub_reg0_reg1;
  LOBYTE(a1->code_F3) = -13;
  a1->nop = (__int64)nop;
  LOBYTE(a1->code_F6) = -10;
  a1->jz_imm = (__int64)jz_imm;
  LOBYTE(a1->code_F7) = -9;
  a1->mov_buf_imm = (__int64)mov_buf_imm;
  LOBYTE(a1->code_F8) = -8;
  a1->enc_reg0_2 = (__int64)enc_reg0_2;
  buffer = (char *)malloc(0x400uLL);
  return __memcpy_chk((__int64)(buffer + 48), (__int64)input, 18LL, -1LL);
}

那么内存中那段数据就是就是指令了,我们将它dump下来,然后根据指令特征写个类似反汇编器的脚本就能还原出真正的执行过程,脚本如下,写得比较烂

code = [0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x30, 0xF6, 0xC1, 0xF0, 0x10, 0x63, 0x00, 0x00, 
        0x00, 0xF8, 0xF2, 0x31, 0xF6, 0xB6, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x32, 0xF6, 
        0xAB, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x33, 0xF6, 0xA0, 0xF0, 0x10, 0x6D, 0x00, 
        0x00, 0x00, 0xF8, 0xF2, 0x34, 0xF6, 0x95, 0xF0, 0x10, 0x57, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x35, 
        0xF6, 0x8A, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x36, 0xF6, 0x7F, 0xF0, 0x10, 0x73, 
        0x00, 0x00, 0x00, 0xF8, 0xF2, 0x37, 0xF6, 0x74, 0xF0, 0x10, 0x45, 0x00, 0x00, 0x00, 0xF8, 0xF2, 
        0x38, 0xF6, 0x69, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x39, 0xF6, 0x5E, 0xF0, 0x10, 
        0x72, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3A, 0xF6, 0x53, 0xF0, 0x10, 0x52, 0x00, 0x00, 0x00, 0xF8, 
        0xF2, 0x3B, 0xF6, 0x48, 0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3C, 0xF6, 0x3D, 0xF0, 
        0x10, 0x63, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3D, 0xF6, 0x32, 0xF0, 0x10, 0x44, 0x00, 0x00, 0x00, 
        0xF8, 0xF2, 0x3E, 0xF6, 0x27, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3F, 0xF6, 0x1C, 
        0xF0, 0x10, 0x79, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x40, 0xF6, 0x11, 0xF0, 0x10, 0x65, 0x00, 0x00, 
        0x00, 0xF8, 0xF2, 0x41, 0xF6, 0x06, 0xF7, 0x01, 0x00, 0x00, 0x00, 0xF3, 0xF7, 0x00, 0x00, 0x00, 
        0x00, 0xF3, 0x5D, 0xC3, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]

table = {0xF0:(6, 'mov_reg_imm'), 0xF1:(2, 'xor_reg0_reg1'), 0xF2:(2, 'cmp_reg0_imm'), 0xF4:(2, 'add_reg0_reg1'),
         0xF5:(2, 'sub_reg0_reg1'), 0xF3:(1, 'ret'),         0xF6:(2, 'jz_imm'),      0xF7:(5, 'mov_buf_imm'), 
         0xF8:(1, 'enc_reg0_2')}

pc = 0
while pc < len(code):
    c = code[pc]
    print hex(pc), '\t',
    if c not in table.keys():
        print 'nop'
        pc += 1
        continue
    print table[c][1],
    if table[c][0] == 1:
        pc += 1
    elif table[c][0] =&#
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值