欢迎来到 嗅灵易学

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

注册机挑战游戏(A Keygen Challenge for Android)第一第二第三关攻略

注册机挑战游戏(A Keygen Challenge for Android)第一第二第三关攻略

今天找到肖大前段时间推荐的一个链接,关于Android的一个apk,详见:


A Keygen Challenge for Android
http://www.kanxue.com/bbs/showthread.php?t=185648

目前就玩到了第三关,特发此贴记录一下过程,有兴趣的一起玩起~
●
[*]Beginner
[*]Easy
[*]Hard

首先先对界面做一个展示吧,该软件分为5个等级,每个等级适用了不同的算法,当然难度随着等级提高也会提高.前两个算法还是挺简单的,各大牛可以忽略~

1.Beginner
第一关较为简单,采用VTS直接逆向并查看java源码可以找到对应的算法:

string1,string2分别表示输入的用户名还有序列号
根据这段源码,我们很快就确定了算法1的计算流程。首先根据输入的用户名,取用户名的每一位进行一些操作,之后 int k = Integer.parseInt(paramString2),意味着序列号是数字,最后判断是否通过验证就是判断k与i(用户名运算的值)是否相等。
那么注册机也就很简单了,直接复制出来,输入用户名把i输出就是序列号了
import java.io.IOException;
import java.util.Scanner;
//xbalien 78388
public class Challenge1Verifier {
	public static void main(String[] args) throws IOException{
		Scanner in = new Scanner(System.in);
		String userName = in.next();
		key(userName);
	}
	public static void key(String paramString1){
	    int i = 0;
	    for (int j = 0; j < paramString1.length(); j++){
	      int m = paramString1.charAt(j);
	      i = m ^ i + m * m;
	    }
	    System.out.println(i);
	}
}

2.Easy
第二关需要结合smali语法,才能解决,光看源码会有一些小细节不好处理~

算法很容易看懂,首先用户名要大于4位,将用户名转为大写字母后,取其中每一位进行运算,生成一个中间变量str2,之后对str2中的每一个字符进行运算,算出一个结果j。
而对于输入的序列,也参与了运算并重j中不断的减去其计算值,最后结果当j变为0的时候判别为输入正确。
当然,由于反编译结果,出现了一个不可见的字符,因此需要通过smali确定两者分别相加的到底是什么值。一个为0x30一个为0x40,两者相差16,因此输入的序列如果与str2长度相同的话只需要每一位比str2都大0x10,就可以找到正确序列,因此算法也就很简单了:
import java.io.IOException;
import java.util.Scanner;
//xbalien AIECIE
public class Challenge2Verifier {
	public static void main(String[] args) throws IOException{
		Scanner in = new Scanner(System.in);
		String userName = in.next();
		key(userName);
	}
	public static void key(String paramString1)
	{
		if (paramString1.length() >= 4){
			String str1 = paramString1.toUpperCase();
			long l = 0L;
			for (int i = 0; i < str1.length(); i++)
				l = 3L * (l + str1.charAt(i)) - 64L;
			String str2 = Long.toString(l);
			System.out.println(str2);
			byte keyByte[] = new byte[str2.length()];
			for (int k = 0; k < str2.length(); k++){
				keyByte[k] = (byte) (str2.charAt(k) + 16);
			}
			System.out.println(new String(keyByte));
		}
	}
}

3.Hard
这一题开始相对就有一些复杂了,因为编译为源码后只能看到一部分,因此其他部分就是分析smali了。

能看到就是public static String bytesToHex(byte[] paramArrayOfByte)函数,以及一些内置的关键值
byte[] secretBytes = this.secretKey.getBytes(Charset.forName("US-ASCII"));
String secretKey = "KeygenChallengeNumber3";
那么关键就是分析public boolean isValid(String paramString1, String paramString2)了,暂时没有找到更好的工具查看跳转之类的,于是使用了IDA作为静态分析工具,因此载入后进入isValid函数,进行分析:
CODE:00002880                 Method 68 (0x44):
CODE:00002880                 public boolean
CODE:00002880                 com.me.keygen.verifiers.challenge.Challenge3Verifier.isValid(
CODE:00002880                   java.lang.String p0,
CODE:00002880                   java.lang.String p1)
CODE:00002880 this = v12
CODE:00002880 p0 = v13
CODE:00002880 p1 = v14
CODE:00002880                 const/16                        v11, 0x30
CODE:00002884                 const/4                         v8, 0
CODE:00002886                 const-string                    v9, asc_3BC2 # "-"
CODE:0000288A                 invoke-virtual                  {p1, v9}, <ref String.split(ref) imp. @ _def_String_split@LL> # ;输入的序列号需要有-作为分隔符
CODE:00002890                 move-result-object              v5 # ;分割后的每一段存放在v5
CODE:00002892                 array-length                    v9, v5
CODE:00002894                 const/16                        v10, 8
CODE:00002898                 if-eq                           v9, v10, loc_289E # ;前提必须有8段,7个-的输入序列才能通过
CODE:0000289C
CODE:0000289C locret:                                 # CODE XREF: Challenge3Verifier_isValid@ZLL+36j
CODE:0000289C                                         # Challenge3Verifier_isValid@ZLL+F0j ...
CODE:0000289C                 return                          v8
CODE:0000289E # ---------------------------------------------------------------------------
CODE:0000289E
CODE:0000289E loc_289E:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+18j
CODE:0000289E                 const/4                         v7, 0
CODE:000028A0
CODE:000028A0 loc_28A0:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+3Ej
CODE:000028A0                 array-length                    v9, v5
CODE:000028A2                 if-ge                           v7, v9, loc_28C0
CODE:000028A6                 aget-object                     v9, v5, v7
CODE:000028AA                 const-string                    v10, a09aF09aF09aF09 # "[0-9A-F][0-9A-F][0-9A-F][0-9A-F]"
CODE:000028AE                 invoke-virtual                  {v9, v10}, <boolean String.matches(ref) imp. @ _def_String_matches@ZL> # ;匹配正则表达式(每一段为4个,且要满足)
CODE:000028B4                 move-result                     v9
CODE:000028B6                 if-eqz                          v9, locret
CODE:000028BA                 add-int/lit8                    v7, v7, 1
CODE:000028BE                 goto                            loc_28A0
CODE:000028C0 # ---------------------------------------------------------------------------
CODE:000028C0
CODE:000028C0 loc_28C0:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+22j
CODE:000028C0                 new-instance                    v0, <t: ByteArrayOutputStream>
CODE:000028C4                 invoke-direct                   {v0}, <void ByteArrayOutputStream.<init>() imp. @ _def_ByteArrayOutputStream__init_@V> # ;新建字节数组输出流
CODE:000028CA                 const/16                        v9, 0x31
CODE:000028CE                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI> # ;49写入字节数组流中
CODE:000028D4                 const/4                         v7, 0
CODE:000028D6
CODE:000028D6 loc_28D6:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+7Cj
CODE:000028D6                 iget-object                     v9, this, Challenge3Verifier_secretBytes # ;取出关键运算值,secretBytes,进行适当的算法,写入字节数组流
CODE:000028DA                 array-length                    v9, v9
CODE:000028DC                 if-ge                           v7, v9, loc_28FE
CODE:000028E0                 iget-object                     v9, this, Challenge3Verifier_secretBytes
CODE:000028E4                 aget-byte                       v9, v9, v7
CODE:000028E8                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:000028EE                 add-int/lit8                    v9, v7, 1
CODE:000028F2                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:000028F8                 add-int/lit8                    v7, v7, 2
CODE:000028FC                 goto                            loc_28D6 # ;取出关键运算值,secretBytes,进行适当的算法,写入字节数组流
CODE:000028FE # ---------------------------------------------------------------------------
CODE:000028FE
CODE:000028FE loc_28FE:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+5Cj
CODE:000028FE                 const/4                         v7, 1
CODE:00002900
CODE:00002900 loc_2900:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+A6j
CODE:00002900                 iget-object                     v9, this, Challenge3Verifier_secretBytes # ;再次取出关键运算值,secretBytes,进行适当的算法,写入字节数组流
CODE:00002904                 array-length                    v9, v9
CODE:00002906                 if-ge                           v7, v9, loc_2928
CODE:0000290A                 iget-object                     v9, this, Challenge3Verifier_secretBytes
CODE:0000290E                 aget-byte                       v9, v9, v7
CODE:00002912                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:00002918                 add-int/lit8                    v9, v7, 1
CODE:0000291C                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:00002922                 add-int/lit8                    v7, v7, 2
CODE:00002926                 goto                            loc_2900 # ;再次取出关键运算值,secretBytes,进行适当的算法,写入字节数组流
CODE:00002928 # ---------------------------------------------------------------------------
CODE:00002928
CODE:00002928 loc_2928:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+86j
CODE:00002928                 invoke-virtual                  {v0, v11}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:0000292E                 invoke-virtual                  {v0, v11}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI> # ;写入两个48
CODE:00002934                 const/4                         v7, 0
CODE:00002936

前面这一段算法很好理解,首先,用户输入的序列号需要有-,一共需要有八段,也就是七个-存在;之后对于每一段要匹配正则表达式"[0-9A-F][0-9A-F][0-9A-F][0-9A-F]"。也就是说每段需要4位数,且满足0-9A-F;之后取出之前的关键值进行处理之后写入字节数组流里。
那么关键在于字节数组流里面的处理是怎么处理的,为了方便查看,接下来使用动态调试查看,我采用apktool+eclipse,进行动态调试,可以根据寄存器查看结果:


根据这里,我在结合静态分析的内容,大致可以看出,前一部分字节数组流存放的分布为:
49 se[0] 1 se[2] 3 …… se[20] 21 se[1] 2 se[3] 4 …… se[21] 22之后再加入了48 48
接下来又一个字节数组流的处理:
CODE:00002936 loc_2936:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+ECj
CODE:00002936                 array-length                    v9, v5 # ;取出输入序列中的前4段xxxx-xxxx-xxxx-xxxx-写入字节数组流
CODE:00002938                 div-int/lit8                    v9, v9, 2
CODE:0000293C                 if-ge                           v7, v9, loc_2972
CODE:00002940 # try 0x2940-0x2968:
CODE:00002940                 aget-object                     v9, v5, v7
CODE:00002944                 const-string                    v10, aUs_ascii # "US_ASCII"
CODE:00002948                 invoke-static                   {v10}, <ref Charset.forName(ref) imp. @ _def_Charset_forName@LL>
CODE:0000294E                 move-result-object              v10
CODE:00002950                 invoke-virtual                  {v9, v10}, <ref String.getBytes(ref) imp. @ _def_String_getBytes@LL>
CODE:00002956                 move-result-object              v9
CODE:00002958                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(ref) imp. @ _def_ByteArrayOutputStream_write@VL>
CODE:0000295E                 const/16                        v9, 0x2D
CODE:00002962                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(int) imp. @ _def_ByteArrayOutputStream_write@VI>
CODE:00002968                 add-int/lit8                    v7, v7, 1
CODE:0000296C                 goto                            loc_2936 # ;取出输入序列中的前4段xxxx-xxxx-xxxx-xxxx-写入字节数组流
CODE:0000296E # ---------------------------------------------------------------------------
CODE:0000296E # catch Exception:
CODE:0000296E                 move-exception                  v1
CODE:00002970                 goto                            locret
CODE:00002972 # ---------------------------------------------------------------------------
CODE:00002972 # try 0x2972-0x297C:
CODE:00002972
CODE:00002972 loc_2972:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+BCj
CODE:00002972                 iget-object                     v9, this, Challenge3Verifier_secretBytes
CODE:00002976                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(ref) imp. @ _def_ByteArrayOutputStream_write@VL>

存入 xxxx-xxxx-xxxx-xxxx- 对应的字节流 和 se全部字节流
在接下去:
CODE:00002972 loc_2972:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+BCj
CODE:00002972                 iget-object                     v9, this, Challenge3Verifier_secretBytes
CODE:00002976                 invoke-virtual                  {v0, v9}, <void ByteArrayOutputStream.write(ref) imp. @ _def_ByteArrayOutputStream_write@VL> # ;将secretBytes也一同写入字节数组流
CODE:0000297C                 const/16                        v9, 0x20
CODE:00002980                 new-array                       v6, v9, <t: byte[]>
CODE:00002984 # try 0x2984-0x29A4:
CODE:00002984                 const-string                    v9, aMd5 # ;对之前写入字节数组流的bytes进行MD5计算
CODE:00002988                 invoke-static                   {v9}, <ref MessageDigest.getInstance(ref) imp. @ _def_MessageDigest_getInstance@LL>
CODE:0000298E                 move-result-object              v4
CODE:00002990                 invoke-virtual                  {v0}, <ref ByteArrayOutputStream.toByteArray() imp. @ _def_ByteArrayOutputStream_toByteArray@L>
CODE:00002996                 move-result-object              v9
CODE:00002998                 invoke-virtual                  {v4, v9}, <void MessageDigest.update(ref) imp. @ _def_MessageDigest_update@VL>
CODE:0000299E                 invoke-virtual                  {v4}, <ref MessageDigest.digest() imp. @ _def_MessageDigest_digest@L>
CODE:000029A4                 move-result-object              v6
CODE:000029A6                 invoke-static                   {v6}, <ref Challenge3Verifier.bytesToHex(ref) Challenge3Verifier_bytesToHex@LL> # ;对于算好的MD5字节调用自定义的bytesToHex
CODE:000029AC                 move-result-object              v9
CODE:000029AE                 invoke-virtual                  {v9}, <ref String.toUpperCase() imp. @ _def_String_toUpperCase@L> # ;转换为大写
CODE:000029B4                 move-result-object              v2
CODE:000029B6                 invoke-virtual                  {p1}, <int String.length() imp. @ _def_String_length@I>
CODE:000029BC                 move-result                     v9
CODE:000029BE                 div-int/lit8                    v9, v9, 2
CODE:000029C2                 invoke-virtual                  {p1, v9}, <ref String.substring(int) imp. @ _def_String_substring@LI>
CODE:000029C8                 move-result-object              v3
CODE:000029CA                 const-string                    v9, asc_3BC2 # "-"
CODE:000029CE                 const-string                    v10, empty_str
CODE:000029D2                 invoke-virtual                  {v3, v9, v10}, <ref String.replaceAll(ref, ref) imp. @ _def_String_replaceAll@LLL> # ;选取输入的序列后面4段,同时把-去掉,排成一个16字节
CODE:000029D8                 move-result-object              v3
CODE:000029DA                 const/4                         v7, 0

到这一步,我们知道了该算法根据输入序列号的前4段计算了一个值,那么后面就是判断部分了,根据输入序列的后4段,判断是否正确
CODE:000029DC loc_29DC:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+184j
CODE:000029DC                 invoke-virtual                  {v2}, <int String.length() imp. @ _def_String_length@I>
CODE:000029E2                 move-result                     v9
CODE:000029E4                 if-ge                           v7, v9, loc_2A12 # ;进行比较,若都相等,V8为1也就是正确
CODE:000029E8                 invoke-virtual                  {v2, v7}, <char String.charAt(int) imp. @ _def_String_charAt@CI> # ;取经过计算后的,处于偶数位置的字符
CODE:000029EE                 move-result                     v9
CODE:000029F0                 div-int/lit8                    v10, v7, 2
CODE:000029F4                 invoke-virtual                  {v3, v10}, <char String.charAt(int) imp. @ _def_String_charAt@CI> # ;去输入序列后4段去掉-的字符的每一个字符
CODE:000029FA                 move-result                     v10
CODE:000029FC                 if-ne                           v9, v10, locret
CODE:00002A00                 add-int/lit8                    v7, v7, 2
CODE:00002A04                 goto                            loc_29DC
CODE:00002A06 # ---------------------------------------------------------------------------
CODE:00002A06 # catch Exception:
CODE:00002A06                 move-exception                  v1
CODE:00002A08                 goto/16                         locret
CODE:00002A0C # ---------------------------------------------------------------------------
CODE:00002A0C # catch Exception:
CODE:00002A0C                 move-exception                  v1
CODE:00002A0E                 goto/16                         locret
CODE:00002A12 # ---------------------------------------------------------------------------
CODE:00002A12
CODE:00002A12 loc_2A12:                               # CODE XREF: Challenge3Verifier_isValid@ZLL+164j
CODE:00002A12                 const/4                         v8, 1 # ;进行比较,若都相等,V8为1也就是正确
CODE:00002A14                 goto/16                         locret
CODE:00002A14                 Method End

到这一部分,我们基本已经了解这整个算法的流程。其中发现用户名是没有参与运算的。
那么开始写注册算法吧(我就假定了前4段字符为0123-4567-89AB-CDEF-,开始计算):
public class Challenge3Verifier {
	static String preKey = "0123-4567-89AB-CDEF-";
	static byte[] preKeyBytes = preKey.getBytes(Charset.forName("US-ASCII"));
	static String secretKey = "KeygenChallengeNumber3";
	static byte[] secretBytes = secretKey.getBytes(Charset.forName("US-ASCII"));
	public static String bytesToHex(byte[] paramArrayOfByte){
	    char[] arrayOfChar1 = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 };
	    char[] arrayOfChar2 = new char[2 * paramArrayOfByte.length];
	    for (int i = 0; i < paramArrayOfByte.length; i++){
	      int j = 0xFF & paramArrayOfByte[i];
	      arrayOfChar2[(i * 2)] = arrayOfChar1[(j >>> 4)];
	      arrayOfChar2[(1 + i * 2)] = arrayOfChar1[(j & 0xF)];
	    }
	    return new String(arrayOfChar2);
	}
	public static void key() throws IOException, NoSuchAlgorithmException {
		byte[] mdkey;
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		byteArrayOutputStream.write(49);
		for (int i = 0; i < secretBytes.length; i += 2) {
			byteArrayOutputStream.write(secretBytes[i]);
			byteArrayOutputStream.write(i+1);
		}
		for (int i = 1; i < secretBytes.length; i += 2) {
			byteArrayOutputStream.write(secretBytes[i]);
			byteArrayOutputStream.write(i+1);
		}
		byteArrayOutputStream.write(48);
		byteArrayOutputStream.write(48);
		byteArrayOutputStream.write(preKeyBytes);
		byteArrayOutputStream.write(secretBytes);
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		md5.update(byteArrayOutputStream.toByteArray());
		mdkey = md5.digest();
		String preFinalKey = bytesToHex(mdkey);
		preFinalKey.toUpperCase();
		byte keyByte[] = new byte[16];
		int j = 0;
		for (int i = 0 ; i < preFinalKey.length(); i += 2) {
			keyByte[j] = (byte) preFinalKey.charAt(i);
			j ++;
		}
		System.out.println(preFinalKey);
		String key = new String(keyByte);
		String finalKey = preKey;
		for (int i = 0; i <= key.length() - 4; i += 4) {
			finalKey += key.substring(i, i+4);	
			if (i != key.length() - 4) {
				finalKey += '-';
			}
		}
		System.out.println(finalKey);
	}
	public static void main(String[] args) {
		try {
			key();
		} catch (NoSuchAlgorithmException | IOException e) {
			e.printStackTrace();
		}
	}
}

最后算出来key为0123-4567-89AB-CDEF-327D-8367-2F13-AAD9,成功通过。
ps:由于还是处于java层面,因此注册机也比通过看汇编写来的容易了,第一次尝试玩着玩意,如有不对请指出~

上传的附件 challenge1.jpg
challenge2.jpg
challenge3.jpg
QQ图片20140529103845.jpg
QQ图片20140529104650.jpg
1.jpg
com.me.keygen.activity.apk

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

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

0 0 0 举报
复制成功