# 2017 SSCTF Login 200

Java层上了一堆中文变量

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-ddbcd9eaaf5a60b21d3d47a3194208fb16ebaa88%2F1019417FCF9B019A1DECC419EECC2F92.png?alt=media)

在一顿修复之后，大概的能梳理出校验过程

校验的地方

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-468a72e8041274d963297a75ec0664dd39a5e9ee%2F62F60092EE0C81454FF20A55DCA4195C.png?alt=media)

一层一层分析，这是一个native函数，需要使用IDA分析

```
JniTest.getValue(strInput.getBytes("utf-8"))
```

使用IDA载入对应的so文件，有一个关键的`encode()`函数

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-ffdb14fae06473897f51259d1860d5de9799ac7f%2FA61E0D433FE59E7FC88581E728FD60CA.png?alt=media)

跟入发现白茫茫一片

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-2cbd03c4bc4f5ff4ec1c83ca951d764219167e06%2F9876CC278F2DBB3AAD2D4BEE6BCFB69D.png?alt=media)

使用流程图查看，发现调用关系断开

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-bc52ff0656a7d64e1f32f102424c2517c94c0b8a%2F38DE0DE170EB153E42ABBCCCED27A26D.png?alt=media)

先去掉下面这个块的函数尾

```
.text:000025CC ; START OF FUNCTION CHUNK FOR encode
.text:000025CC CODE16
.text:000025CC
.text:000025CC loc_25CC
.text:000025CC BX      PC
.text:000025CC ; END OF FUNCTION CHUNK FOR 
```

然后`Force BL Call`

即可修复，F5之后的代码也比较正常可读

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-44aad4a3d950b70dbe23528123f116e076d7e9c7%2F0E6AFE4B8CA8B6BD244E34EBEC3891FD.png?alt=media)

伪代码

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-d4ac16f5358dfdd434fc87844461e66c1ee6898f%2F09FCF618B7CCFD8DA42A951E9D147B44.png?alt=media)

入口处看到了长度由12变为16位，也就是三位扩充成4位，猜测是Base64算法

```c
if ( v4 )
    dwDivideJudge = j_j___divsi3(_lenOfbaRegCode, 3) + 1;
else
    dwDivideJudge = j_j___divsi3(_lenOfbaRegCode, 3);// Java层注册码要求12位
lpStringBuffer = j_j_malloc(4 * dwDivideJudge);// 长度由12位扩充为16位
```

找到一个转换表

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-06c2ee688fa6a3f7a8b7339ec26ba426ad7d7a3f%2F06F5CF24544521871B10D811F3AFA941.png?alt=media)

使用WinHex找到对应偏移，C Source模式拷贝出来

```c
unsigned char data[66] = {
    0x21, 0x3A, 0x23, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2B, 0x2D, 0x2A, 0x2F, 0x60, 0x7E, 0x5F, 0x5B, 
    0x5D, 0x7B, 0x7D, 0x3F, 0x3C, 0x3E, 0x2C, 0x2E, 0x40, 0x5E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 
    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 
    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x5C, 0x27, 
    0x3B, 0x00
};
```

IDA里没显示完全，其实应该是下面这样的

```
!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\'; 
```

正常的Base64编码表如下

```
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
```

所以在这里写还原代码的时候不能使用下面这种常规编码表的方式

```c
static char Base64Table[256] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0   - 15
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16  - 31
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32  - 47
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48  - 63
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, // 64  - 79
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80  - 95
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96  - 111
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112 - 127
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 - 143
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144 - 159
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160 - 175
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176 - 191
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192 - 207
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208 - 223
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224 - 239
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 240 - 255
};
```

而是根据对应的char来查找index，然后拼接到一个`int`型数据里去，由于编码表是变形的，所以只能使用循环来查找

这里我放一份比较常规的Base64编码的C语言实现，可以跟反编译后的代码进行对比

```
const char Base64Code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void Base64_encode(char *lpString)
{
    char *lpBuffer;
    int lpString_len = strlen(lpString);
    while(lpString_len > 0)
    {
        *lpBuffer++ = Base64Code[(lpString[0] >> 2 ) & 0x3F];
        if(lpString_len > 2)
        {
            *lpBuffer++ = Base64Code[((lpString[0] & 3) << 4) | (lpString[1] >> 4)];
            *lpBuffer++ = Base64Code[((lpString[1] & 0xF) << 2) | (lpString[2] >> 6)];
            *lpBuffer++ = Base64Code[lpString[2] & 0x3F];
        }
        else
        {
            switch(lpString_len)
            {
            case 1:
                *lpBuffer ++ = Base64Code[(lpString[0] & 3) << 4 ];
                *lpBuffer ++ = '=';
                *lpBuffer ++ = '=';
                break;
            case 2:
                *lpBuffer ++ = Base64Code[((lpString[0] & 3) << 4) | (lpString[1] >> 4)];
                *lpBuffer ++ = Base64Code[((lpString[1] & 0x0F) << 2) | (lpString[2] >> 6)];
                *lpBuffer ++ = '=';
                break;
            }
        }
        lpString += 3;
        lpString_len -= 3;
    }
    *lpBuffer = '\0';
}
```

Base64编码基于的一个原理就是6位`01`即可表示64个不同的字符在取第一个字符的时候，只需要取前6位，转为index，找到编码表对应的字符即可，但是这里的算法还进行了一次异或，所以最终的编码在计算的时候，需要把每4个字节的第一个字节跟`0x3F`进行异或，再去找对应的字符下标index

```
 *lpStringBuffer_chBy4b = a__Abcdefghijkl[temp >> 2] ^ 0x3F;
```

第二字节同样做了异或操作

```
lpStringBuffer_chBy4b[1] = *(&a__Abcdefghijkl[16 * temp & 0x3F] + (j >> 4)) ^ 0xF;
```

第三和第四字节常规的编码，没有其余操作

结合输入的注册码为12位，那么我们还原的代码如下，对于后面的判断我就省略了

```
public class MyClass {
	
	static byte[] dataEncoded = {0x01, 0x63, 0x5e, 0x6c, 0x5f, 0x23, 0x78, 0x25, 0x5f, 0x27, 0x35, 0x6c, 0x11, 0x66, 0x31, 0x65};
	
	static byte[] Base64Table = {
		    0x21, 0x3A, 0x23, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2B, 0x2D, 0x2A, 0x2F, 0x60, 0x7E, 0x5F, 0x5B, 
		    0x5D, 0x7B, 0x7D, 0x3F, 0x3C, 0x3E, 0x2C, 0x2E, 0x40, 0x5E, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 
		    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 
		    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x5C, 0x27, 
		    0x3B, 0x00
		};
	
	private static int GetCharIndex(byte ch) {
		for(int i = 0; i < Base64Table.length; i++) {
			if (ch == Base64Table[i]) {
				return i;
			}
		}
		return -1;
	}
	
	public static void main(String[] args) throws Exception {
		int len = dataEncoded.length * 3 / 4;
		byte[] dataDecoded = new byte[len];
		int j = 0;
		for(int i = 0; i < dataEncoded.length; i += 4) {
			dataDecoded[j++] = (byte)((GetCharIndex((byte)(dataEncoded[i] ^ 0x3F)) << 2) + ((GetCharIndex((byte)(dataEncoded[i + 1] ^ 0x0F)) >> 4) & 0x03));
			dataDecoded[j++] = (byte)(((GetCharIndex((byte)(dataEncoded[i + 1] ^ 0x0F)) & 0x0F) << 4) + ((GetCharIndex(dataEncoded[i + 2]) >> 2) & 0x0F));
			dataDecoded[j++] = (byte)((GetCharIndex(dataEncoded[i + 2]) << 6) + (GetCharIndex(dataEncoded[i + 3])));
		}
		System.out.println(new String(dataDecoded));
	}
}
```

因为上一题的套路，我猜后面那个32位字符串就是应该用来解密的数据，所以直接拿来测试

输出

```
VVe1lD0ne^-^
```

为了确定整个流程，再次回到Java层进行流程的分析

```
DecryptUtils.checkRegCode(DecryptUtils.extensionTo32(JniTest.getValue(strInput.getBytes("utf-8"))), "01635e6c5f2378255f27356c11663165")
```

首先是`DecryptUtils.extensionTo32()`，用于将`byte[]`转为32位字符串

```
public static String extensionTo32(byte[] Data) {
    StringBuilder stringBuilder = new StringBuilder();
    int lenOfData = Data.length;
    int i;
    for(i = 0; i < lenOfData; ++i) {
        stringBuilder.append(String.format("%02x", Integer.valueOf(Data[i] & 255)));
    }

    return stringBuilder.toString();
}
```

最终的`DecryptUtils.checkRegCode()`很有意思，一不小心就会被带到坑里去

```
public static boolean checkRegCode(String strCalcu, String strJUDGE) {
    boolean boolResult = false;
    try {
        String v4 = DecryptUtils.CTF1(DecryptUtils.Encrypt_MD5(strCalcu), strCalcu);
        String v5 = DecryptUtils.CTF1(DecryptUtils.Encrypt_MD5(strJUDGE), strJUDGE);
        if(v4.length() < v5.length()) {
            return boolResult;
        }

        int i = 0;
        int j = 0;
        while(i < v4.length()) {
            int temp = (v5.charAt(i) ^ v4.charAt(i)) + i ^ i;
            j += temp;
            while(temp > 0) {
                j += v5.charAt(temp) + v4.charAt(temp);
                --temp;
            }

            ++i;
        }

        boolResult = DecryptUtils.Encrypt_MD5(String.valueOf(j)).equals("cfcd208495d565ef66e7dff9f98764da");
    }
    catch(Exception v1) {
        v1.printStackTrace();
    }

    return boolResult;
}
```

如果反推，马上就可以得到答案，如果正向走下来

我们正向走一遍，首先是`CTF1()`，将每个字符拆成2个4位，然后再挑数据拼接

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-73d1b82461b8e4eab66d047b8c98bf059a069dd5%2FF1A9F28160078AC9A0C8407E6661DE2D.png?alt=media)

接下来进行对比计算

```c
int i = 0;
int j = 0;
while(i < v4.length()) {
    int temp = (v5.charAt(i) ^ v4.charAt(i)) + i ^ i;
    j += temp;
    while(temp > 0) {
        j += v5.charAt(temp) + v4.charAt(temp);
        --temp;
    }

    ++i;
}
boolResult = DecryptUtils.Encrypt_MD5(String.valueOf(j)).equals("cfcd208495d565ef66e7dff9f98764da");
```

计算完后有一个MD5值的对比，尝试解密这个MD5

```
您查询的字符串是“cfcd208495d565ef66e7dff9f98764da”,解密的结果为“0”!
```

那么说明计算完`j`是0

回头看计算过程，`j`一直是相加的计算，想让`j`是0，那么在异或的时候两个值一定要相等，说明两个字符串相等，回溯到最前面，说明传进来的两个字符串相等，那么直接拿上面的32位字符串去解密，就可以得到正确的输入数据，输入那个数据，就可以弹出flag

输入`VVe1lD0ne^-^`，弹出flag

![](https://2646860265-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJh2iv6UDGq4O16NDjFK8%2Fuploads%2Fgit-blob-dd0841f277618198393ca4207cfebae4ccbe8bbf%2FAF0853C2410C0B13F6F063867C32CC77.jpg?alt=media)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wnagzihxa1n.gitbook.io/happy-android-security/capture_the_flag/2017_ssctf_login.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
