吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9377|回复: 16
收起左侧

[转贴] 【转帖】一道有意思的魔改base64逆向

  [复制链接]
默小白 发表于 2019-3-12 10:08

转自:https://xz.aliyun.com/t/4302

STEM CTF Cyber Challenge 2019的一道400分的逆向题,做题过程中学到了很多以前忽略的小知识点,以下是详细复现记录

REbase-fix

简单交互

先file一下,发现是x86-64的elf

➜  Desktop file REbase-fix 
REbase-fix: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

IDA打开后发现没有main函数,应该是加了壳

strings后发现,最后两行有UPX!的字符串

img

大概率是upx壳,于是直接upx -d脱壳后扔IDA分析

不过还是找不到main,应该是做了去符号的处理

和程序交互,先找找有没有字符串

img

程序接收一个命令行参数,看起来像是进行了base64编码,要求相等

输出1111111的base64编码值比较,发现并不相等

img

题目名字是REbase-fix,猜测意思是修复base系列编码

通过这两个测试字符串1111111,可以发现,程序输出的DZTb就相当于标准base64的MTEx

应该是可以手动测试自己探测出程序的base表,但是既然这里看到了Try Again :(,在IDA中搜索一下

跟着交叉引用,F5后发现关键代码

img

但是发现,即是根据功能猜测sub_4098B0是输出函数,点进去也会看到一堆复杂的代码 ......

又想到既然是base64的魔改,那程序中应该会有base码表

但是字符串窗口中里有太多字符串了,因为能找到Try Again :(",暂且认为字符串没有加密,但是也不能精准的找到关键的码表

这时感觉没有突破口了

尝试angr

尝试写一个angr脚本

import angr
import claripy

proj = angr.Project("./REbase-fix",auto_load_libs=False)
argv1 = claripy.BVS('argv1',50*8)
state = proj.factory.entry_state(args=["./REbase-fix",argv1])
simgr = proj.factory.simgr(state)
simgr.explore(find = 0x402070,avoid = 0x402084)

由于是命令行参数,用到这个claripy库,但是跑了两个多小时也没跑出来

我感觉爆破base64编码应该用不到这么久吧,毕竟没有多二进制码进行过多操作,可能是程序里有暗桩

查找码表

于是这条路又走不通了,参考一下其他大佬的writeup

发现一条命令strings REbase-fix | grep -x '.\{30,\}' | head

用来搜索长度大于等于30的字符串

img

学到了...用strings配合正则表达式

看上去是滚键盘得到的字符串,长度也正好,看起来就是base的码表了

魔改base64解码

现在只要找一个base64的实现,把码表改成我们自己的就可以了

我用的cpp实现,但是代码太长,找了一个好用的python实现

import re

def base64_encode(s, dictionary):
    r = ""
    p = ""
    c = len(s) % 3

    if (c > 0):
        for i in range(c, 3):
            p += '='
            s += "\0"

    for c in range(0, len(s), 3):
        n = (ord(s[c]) << 16) + (ord(s[c+1]) << 8) + (ord(s[c+2]))
        n = [(n >> 18) & 0x3F, (n >> 12) & 0x3F, (n >> 6) & 0x3F, n & 0x3F]
        r += dictionary[n[0]] + dictionary[n[1]] + dictionary[n[2]] + dictionary[n[3]]
    return r[0:len(r) - len(p)]  + p

def base64_decode(s, dictionary):
    base64inv = {}
    for i in range(len(dictionary)):
        base64inv[dictionary[i]] = i

    s = s.replace("\n", "")
    if not re.match(r"^([{alphabet}]{{4}})*([{alphabet}]{{3}}=|[{alphabet}]{{2}}==)?$".format(alphabet = dictionary), s):
        raise ValueError("Invalid input: {}".format(s))

    if len(s) == 0:
        return ""
    p = "" if (s[-1] != "=") else "AA" if (len(s) > 1 and s[-2] == "=") else "A"
    r = ""
    s = s[0:len(s) - len(p)] + p
    for c in range(0, len(s), 4):
        n = (base64inv[s[c]] << 18) + (base64inv[s[c+1]] << 12) + (base64inv[s[c+2]] << 6) + base64inv[s[c+3]]
        r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255)
    return r[0:len(r) - len(p)]
def test_base64():
    import base64
    import string
    import random
    dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    def random_string(length):
        return ''.join(random.choice(string.ascii_letters) for m in range(length))
    for i in range(100):
        s = random_string(i)
        encoded = base64_encode(s, dictionary)
        assert(encoded == base64.b64encode(s))
        assert(s == base64_decode(encoded, dictionary))
if __name__ == "__main__":
    dictionary =  "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbn/+m1234567890"
    print(base64_decode("ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8=", dictionary), end='')

img

把这段丢给程序,发现输出有乱码,而且因为flag格式是MCA{},也没有预期的}

丢给程序发现还差了一点

img

因为最后一个字符是},于是手动测几个

img

看上去}之前的字符不管是什么都会输出Congratulations!,不符合常理,字符串编码后应该是一对一的

但是由于是赛后复现,无法和服务器交互,其他writeup上说服务器只接受

{Th15_wUz_EaZy_Pe@Zy_L3m0n_SqU33zy}

最后的思考

linux下的命令,例如stringsgrep配合管道符的使用需要多加练习,在CTF中很可能可以作为突破口


rebase.zip (271.69 KB, 下载次数: 30)

免费评分

参与人数 1吾爱币 +1 收起 理由
a0gbe5sgpp5 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

fuckbin 发表于 2019-4-28 21:06
本帖最后由 fuckbin 于 2019-4-28 21:12 编辑

编码表就在 sub_401BED这个函数里
__int64 __fastcall sub_401BED(__int64 a1)
{
  int v2; // [rsp+10h] [rbp-60h]
  int v3; // [rsp+14h] [rbp-5Ch]
  int v4; // [rsp+18h] [rbp-58h]
  int v5; // [rsp+1Ch] [rbp-54h]
  int v6; // [rsp+20h] [rbp-50h]
  int v7; // [rsp+24h] [rbp-4Ch]
  int v8; // [rsp+28h] [rbp-48h]
  int v9; // [rsp+2Ch] [rbp-44h]
  int v10; // [rsp+30h] [rbp-40h]
  int v11; // [rsp+34h] [rbp-3Ch]
  int v12; // [rsp+38h] [rbp-38h]
  int v13; // [rsp+3Ch] [rbp-34h]
  __int64 v14; // [rsp+40h] [rbp-30h]
  int v15; // [rsp+4Ch] [rbp-24h]
  int v16; // [rsp+50h] [rbp-20h]
  int v17; // [rsp+54h] [rbp-1Ch]
  const char *v18; // [rsp+58h] [rbp-18h]
  int i; // [rsp+64h] [rbp-Ch]
  int v20; // [rsp+68h] [rbp-8h]
  int v21; // [rsp+6Ch] [rbp-4h]

  v18 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbn/+m1234567890";
  v17 = sub_4010C0(a1);
  sub_408DF0((unsigned __int64)"%d\n");
  v16 = v17 % 3;
  v15 = v17 / 3;
  v21 = 4 * (v17 / 3) + 1;
  if ( v17 % 3 > 0 )
    v21 += 4;
  v14 = sub_4157A0(v21, (unsigned int)v17);
  sub_401088(v14, 0LL, v21);
  *(_BYTE *)(v21 + v14) = 0;
  v13 = 0;
  v2 = 0;
  v20 = 0;
  v12 = 16515072;
  v11 = 258048;
  v10 = 4032;
  v9 = 63;
  for ( i = 0; i < v15; ++i )
  {
    v8 = 3 * i;
    sub_401038(&v2, 3 * i + a1, 3LL);
    v20 = v2 & 0xFF00 | (v2 << 16) & 0xFF0000 | (v2 >> 16);
    v7 = (v12 & v20) >> 18;
    v6 = (v11 & v20) >> 12;
    v5 = (v10 & v20) >> 6;
    v13 = v9 & v20;
    *(_BYTE *)(4 * i + v14) = v18[v7];
    *(_BYTE *)(4 * i + 1LL + v14) = v18[v6];
    *(_BYTE *)(4 * i + 2LL + v14) = v18[v5];
    *(_BYTE *)(4 * i + 3LL + v14) = v18[v13];
  }
  if ( v16 > 0 )
  {
    v4 = 3 * v15;
    v3 = 4 * v15;
    sub_401038(&v2, a1 + 3 * v15, v16);
    if ( v16 == 1 )
    {
      v6 = v9 & v20;
      v7 = v9 & (v20 >> 6);
      *(_BYTE *)(v3 + v14) = v18[v9 & (v20 >> 6)];
      *(_BYTE *)(v3 + 1LL + v14) = v18[v6];
      *(_BYTE *)(v3 + 2LL + v14) = 61;
      *(_BYTE *)(v3 + 3LL + v14) = 61;
    }
    if ( v16 == 2 )
    {
      v20 = v2 >> 8;
      v5 = v9 & (v2 >> 8);
      v6 = v9 & (v2 >> 14);
      v7 = v9 & (v2 >> 20);
      *(_BYTE *)(v3 + v14) = v18[v9 & (v2 >> 20)];
      *(_BYTE *)(v3 + 1LL + v14) = v18[v6];
      *(_BYTE *)(v3 + 2LL + v14) = v18[v5];
      *(_BYTE *)(v3 + 3LL + v14) = 61;
    }
  }
  return v14;
}
再贴一记大佬的代码,学习一下
import base64a = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbn/+m1234567890"b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"base_fix = "ZXFWtmKgDZCyrmC5B+CiVfsyXUCQVfsyZRFzDU4yX2YCD/F5Ih8="table = ''.maketrans(a, b)print(base64.b64decode(base_fix.translate(table)))

冰露㊣神 发表于 2019-3-12 13:00
其实strings不过是linux下一个命令,没必要纠结这个,你strings能查到,ida里肯定也能查到,何不用shift+f12去看下里面有没有什么东西呢
fsdfdsfsdfdsf 发表于 2019-3-12 10:48
sensMe 发表于 2019-3-12 12:49
看不懂,前排膜拜一下,,
成都 发表于 2019-3-12 13:49

看不懂,前排膜拜一下
a77582508 发表于 2019-3-13 09:12
每每看到这样子的帖子,都不禁感叹!!好厉害
yaoyao7 发表于 2019-3-13 09:25
支持4楼的说法,我感觉shift+f12应该可以看到,待我试试~
不小于3个字符 发表于 2019-3-13 11:09
看不懂,前排膜拜一下
淡紫星辰 发表于 2019-3-14 08:23 来自手机
新手完全看不懂。。厉害
pkttttt 发表于 2019-3-14 23:29
新手完全看不懂。。厉害
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 02:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表