【转帖】一道有意思的魔改base64逆向
转自: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!`的字符串
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175441-883029e6-3e63-1.png)
大概率是upx壳,于是直接`upx -d`脱壳后扔IDA分析
不过还是找不到`main`,应该是做了去符号的处理
和程序交互,先找找有没有字符串
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175450-8d6e3344-3e63-1.png)
程序接收一个命令行参数,看起来像是进行了base64编码,要求相等
输出`111`和`1111`的base64编码值比较,发现并不相等
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175531-a57ea112-3e63-1.png)
题目名字是`REbase-fix`,猜测意思是修复base系列编码
通过这两个测试字符串`111`和`1111`,可以发现,程序输出的`DZTb`就相当于标准base64的`MTEx`
应该是可以手动测试自己探测出程序的base表,但是既然这里看到了`Try Again :(`,在IDA中搜索一下
跟着交叉引用,F5后发现关键代码
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175539-aa731112-3e63-1.png)
但是发现,即是根据功能猜测`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的字符串
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175549-b0aa64fe-3e63-1.png)
学到了...用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) << 16) + (ord(s) << 8) + (ord(s))
n = [(n >> 18) & 0x3F, (n >> 12) & 0x3F, (n >> 6) & 0x3F, n & 0x3F]
r += dictionary] + dictionary] + dictionary] + dictionary]
return r+ p
def base64_decode(s, dictionary):
base64inv = {}
for i in range(len(dictionary)):
base64inv] = 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 + p
for c in range(0, len(s), 4):
n = (base64inv] << 18) + (base64inv] << 12) + (base64inv] << 6) + base64inv]
r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255)
return r
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='')
```
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175604-b9a00aaa-3e63-1.png)
把这段丢给程序,发现输出有乱码,而且因为flag格式是`MCA{}`,也没有预期的`}`
丢给程序发现还差了一点
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175615-c041517a-3e63-1.png)
因为最后一个字符是`}`,于是手动测几个
!(https://xzfile.aliyuncs.com/media/upload/picture/20190304175624-c51ba83a-3e63-1.png)
看上去`}`之前的字符不管是什么都会输出`Congratulations!`,不符合常理,字符串编码后应该是一对一的
但是由于是赛后复现,无法和服务器交互,其他writeup上说服务器只接受
```
{Th15_wUz_EaZy_Pe@Zy_L3m0n_SqU33zy}
```
## 最后的思考
linux下的命令,例如`strings`和`grep`配合管道符的使用需要多加练习,在CTF中很可能可以作为突破口
本帖最后由 fuckbin 于 2019-4-28 21:12 编辑
编码表就在 sub_401BED这个函数里
__int64 __fastcall sub_401BED(__int64 a1)
{
int v2; //
int v3; //
int v4; //
int v5; //
int v6; //
int v7; //
int v8; //
int v9; //
int v10; //
int v11; //
int v12; //
int v13; //
__int64 v14; //
int v15; //
int v16; //
int v17; //
const char *v18; //
int i; //
int v20; //
int v21; //
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;
*(_BYTE *)(4 * i + 1LL + v14) = v18;
*(_BYTE *)(4 * i + 2LL + v14) = v18;
*(_BYTE *)(4 * i + 3LL + v14) = v18;
}
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;
*(_BYTE *)(v3 + 1LL + v14) = v18;
*(_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;
*(_BYTE *)(v3 + 1LL + v14) = v18;
*(_BYTE *)(v3 + 2LL + v14) = v18;
*(_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)))
其实strings不过是linux下一个命令,没必要纠结这个,你strings能查到,ida里肯定也能查到,何不用shift+f12去看下里面有没有什么东西呢 不错,不过有点问题 看不懂,前排膜拜一下,,
看不懂,前排膜拜一下 每每看到这样子的帖子,都不禁感叹!!好厉害 支持4楼的说法,我感觉shift+f12应该可以看到,待我试试~ 看不懂,前排膜拜一下{:1_901:} 新手完全看不懂。。厉害 新手完全看不懂。。厉害
页:
[1]
2