欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]CTF2017 第十二题 kxuectf-apk 解题报告

[原创]CTF2017 第十二题 kxuectf-apk 解题报告

CTF2017 第十二题 kxuectf-apk

dex 部分

放入 JEB,截两段重要源码

this.button.setOnClickListener(new View$OnClickListener() {

            public void onClick(View v) {

			    // 点击事件,组装 ab.a(下面的算法)、ba.a(一个替换算法)、ca.a(base64)

				// 传入 jni 的 check 函数

                String v2 = uv.this.editText.getText().toString().trim();

                if(ua.check(ab.a(v2) + ba.a(v2) + ca.a(v2)) == 1) {

                    bc.showresult(uv.this.tv, true);

                }

                else {

                    bc.showresult(uv.this.tv, false);

                }

            }

        });

// 上面的 ab.a 算法

public static String encrypt(String input) {

        String v3 = "";

        int v2;

        for(v2 = 0; v2 < input.length(); ++v2) {

            int v0 = input.charAt(v2);

            if(v0 < 97 || v0 > 109) {

                if(v0 >= 65 && v0 <= 77) {

                    v3 = String.valueOf(v3) + (((char)(v0 + 13)));

                    goto label_21;

                }

                if(v0 >= 110 && v0 <= 122) {

                    v3 = String.valueOf(v3) + (((char)(v0 - 13)));

                    goto label_21;

                }

                if(v0 >= 78 && v0 <= 90) {

                    v3 = String.valueOf(v3) + (((char)(v0 - 13)));

                    goto label_21;

                }

                if(v0 >= 48 && v0 <= 57) {

                    v3 = String.valueOf(v3) + (((char)(v0 ^ 7)));

                    goto label_21;

                }

                v3 = String.valueOf(v3) + (((char)(v0 ^ 6)));

            }

            else {

                v3 = String.valueOf(v3) + (((char)(v0 + 13)));

            }

        label_21:

        }

        return v3;

    }

这个 ab.a 算法其实有多解,其实本题最后应该有4个解

K^ -> X
k~ -> x
@S -> F
M\ -> Z
`s -> f
m| -> z
L_ -> Y

附 python 编解码

from collections import defaultdict

def aba_(d):

    if 65 <= d <= 77:

        return chr(d + 13)

    elif 110 <= d <= 122:

        return chr(d - 13)

    elif 78 <= d <= 90:

        return chr(d - 13)

    elif 48 <= d <= 57:

        return chr(d ^ 7)

    elif 97 <= d <= 109:

        return chr(d + 13)

    return chr(d ^ 6)

def aba_enc(s):

    r = ""

    for c in s:

        r += aba_(ord(c))

    return r

def aba_dec(s):

    dt = defaultdict(str)

    for i in xrange(0x20, 0x7F):

        dt[aba_(i)] += chr(i)

    r = ""

    for c in s:

        t = dt[c]

        if len(t) == 1:

            r += t

        else:

            r += '[' + t + ']'

    return r

jni 部分

jni 有3个平台可供选择分析,我选择了 IDA 相容性较好的 armeabi-v7a 版本

反反调试

jni 部分有数种反调试手段,但是都可以用比较原始的方法去掉

  • 可以注入一个 so,修改相关函数的返回
  • 我是直接使用 keypatch 修改 so,替换原有 apk 安装

主程序逻辑

  1. 截取 dex 部分输入的组合字符前36字节
  2. 关于输入长度,程序没有并没有说明,只有大于120会失败,其实应该改成不等于120失败,就可以不用提示也能做出来,因为没有限定长度,只能靠猜测36字节就是 aba 加密结果才能作出该题,因为只有 aba 是36字节时,整体组合字符才会是 120
  3. 在36字节最后 padding 4,4,4,4,组成40字节,使用 FDB4685408CD564E 作为密钥,用变种的 DES 算法加密后,得到 desres(40字节)
  4. 将 654832EFBACD564E0F9B1D27 组合 desres[32:36] 成16字节密钥,用 RC6 算法加密 desres[:32] 得到 rc6res(32字节) 与程序内置的结果进行对比

解法步骤

需要使用调试来判断2个变种加密算法的修改细节,本文最后一部分会附上 diff,因为题目进行中公开了用户输入的前8位 kxuectf{ 和末位 },本文将在给出提示的前提下提供解法步骤

  1. aba_enc("kxuectf{") 得到 786b68727067737d
  2. DES 加密后得到 4cd9a3e6edfed105
  3. 枚举 desres[32:36] 部分,组合RC6密钥,解密 内置结果[:16],得到 desres[:16]
  4. 对比 desres[:8] 与 4cd9a3e6edfed105,相同则找到正确的 desres[32:36] 136a7e1f,以及 desres[:16]
  5. RC6解密 内置结果[16:] 解出剩下的 desres[16:32],DES 解密 desres[:32] 解出 abares[:32]
  6. 在可见字符范围内(aba输出可见字符),枚举 abares[32:36],padding 上[4,4,4,4]组合成 8 字节,用变种 DES 加密
  7. 对比 加密结果[:4] 与 136a7e1f,相同找到正确的 abares[32:36] 613e367b,组合成 abares(36字节)
  8. aba_dec(abares) 得到正确解,应该有4组,选了组没有奇怪标点的提交

算法源码

DES

set_key 位移部分不知道有bug还是啥,和标准不大一样,原型代码

--- Des.cpp	2017-06-22 21:29:47.000000000 +0800

+++ Des2.cpp	2017-06-25 12:19:36.000000000 +0800

@@ -6,7 +6,7 @@

 */

 //////////////////////////////////////////////////////////////////////////

 

-#include "memory.h"

+#include <string.h>

 #include "Des.h"

 

 //////////////////////////////////////////////////////////////////////////

@@ -163,7 +163,13 @@

             Xor(Ri, Li, 32);

             memcpy(Li, tmp, 32);

         }

+        memcpy(tmp, M, 32);

+        memcpy(M, M + 32, 32);

+        memcpy(M + 32, tmp, 32);

     }else{

+        memcpy(tmp, M, 32);

+        memcpy(M, M + 32, 32);

+        memcpy(M + 32, tmp, 32);

         for(int i=15; i>=0; --i) {

             memcpy(tmp, Li, 32);

             F_func(Li, (*pSubKey)[i]);

@@ -179,10 +185,23 @@

     static bool K[64], *KL=&K[0], *KR=&K[28];

     ByteToBit(K, Key, 64);

     Transform(K, K, PC1_Table, 56);

+    static bool KK[120], *K1=&KK[0], *K2=&KK[60];

+    memcpy(K1, K, 28); memcpy(K1 + 28, K, 28);

+    memcpy(K2, K + 28, 28); memcpy(K2 + 28, K + 28, 28);

+

+    int loop = 0;

     for(int i=0; i<16; ++i) {

-        RotateL(KL, 28, LOOP_Table[i]);

-        RotateL(KR, 28, LOOP_Table[i]);

-        Transform((*pSubKey)[i], K, PC2_Table, 48);

+        loop += LOOP_Table[i];

+        for (int j=0; j<48; ++j) {

+            int k = PC2_Table[j];

+            bool r;

+            if (k < 28) {

+                r = K1[loop + k - 1];

+            } else {

+                r = K1[32 + loop + k - 1];

+            }

+            SubKey[0][i][j] = r;

+        }

     }

 }

 void F_func(bool In[32], const bool Ki[48])

@@ -220,14 +239,18 @@

 }

 void ByteToBit(bool *Out, const char *In, int bits)

 {

+    int t = bits - 1;

+    if (t > 7) t = 7;

     for(int i=0; i<bits; ++i)

-        Out[i] = (In[i>>3]>>(i&7)) & 1;

+        Out[i] = (In[i>>3]>>(t-i%8))&1;

 }

 void BitToByte(char *Out, const bool *In, int bits)

 {

+    int t = bits - 1;

+    if (t > 7) t = 7;

     memset(Out, 0, bits>>3);

     for(int i=0; i<bits; ++i)

-        Out[i>>3] |= In[i]<<(i&7);

+        Out[i>>3] |= In[i]<<(t-i%8);

 }

 //////////////////////////////////////////////////////////////////////////

 // Code ends at Line 231

RC6

RC6 部分赛后 poyoten 发现应该是这个实现,我为了用 go 快速枚举,我是在 go-rc6 这个实现上修改的

// Package rc6 implements the RC6 cipher

/*

For more information, please see:

    https://en.wikipedia.org/wiki/RC6

    http://www.emc.com/emc-plus/rsa-labs/historical/rc6-block-cipher.htm

*/

package rc6

import (

	"crypto/cipher"

	"strconv"

)

const (

	rounds    = 20

	roundKeys = 2*rounds + 4

	keyWords  = 4

)

type rc6cipher struct {

	rk [roundKeys]uint32

	L  [4]uint32

}

func rotl32(k uint32, rot uint32) uint32 {

	return (k << rot) | (k >> (32 - rot))

}

func rotr32(k uint32, rot uint32) uint32 {

	return (k >> rot) | (k << (32 - rot))

}

type KeySizeError int

func (k KeySizeError) Error() string { return "rc6: invalid key size " + strconv.Itoa(int(k)) }

// New returns a cipher.Block implementing RC6.  The key argument must be 16 bytes.

func New(key []byte) (cipher.Block, error) {

	if l := len(key); l != 16 {

		return nil, KeySizeError(l)

	}

	c := &rc6cipher{}

	for i := 0; i < keyWords; i++ {

		c.L[i] = getUint32(key)

		key = key[4:]

	}

	return c, nil

}

func (c *rc6cipher) setrk() {

	copy(c.rk[:], skeytable)

	var A uint32

	var B uint32

	var i, j int

	for k := 0; k < 3*roundKeys; k++ {

		c.rk[i] = rotl32(c.rk[i]+(A+B), 3)

		A = c.rk[i]

		c.L[j] = rotl32(c.L[j]+(A+B), (A+B)&31)

		B = c.L[j]

		i = (i + 1) % roundKeys

		j = (j + 1) % keyWords

	}

}

func (c *rc6cipher) BlockSize() int { return 16 }

func (c *rc6cipher) Encrypt(dst, src []byte) {

	A := getUint32X(src)

	B := getUint32X(src[4:])

	C := getUint32X(src[8:])

	D := getUint32X(src[12:])

	c.setrk()

	B = B + c.rk[0]

	D = D + c.rk[1]

	for i := 1; i <= rounds; i++ {

		t := rotl32(B*(2*B+1), 5)

		u := rotl32(D*(2*D+1), 5)

		A = rotl32((A^t), u&31) + c.rk[2*i]

		C = rotl32((C^u), t&31) + c.rk[2*i+1]

		A, B, C, D = B, C, D, A

	}

	A = A + c.rk[2*rounds+2]

	C = C + c.rk[2*rounds+3]

	putUint32X(dst, A)

	putUint32X(dst[4:], B)

	putUint32X(dst[8:], C)

	putUint32X(dst[12:], D)

}

func (c *rc6cipher) Decrypt(dst, src []byte) {

	A := getUint32X(src)

	B := getUint32X(src[4:])

	C := getUint32X(src[8:])

	D := getUint32X(src[12:])

	c.setrk()

	C = C - c.rk[2*rounds+3]

	A = A - c.rk[2*rounds+2]

	for i := rounds; i >= 1; i-- {

		A, B, C, D = D, A, B, C

		u := rotl32(D*(2*D+1), 5)

		t := rotl32(B*(2*B+1), 5)

		C = rotr32((C-c.rk[2*i+1]), t&31) ^ u

		A = rotr32((A-c.rk[2*i]), u&31) ^ t

	}

	D = D - c.rk[1]

	B = B - c.rk[0]

	putUint32X(dst, A)

	putUint32X(dst[4:], B)

	putUint32X(dst[8:], C)

	putUint32X(dst[12:], D)

}

// avoid pulling in encoding/binary

func getUint32(b []byte) uint32 {

	return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24

}

func getUint32X(b []byte) uint32 {

	return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24

}

func putUint32(b []byte, v uint32) {

	b[0] = byte(v)

	b[1] = byte(v >> 8)

	b[2] = byte(v >> 16)

	b[3] = byte(v >> 24)

}

func putUint32X(b []byte, v uint32) {

	b[0] = byte(v >> 24)

	b[1] = byte(v >> 16)

	b[2] = byte(v >> 8)

	b[3] = byte(v)

}

// skeytable computed from

/*

Pw = uint32(0xb7e15163)

Qw = uint32(0x9e3779b9)

    T = 2*(R+2);

    S[0] = Pw;

    for (i = 1 ; i < T ; i++)  {

        S[i] = S[i-1] + Qw;

    }

*/

var skeytable = []uint32{

	0xb7e15163, 0x19a9d7aa, 0x7b725df1, 0xdd3ae438, 0x3f036a7f, 0xa0cbf0c6, 0x294770d, 0x645cfd54,

	0xc625839b, 0x27ee09e2, 0x89b69029, 0xeb7f1670, 0x4d479cb7, 0xaf1022fe, 0x10d8a945, 0x72a12f8c,

	0xd469b5d3, 0x36323c1a, 0x97fac261, 0xf9c348a8, 0x5b8bceef, 0xbd545536, 0x1f1cdb7d, 0x80e561c4,

	0xe2ade80b, 0x44766e52, 0xa63ef499, 0x8077ae0, 0x69d00127, 0xcb98876e, 0x2d610db5, 0x8f2993fc,

	0xf0f21a43, 0x52baa08a, 0xb48326d1, 0x164bad18, 0x7814335f, 0xd9dcb9a6, 0x3ba53fed, 0x9d6dc634,

	0xff364c7b, 0x60fed2c2, 0xc2c75909, 0x248fdf50,

}


注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功