2017第二届广东省强网杯线上赛Nonstandard OD分析 先运行一下,输入错误的话直接退出 查壳发现无壳,VC++2013写的
拖进OD,先搜索字符串,非常明显 在Please Input Flag后面和yes前的cmp判断下断点 运行起来跟一下,发现call Nonstand.00401480是对我们输入的字符串做变换,看来又是个算法题了,IDA-F5吧
IDA分析 找到提示所在位置,F5 看起来非常简单啊 我们只需要让sub_401480函数等于1就好了,所以我们进去看看 v1是我们输进去的,sub_401070是对输入的进行变换,得到v2 然后就是对每一位进行移位操作后与byte_402120进行比较 byte_402120的值为
nAdtxA66nbbdxA71tUAE2AOlnnbtrAp1nQzGtAQGtrjC7===
千万注意前面的n,差点让坑
然后我们就只需要分析sub_401070这个变换了,打开以后,
wtf。。。
我就只截取关键部分了
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;
和
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用“=”补足
好的,还有最后一个坑 这莫名其妙的sub_401000,点进去 这里是重新修改的密码表了,
字母倒序
奇数小写偶数大写
后面又加入765321 所以表的顺序是
zYxWvUtSrQpOnMlKjIhGfEdCbA765321
那我们自己写一个解密脚本
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
彩蛋 一共发现了两个彩蛋,一个在OD里,一个在IDA里
OD的找不到位置了。。。
ZmxhZyU3QmZsYWdfaXNfbm90X21lJTIxJTdE
IDA的
ZmxhZ3tmbGFnX2lzX25vdF9tZSF9
具体是什么,你可以拿base64解一下试试