Gslab游戏安全竞赛学生组WP
本帖最后由 FraMeQ 于 2017-7-27 12:36 编辑比赛挺遗憾的,第一题验证楞是没看出来是一个delta=0的等式,一直以为解出来的方程会有小数,无法想象程序怎么验证成功。导致没比赛资格了。数学功力还得加强,下面仅是学生组安卓的分析。有兴趣的同学关注下[我的博客](http://www.get1t.cn),一起交流学习!
### Round1
首先程序校验了key的格式,格式应该为XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX,长度为32,仅含有`,`
其次取每一段进行运算,首尾进行运算,结果记为`a1,a2,a3,a6`(在接下来给的注册机中会有这一段运算)
接下来是对Code进行Base64解码,这里的Base64并不是标准的Base64,改动在于索引的下标进行了异或。所以解Base64也必须异或回去,解码出来的长度限制为0x10,前8位记为x,后8位记为y(由于之前没做出来,所以就没有看进阶版部分,进阶版部分请移步至[看雪](http://bbs.pediy.com/thread-219114.htm)),
接下来就是方程组求解问题(用x86版本的so能清楚的看到方程式)
[![](http://www.get1t.cn/content/images/2017/07/ICYUKRW-F-N2LR-ZWT-FNIX.png)](http://www.get1t.cn/content/images/2017/07/ICYUKRW-F-N2LR-ZWT-FNIX.png)
> a3 + a6 \* (a2 + a1 \* a6) = ( y + x \* a6 )
(a2 - x) \* (a2 - x) = 4 \* (a3 - y) \* a1
用python的sympy库求解很方便。直接附上注册机
```python
import numpy as np
import struct
from sympy import *
base='OPWvYny#Nopz0$HI34QRSG@dJKq7fghD9Zi*kAB8rsFu56L&Ca^2tTUVEewxlm+/='
def encodeBase64(string):
tmp = len(string)%3
if tmp == 1:
string += '\x00\x00'
if tmp == 2:
string += '\x00'
en = ''
for i in range(len(string)/3):
a = ord(list(string)) >> 2
b = (ord(list(string)) & 0x3) *0x10 + (ord(list(string)) >> 4)
c = ((ord(list(string)) & 0xF) * 4 ) + (ord(list(string)) >> 6)
d = ord(list(string)) & 0x3F
a = a^(a>>4)
b = b^(b>>4)
c = c^(c>>4)
d = d^(d>>4)
en += base
en += base
en += base
en += base
if tmp == 1:
en = en[:-2] + '=='
if tmp == 2:
en = en[:-1] + '='
return en
def decodeBase64(string):
out = ''
if (len(string)%4) != 0:
return false
for i in range(len(string)/4):
a = list(base).index(list(string))%64
b = list(base).index(list(string))%64
c = list(base).index(list(string))%64
d = list(base).index(list(string))%64
a = a^(a>>4)
b = b^(b>>4)
c = c^(c>>4)
d = d^(d>>4)
out += chr((a << 2) + (b & 0x3F)/0x10)
out += chr((b & 0xF)*0x10 + (c >> 2))
out += chr((c & 0x3)*0x40 + d)
return out
def calc(key):
l = key.split('-')
a = ord(l) * ord(l[-1]) * 0x10000
a += ord(l) ^ ord(l[-1])
a += ord(l) % (ord(l[-1])+1) + 1
a += ord(l) / (ord(l[-1]) + 1)
b = (ord(l) ^ ord(l[-2])) * 0x10000
b += ord(l) % (ord(l[-2])+3)
b += ord(l) / (ord(l[-2])+1) + 5
b += ord(l) + (ord(l[-2]))
c = ord(l) / (ord(l[-3]) + 3) * 0x10000
c ^= ord(l) * ord(l[-3])
c += ord(l) % (ord(l[-3]) + 7) + 5
c += ord(l) + ord(l[-3])
d = (ord(l) + ord(l[-4])) * 0x10000
d *= ord(l) / (ord(l[-4]) + 2)
d += ord(l) % (ord(l[-4]) + 5) + 7
d += ord(l) * ord(l[-4])
return a,b,c,d
def tohex(val, nbits):
return (val + (1 << nbits)) % (1 << nbits)
if __name__ == "__main__":
#key = '0234-5678-90ab-00ef-0f87-6543-21fe-0cba'
key = 'aaaa-aaaa-aaaa-aaaa-aaaa-aaaa-aaaa-aaaa'
#key = '0234-5678-90ab-00ef-0f87-6543-21fe-cccc'
#key = 'aaaa-1111-2222-3333-4444-5555-6666-7777'
a1,a2,a3,a6 = calc(key)
#print hex(a1),hex(a2),hex(a3),hex(a6)
x = Symbol('x')
y = Symbol('y')
"""
a3 + a6 * (a2 + a1 * a6) = ( y + x * a6 )
(a2 - x) * (a2 - x) = 4 * (a3 - y) * a1
"""
result = solve(,)
x1 = int(result)
y1 = int(result)
#print 'The solve is %x,%x' % (x1,y1)
while x1 < 0:
x1 = tohex(x1,64)
while y1 < 0:
y1 = tohex(y1,64)
print 'The solve is %x,%x' % (x1,y1)
str = struct.pack('<Q',x1)
str += struct.pack('<Q',y1)
print 'Key: %s' % key
print 'code: %s' % encodeBase64(str)
```
----------------------
### Round2#1
首先题目给了一个main函数,验证函数有两个,我们只需要编写so让main函数加载,导出`zapus_get`函数即可。
####Check1
这里我先给出从伪代码还原成C代码(为了节省篇幅,这里省略了box)
```c
#include <stdio.h>
unsigned char box = {
};
int main()
{
//unsigned char in = {0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF};
unsigned char in = {0x91,0x8f,0x45,0xd4,0xed,0xf4,0x4b,0x37,0x5f,0xa8,0x15,0xa,0x95,0x6,0xe2,0x33};
unsigned char out = {0,};
//char out = {0xFF,0x99,0x63,0x6B,0x30,0xCF,0xD1,0x4A,0x39,0x09,0x64,0x17,0x73,0xED,0xE4,0x47};
int i,j;
unsigned char v8=0,v10=0,v6=0,v11=0;
int index=0;
for(i=0;i<0x80;i++)
{
v6 = 0;
for(j=0;j<0x80;j++)
{
index = 7-(j&0x7);
v8 = in;
v10 = box;
v6 ^= ((v8 & v10) >> index);
v6 = v6 & 1;
}
v11 = i & 7;
out = out | (v6<<(7-v11));
}
for(i=0;i<0x10;i++)
printf("%02X ",out);
}
```
其实这段代码就是一个异或方程式求解,内层循环是取每一个bit 一共取0x80次之后,做与运算(其实这里就是乘法),之后结果异或(其实这里就是一个模2的加法运算),那么这里应该很清楚了,接下来就是解异或方程式了。
> 异或方程组就是形如这个样子的方程组:
Mx^Mx^…^Mx=B
Mx^Mx^…^Mx=B
…
Mx^Mx^…^Mx=B
其中“^”表示异或(XOR, exclusive or),M表示第i个式子中x的系数,是1或者0。B是第i个方程右端的常数,是1或者0。
解这种方程可以套用高斯消元法,只须将原来的加减操作替换成异或操作就可以了,两个方程的左边异或之后,它们的公共项就没有了。
具体的操作方法是这样的:对于k=0..N-1,找到一个M不为0的行i,把它与第k行交换,用第k行去异或下面所有M不为0的行i,消去它们的第k个系数,这样就将原矩阵化成了上三角矩阵;最后一行只有一个未知数,这个未知数就已经求出来了,用它跟上面所有含有这个未知数的方程异或,就小觑了所有的着个未知数,此时倒数第二行也只有一个未知数,它就被求出来了,用这样的方法可以自下而上求出所有未知数。
引用了一段话,解方程组的核心就是高斯消元,这里我给出还原代码
```c
unsigned char box = {};
unsigned char a;
unsigned char ans = {0x47,0x53,0x4c,0x61,0x62,0x31,0x37,0,0,0,0,0,0x9B,0x1D,0,0};
void init()
{
int i,j,k;
for(i=0;i<0x80;i++)
{
for(j=0;j<0x10;j++)
{
for(k=7;k>=0;k--)
a = (box >> k) & 1;
}
}
for(j=0;j<0x10;j++)
{
for(k=7;k>=0;k--)
a = (ans >> k) & 1;
}
}
void gauss(int n)
{
for(int i=1; i<=n; i++)
{
int j=i;
while(j<=n&&!a)
j++;
if(j>n)
continue;
if(i!=j)
for(int k=1; k<=n+1; k++)
swap(a,a);
for(int j=1; j<=n; j++)
if(i!=j&&a)
for(int k=1; k<=n+1; k++)
a^=a;
}
}
int main()
{
int i,j=0;
unsigned char out = {0,};
init();
gauss(0x80);
for(i=0;i<0x10;i++)
{
for(j=1;j<=8;j++)
{
out |= a << (8-j);
}
}
}
```
之后比较的为`gslab17`和`getpid()`得到的值,那么在编写so的时候只需要获取pid拼接即可。
####Check2
第二段是一个对so的crc校验,随便找一个能修改的位置,遍历一个DWORD值,爆破就能得到正确的CRC32校验的值(赛后看了看雪的Writeup用的是CRC32碰撞,[链接](http://bbs.pediy.com/thread-8699.htm))。附上我的代码:
```py
import zlib
import struct
f = open('libZapus.so','rb')
a = f.read()[:-4]
i=0
while i <= 0xFFFFFFFF:
if zlib.crc32(a+struct.pack("<I",i)) ==0x614C5347:
print i
#239832435
i+=1
f.close()
'''
i = 239832435
f = open('libZapus1.so','wb')
f.write(a+struct.pack("<I",i))
f.close()
'''
```
-------------------
### Round2#2
此题是一个修改过的mono引擎,通过一些字符串很明显的能看出来是一个mono引擎。
首先前面解密了内存中的dll,在这里我们可以把这段内存dump下来。发现是.net编写
[!(http://www.get1t.cn/content/images/2017/07/EAQXX-3-ZT-K-3P-N571CSN.png)](http://www.get1t.cn/content/images/2017/07/EAQXX-3-ZT-K-3P-N571CSN.png)
dump下来发现并不是有效的PE文件,那么肯定有所变形,需要我们来修复
[这里](http://gslab.qq.com/article-273-1.html)有一篇文章是来定位需要修复的位置,我是直接在CFF中查看每个属性对比正确的.net查找异常点,[这里](http://www.cnblogs.com/seely/p/4186864.html)有一篇文章是介绍.net文件的PE结构
一共有如下位置被修改过:
1. PE标志位 45 50 00 01 -> 45 50 00 00
2. 元数据表头 NumberOfStream 05 01 -> 05 00
3. 元数据表流中的记录项(这里我找了挺久的,根据ILSpy中的异常,该表是记录的个数,明显发现有一些数据特别大,高位置零)
修复好了之后我们就能看到加密的算法了
[!(http://www.get1t.cn/content/images/2017/07/X-CH24-UMHVK5SJ--ZLHCX3.png)](http://www.get1t.cn/content/images/2017/07/X-CH24-UMHVK5SJ--ZLHCX3.png)
这里Code方法其实就是xtea算法(通过算法中的黄金比例可以看出来),那么接下来的工作就是解密代码了,附上解密代码
```c
#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<stdlib.h>
//xtea encode
void encipher(uint32_t v, uint32_t const key) {
unsigned int i;
int num_rounds = 32;
uint32_t v0 = v, v1 = v, sum = 0, delta = 0x9E3779B9;
for (i = 0; i < num_rounds; i++) {
v0 += (v1 << 4 ^ (v1 >> 5) + v1 ^ sum + key);
sum += delta;
v1 += (v0 << 4 ^ (v0 >> 5) + v0 ^ sum + key[(sum >> 11) & 3]);
}
v = v0; v = v1;
}
//xtea decode
void decipher(uint32_t v, uint32_t const key) {
unsigned int i;
int num_rounds = 32;
uint32_t v0 = v, v1 = v, delta = 0x9E3779B9, sum = delta*num_rounds;
for (i = 0; i < num_rounds; i++) {
v1 -= (v0 << 4 ^ (v0 >> 5) + v0 ^ sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (v1 << 4 ^ (v1 >> 5) + v1 ^ sum + key);
}
v = v0; v = v1;
}
int main()
{
FILE *pFile;
int len;
int* pBuf;
char* pDeBuf;
int index = 0;
int i = 0, num = 0;
unsigned int key = {
363609949u,
512121596u,
1703126449u,
1423373290u
};
unsigned int v = { 0, };
unsigned int p = { 0, };
pFile = fopen("data.encrypted", "rb");
fseek(pFile, 0, SEEK_END);
len = ftell(pFile);
pBuf = (int*)malloc(len);
pDeBuf = (char*)malloc(len);
memset(pBuf, 0, len);
memset(pDeBuf, 0, len);
rewind(pFile);
fread(pBuf, 1, len, pFile);
fclose(pFile);
p = v = pBuf;
p = v = pBuf;
decipher(v, key);
v ^= key;
v ^= key;
num = ((char*)v);
for (i = num; i < 7; i++)
pDeBuf = ((char*)v);
// 06 ce cd cc cb ca c9 89
for (i = 8; i < len; i += 8)
{
v = pBuf;
v = pBuf;
decipher(v, key);
v ^= p;
v ^= p;
memcpy(&pDeBuf, v, 8);
p = pBuf;
p = pBuf;
index += 8;
}
printf("%d", index);
pFile = fopen("data.decrypted", "wb");
fwrite(pDeBuf, 1, index, pFile);
fclose(pFile);
return 0;
}
```
得到图片,图片并不是我们正确的结果,在PNG图片中,只会含有一个sRGB图像块,Dump下来的图片有两个,切出多余的数据,修改头为BM,即为正确结果 round2#1学生组的其实不需要爆破(没试过社会组的),手算一下就能算出来,我还花了点时间把手算的思路写成了代码。。。。
round2#2要过两处反调试才能dump,我做的时候明明过了,但调试时提示 mscorlib.dll找不到,很奇怪,明明不调试能运行通的。最后还是直接静态分析出了dll,dll不止你发的那一处修改,还有一处
幸好我是学c++的还能看懂点 楼主脱360壳儿那篇文章写的很好,很想学习,但不懂反编译... tanghengvip 发表于 2017-7-26 10:13
楼主脱360壳儿那篇文章写的很好,很想学习,但不懂反编译...
可以看看看雪上面利用dex2oat脱壳 小白表示完全看不懂啊 一点都看不懂。 学会数理化,走遍天下全不怕。这句话还是有道理的 厉害了! 支持一下,很不错的资源分析,感谢楼主的热心分享~~ 第二轮第二题得到的图片上面没有对应文字
png图片第二个sRGB数据块很可疑 但是没能分析出更多信息