avatar

目录
2017第二届广东省强网杯线上赛Nonstandard

2017第二届广东省强网杯线上赛Nonstandard

OD分析

先运行一下,输入错误的话直接退出
mark
查壳发现无壳,VC++2013写的

拖进OD,先搜索字符串,非常明显
mark
在Please Input Flag后面和yes前的cmp判断下断点
运行起来跟一下,发现call Nonstand.00401480是对我们输入的字符串做变换,看来又是个算法题了,IDA-F5吧

IDA分析

找到提示所在位置,F5
mark
看起来非常简单啊
我们只需要让sub_401480函数等于1就好了,所以我们进去看看
mark
v1是我们输进去的,sub_401070是对输入的进行变换,得到v2
然后就是对每一位进行移位操作后与byte_402120进行比较
byte_402120的值为
mark

nAdtxA66nbbdxA71tUAE2AOlnnbtrAp1nQzGtAQGtrjC7===

千万注意前面的n,差点让坑

然后我们就只需要分析sub_401070这个变换了,打开以后,

wtf。。。

mark
mark
mark
我就只截取关键部分了

c++
        v14 = v13;
HIDWORD(v15) = v19;
LODWORD(v15) = v23 & 0xFFFFFFF8;
HIDWORD(v16) = v30 | ((unsigned __int64)(v18 & 1) >> 24);
LODWORD(v16) = (((v23 & 0xFFFFFFF8) << 8) + (v18 & 0xFFFFFFC0 | ((v23 & 7) << 8)) + (v18 & 0x3E)) << 8;
v17 = ((v14 & 0x1F)
+ __PAIR__(
HIDWORD(v14) | (unsigned int)((unsigned __int64)(v20 & 3) >> 24),
v14 & 0xFFFFFFE0 | ((v20 & 3) << 8))
+ ((__PAIR__(
v31 | (unsigned int)((unsigned __int64)(v21 & 0xF) >> 24),
v20 & 0xFFFFFF80 | ((v21 & 0xF) << 8))
+ ((__PAIR__(
(__PAIR__(v15 >> 24, (v23 & 0xFFFFFFF8) << 8)
+ __PAIR__(
v29 | (unsigned int)((unsigned __int64)(v23 & 7) >> 24),
v18 & 0xFFFFFFC0 | ((v23 & 7) << 8))
+ (v18 & 0x3E)) >> 24,
v21 & 0xFFFFFFF0 | ((v18 & 1) << 8))
+ v16) << 8)
+ (v20 & 0x7C)) << 8)) >> 32;
HIDWORD(v14) = (v14 & 0x1F)
+ (v14 & 0xFFFFFFE0 | ((v20 & 3) << 8))
+ (((v20 & 0xFFFFFF80 | ((v21 & 0xF) << 8))
+ (((v21 & 0xFFFFFFF0 | ((v18 & 1) << 8)) + (_DWORD)v16) << 8)
+ (v20 & 0x7C)) << 8);
*v24 = byte_403020[(unsigned __int8)v17 >> 3];
v24[1] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 30) & 0x1F];
v24[2] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 25) & 0x1F];
v24[3] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 20) & 0x1F];
v24[4] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 15) & 0x1F];
v24[5] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 10) & 0x1F];
LOBYTE(v16) = __PAIR__(v17, HIDWORD(v14)) >> 5;
v3 = v25;
v24[6] = byte_403020[v16 & 0x1F];
LOBYTE(v16) = byte_403020[BYTE4(v14) & 0x1F];
v2 = v32;
v24[7] = v16;
v24 += 8;
}
while ( v8 < v25 );
result = v28;
}
if ( v22 > 0 )
memset(&result[v26], 61u, v22);
*(&v28[v26] + v22) = 0;
result = v28;
}
return result;
}

这么一大堆有点烦啊,想到我们要做对比的那一串是base64编码的,那么这个算法是不是呢,仔细看一下发现和base64结构相似,但这里
LODWORD(v16) = (((v23 & 0xFFFFFFF8) << 8) + (v18 & 0xFFFFFFC0 | ((v23 & 7) << 8)) + (v18 & 0x3E)) << 8;

Code
v24[1] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 30) & 0x1F];
v24[2] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 25) & 0x1F];
v24[3] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 20) & 0x1F];
v24[4] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 15) & 0x1F];
v24[5] = byte_403020[(__PAIR__(v17, HIDWORD(v14)) >> 10) & 0x1F];

每次取5个比特,分别赋给8个值,每个值5个位 ,这是base32

base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3。

同样,base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。

base16就是用16(2的4次方)个特定ASCII码表示256个ASCII字符。1个ASCII字符经过base16编码后会变为2个字符,长度增加一倍。不足2n用“=”补足

好的,还有最后一个坑
mark
这莫名其妙的sub_401000,点进去
mark
这里是重新修改的密码表了,

  1. 字母倒序

  2. 奇数小写偶数大写

  3. 后面又加入765321
    所以表的顺序是

    zYxWvUtSrQpOnMlKjIhGfEdCbA765321

那我们自己写一个解密脚本

Code
s = "nAdtxA66nbbdxA71tUAE2AOlnnbtrAp1nQzGtAQGtrjC7==="
table = "zYxWvUtSrQpOnMlKjIhGfEdCbA765321"

def find(x):
if(x=='='):
return 0
return table.index(x)

for i in range(len(s)//8):
p = s[i*8:i*8+8]
t = 0
for j in p:
t = t<<5
t += find(j)
for j in range(5):
print(chr((t&0xff00000000)>>32), end='')
t = t<<8

运行得到flag
mark

彩蛋

一共发现了两个彩蛋,一个在OD里,一个在IDA里

OD的找不到位置了。。。

ZmxhZyU3QmZsYWdfaXNfbm90X21lJTIxJTdE

IDA的
mark

ZmxhZ3tmbGFnX2lzX25vdF9tZSF9

具体是什么,你可以拿base64解一下试试

文章作者: kabeor
文章链接: https://kabeor.github.io/2017%E7%AC%AC%E4%BA%8C%E5%B1%8A%E5%B9%BF%E4%B8%9C%E7%9C%81%E5%BC%BA%E7%BD%91%E6%9D%AF%E7%BA%BF%E4%B8%8A%E8%B5%9BNonstandard/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 K's House

评论