好友
阅读权限 30
听众
最后登录 1970-1-1
本帖最后由 zxcfvasd 于 2014-6-7 16:29 编辑
【标题】: Keygen Challenge 攻略
【作者】: h_one
也有段时间没有发帖子了,前两天找到Claud大侠推荐的一个android cm,IDA,">A Keygen Challenge for Android 初学android的菜菜也来练练手,今天将自己分析的过程与大家分享下。
这个游戏共有5关:
一.程序流程分析
使用APK改之理反编译 通过AndroidManfest.xml找到入口Activity的smali进行分析。找到oncreate创建下拉列表,共5个选项,并为每个选项绑定一个值,依次是1~5 分别表示难易程度
[AppleScript] 纯文本查看 复制代码
#levels
const v6, 0x1090008
invoke-static {p0, v5, v6}, Landroid/widget/ArrayAdapter;->createFromResource(Landroid/content/Context;II)Landroid/widget/ArrayAdapter;
#ArrayAdapter adapter = ArrayAdapter.createFromResource(R.id.levels, v6);
move-result-object v0
.line 64
.local v0, "adapter":Landroid/widget/ArrayAdapter;, "Landroid/widget/ArrayAdapter<Ljava/lang/CharSequence;>;"
const v5, 0x1090009
invoke-virtual {v0, v5}, Landroid/widget/ArrayAdapter;->setDropDownViewResource(I)V
.line 65
iget-object v5, p0, Lcom/me/keygen/activity/MainActivity;->levelSelect:Landroid/widget/Spinner;
#adapter.setDropDownViewResource(v5);
invoke-virtual {v5, p0}, Landroid/widget/Spinner;->setOnItemSelectedListener(Landroid/widget/AdapterView$OnItemSelectedListener;)V
.line 67
iget-object v5, p0, Lcom/me/keygen/activity/MainActivity;->levelSelect:Landroid/widget/Spinner;
#创建下拉列表框, 并且位每一个选项绑定一个值
invoke-virtual {v5, v0}, Landroid/widget/Spinner;->setAdapter(Landroid/widget/SpinnerAdapter;)V
.line 69
iget-object v5, p0, Lcom/me/keygen/activity/MainActivity;->submit:Landroid/widget/Button;
new-instance v6, Lcom/me/keygen/activity/MainActivity$1;
invoke-direct {v6, p0}, Lcom/me/keygen/activity/MainActivity$1;-><init>(Lcom/me/keygen/activity/MainActivity;)V
invoke-virtual {v5, v6}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
#创建 注册按钮监听事件
.line 77
进入按钮事件后调用 validateSerial() 函数 ,获取用户名和序列号,然后根据选择的 currentChallenge字段来进入不同难度 ,默认值是1,最后调用接口函数isValid对用户名序列号验证
[AppleScript] 纯文本查看 复制代码
.line 102
iget-object v0, p0, Lcom/me/keygen/activity/MainActivity;->name:Landroid/widget/EditText;
invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v7
#String name = this.name.getText().toString();
.line 103
.local v7, "name":Ljava/lang/String;
iget-object v0, p0, Lcom/me/keygen/activity/MainActivity;->serial:Landroid/widget/EditText;
invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v8
#String serial = this.serial.getText().toString();
.line 104
.local v8, "serial":Ljava/lang/String;
iget v0, p0, Lcom/me/keygen/activity/MainActivity;->currentChallenge:I
#currentChallenge 字段 是根据下拉列表选择的按钮,也就是难易程序的选择
invoke-direct {p0, v0}, Lcom/me/keygen/activity/MainActivity;->getVerifierForChallenge(I)Lcom/me/keygen/verifiers/KeyVerifier;
move-result-object v6
#KeyVerifier kv = getVerifierForChallenge(this.currentChallenge);
.line 105
.local v6, "kv":Lcom/me/keygen/verifiers/KeyVerifier;
if-nez v6, :cond_0
.line 107
const-string v0, "No Verifier Present For This Level"
invoke-static {p0, v0, v10}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 129
:goto_0
return-void
.line 110
:cond_0
invoke-interface {v6, v7, v8}, Lcom/me/keygen/verifiers/KeyVerifier;->isValid(Ljava/lang/String;Ljava/lang/String;)Z
#kv.isValid() 调用KeyVerifier接口的isValid对用户名序列号验证
move-result v9
二.闯关
0x1
第一关很简单,直接看源码,写出注册机搞定
抠出代码写注册机. 我用的C
[C] 纯文本查看 复制代码
int main(int argc, char* argv[])
{
char username[20] = {0};
printf("input username:");
scanf("%s", username);
int i = 0, m = 0;
int len = strlen(username);
for (int j = 0; j < len; j++)
{
m = username[j];
i = m ^ i + m * m;
}
printf("%d\n", i);
getchar();
}
0x2
第二关也比较简单,需要源码与smlia结合看
上图就是算法,首先用户名大于4位,将输入用户名转化成大写,依次取每一位变换求和得到 l , 将求和值转化成字符串str2。这里反编译出现了不可见的字符,对比查看smali,一个是0x30,一个是0x40,相差0x10。j是str2每一位加0x30求和值,因此输入的序列号如果与str2长度相同的话只需要每一位比str都大0x10,这样就可以找到序列号。
根据以上分析写出注册机:
[C] 纯文本查看 复制代码
int main(int argc, char* argv[])
{
char username[20] = {0};
printf("input username:");
scanf("%s", username);
/************************************************************************/
/* challeng1 */
/************************************************************************/
/* int i = 0, m = 0;
int len = strlen(username);
for (int j = 0; j < len; j++)
{
m = username[j];
i = m ^ i + m * m;
}
printf("%d\n", i);
getchar();*/
/************************************************************************/
/* challeng2 */
/************************************************************************/
int name_len = 0,i = 0;
name_len = strlen(username);
char str1[20] = {0};
long nameSum = 0;
for (i = 0; i < name_len; i++)
{
if (username[i] >= 'a' && username <= 'z')[/i]
{
username -= 32;
}
}
for (i = 0; i < name_len; i++)
{
nameSum = 3 * (nameSum + username) - 64;
}
itoa(nameSum, str1, 10);
char *finalsum = new char[strlen(str1)];
for (i = 0; i < strlen(str1); i++)
{
finalsum = str1 + 16;
}
printf("%s\n", finalsum);
return 0;
[i]}
0x3
这个比前两个稍微难了些,但也只是java层面的。编译为源码后只能看到一部分java代码了,关键的isVaild函数做了处理,只有老老实实的看smali了.
检查Serial字符串格式
[Java] 纯文本查看 复制代码
string[] parts = serial.split("-");
if(parts.length != 0x8)
return;
说明序列号格式是 XXXXX-XXXX-XXXXX-XXXXXX-XXXX-XXXX-XXXX-XXXX形式的
[AppleScript] 纯文本查看 复制代码
.line 25
const-string v9, "-"
invoke-virtual {p2, v9}, Ljava/lang/String;->split(Ljava/lang/String;)[Ljava/lang/String;
move-result-object v5
#String[] parts = serial.split("-");
.line 26
.local v5, "parts":[Ljava/lang/String;
array-length v9, v5
const/16 v10, 0x8
if-eq v9, v10, :cond_1
#if(parts.length != 0x8); return ;
.line 92
:cond_0
:goto_0
return v8
.line 31
:cond_1
这是一个for循环 ,8段字符串正则匹配,由const-string v10, "[0-9A-F][0-9A-F][0-9A-F][0-9A-F]" 可以知道每段4个字符且每个字符为0~9 A~F
serial:xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx(0~9||A~F)
[AppleScript] 纯文本查看 复制代码
:cond_1
const/4 v7, 0x0
#int x = 0;
.local v7, "x":I
:goto_1
array-length v9, v5
#v9 = parts.length;
if-ge v7, v9, :cond_2
#if(v7 >= 0x8)
.line 33
aget-object v9, v5, v7
const-string v10, "[0-9A-F][0-9A-F][0-9A-F][0-9A-F]"
invoke-virtual {v9, v10}, Ljava/lang/String;->matches(Ljava/lang/String;)Z
#每段字符串正则匹配
move-result v9
if-eqz v9, :cond_0
.line 31
add-int/lit8 v7, v7, 0x1
goto :goto_1
接下来是两个for循环,将secretBytes 数据写入输出流
[Java] 纯文本查看 复制代码
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(0x31);
for(int i = 0; i < secretBytes.length; i+=2)
{
baos.write(secretBytes);
baos.write(i+1);
}
for(int i = 1; i < secretBytes.length; i += 2)
{
baos.write(secretByte);
baos.write(i+1);
}
[AppleScript] 纯文本查看 复制代码
.line 39
:cond_2
new-instance v0, Ljava/io/ByteArrayOutputStream;
#ByteArrayOutputStream baos = new ByteArrayOutputStream();
invoke-direct {v0}, Ljava/io/ByteArrayOutputStream;-><init>()V
.line 41
.local v0, "baos":Ljava/io/ByteArrayOutputStream;
const/16 v9, 0x31
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(0x31);
.line 43
const/4 v7, 0x0
#int i = 0;
:goto_2
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->secretBytes:[B
array-length v9, v9
if-ge v7, v9, :cond_3
# i < secretBytes.length;
.line 45
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->secretBytes:[B
aget-byte v9, v9, v7
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(secretBytes);
.line 46
add-int/lit8 v9, v7, 0x1
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(i+1);
.line 43
add-int/lit8 v7, v7, 0x2
#i+=2;
goto :goto_2
.line 49
:cond_3
const/4 v7, 0x1
##int v7 = 1;
:goto_3
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->secretBytes:[B
array-length v9, v9
if-ge v7, v9, :cond_4
# v7 > secretBytes.length;
.line 51
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->secretBytes:[B
aget-byte v9, v9, v7
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(secretByte);
.line 52
add-int/lit8 v9, v7, 0x1
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(i+1);
.line 49
add-int/lit8 v7, v7, 0x2
goto :goto_3 接下来一个for循环是将序列号的前4段部分 经处理依次写入标准输出流
[Java] 纯文本查看 复制代码
baos.write(0x30);
baos.write(0x30);
for(int = 0; i < parts.length/2; i++)
{
baos.write(parts.Charset.forName("US_ASCII"));
baos.wirte("-");
}
baos.write(secretBytes);
[AppleScript] 纯文本查看 复制代码
.line 55
:cond_4
invoke-virtual {v0, v11}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(0x30);
.line 56
invoke-virtual {v0, v11}, Ljava/io/ByteArrayOutputStream;->write(I)V
#baos.write(0x30);
.line 58
const/4 v7, 0x0
#i = 0;
:goto_4
array-length v9, v5 #v5存放是注册码数组(parts)
#0x8 = parts.length
div-int/lit8 v9, v9, 0x2
#v9 = 4; 获取序列号的前四段
if-ge v7, v9, :cond_5
#i < 4
.line 62
:try_start_0
aget-object v9, v5, v7
#parts
const-string v10, "US_ASCII"
invoke-static {v10}, Ljava/nio/charset/Charset;->forName(Ljava/lang/String;)Ljava/nio/charset/Charset;
move-result-object v10
invoke-virtual {v9, v10}, Ljava/lang/String;->getBytes(Ljava/nio/charset/Charset;)[B
move-result-object v9
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write([B)V
#baos.write(parts.Charset.forName("US_ASCII"));
.line 63
const/16 v9, 0x2d
#紧接着写入“-”
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write(I)V
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.line 58
add-int/lit8 v7, v7, 0x1
#i++;
goto :goto_4
.line 64
:catch_0
move-exception v1
.local v1, "e":Ljava/lang/Exception;
goto :goto_0
.line 68
.end local v1 # "e":Ljava/lang/Exception;
:cond_5
:try_start_1
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->secretBytes:[B
#baos.write(secretBytes);
invoke-virtual {v0, v9}, Ljava/io/ByteArrayOutputStream;->write([B)V
:try_end_1
.catch Ljava/lang/Exception; {:try_start_1 .. :try_end_1} :catch_1
接下来将前面准备的标准输出流数据经行MD5摘要, 最后经过MD5处理后得到的32位字符串会保存在v2寄存器上,此处我们可以smali注入查看
[Java] 纯文本查看 复制代码
byte[] result = new byte[0x20];[/color]
[color=#000000]MessageDigest md = MessageDigest.getInstance("MD5");[/color]
[color=#000000]md.update(baos.toByteArray());[/color]
[color=#000000]result = md.digest();[/color]
[color=#000000]String sFinalKey = bytesToHex(md.digest()).toUpperCase();
[AppleScript] 纯文本查看 复制代码
.line 72
const/16 v9, 0x20
new-array v6, v9, [B
#byte[] result = new byte[0x20];
.line 75
.local v6, "result":[B
:try_start_2
const-string v9, "MD5"
invoke-static {v9}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;
move-result-object v4
#MessageDigest md = MessageDigest.getInstance("MD5");
.line 76
.local v4, "md":Ljava/security/MessageDigest;
invoke-virtual {v0}, Ljava/io/ByteArrayOutputStream;->toByteArray()[B
move-result-object v9
invoke-virtual {v4, v9}, Ljava/security/MessageDigest;->update([B)V
#md.update(baos.toByteArray());
.line 77
invoke-virtual {v4}, Ljava/security/MessageDigest;->digest()[B
:try_end_2
.catch Ljava/lang/Exception; {:try_start_2 .. :try_end_2} :catch_2
move-result-object v6
#result = md.digest();
.line 80
invoke-static {v6}, Lcom/me/keygen/verifiers/challenge/Challenge3Verifier;->bytesToHex([B)Ljava/lang/String;
move-result-object v9
invoke-virtual {v9}, Ljava/lang/String;->toUpperCase()Ljava/lang/String;
#String sFinalKey = bytesToHex(md.digest()).toUpperCase();
move-result-object v2[/color][color=#0000ff] #此处的v2就是最终MD5处理后的字符串,在这里我们可以进行Smila注入,查看v2值[/color][color=#000000]
将前面得到的MD5字符串,取偶数位,依次和输入序列号的后16位比较。因此我们可以在前面经行Smali注入,取出偶数位则是正确序列号 [AppleScript] 纯文本查看 复制代码
.line 81
.local v2, "foo":Ljava/lang/String;
invoke-virtual {p2}, Ljava/lang/String;->length()I
move-result v9
div-int/lit8 v9, v9, 0x2
#v9 = serial.length/2
invoke-virtual {p2, v9}, Ljava/lang/String;->substring(I)Ljava/lang/String;
move-result-object v3
#lastHalf = serial.substring(serial.length/2); 取输入序列号后半部分
.line 82
.local v3, "lastHalf":Ljava/lang/String;
const-string v9, "-"
const-string v10, ""
invoke-virtual {v3, v9, v10}, Ljava/lang/String;->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#lastHalf.replaceAll("-", ""); 去除后半部分序列号中的“-”
move-result-object v3
.line 84
const/4 v7, 0x0
#int i = 0;
:goto_5
invoke-virtual {v2}, Ljava/lang/String;->length()I
move-result v9
if-ge v7, v9, :cond_6
#i < sFinalString.length();
.line 86
invoke-virtual {v2, v7}, Ljava/lang/String;->charAt(I)C
move-result v9
#v9 = sFinalString.charAt(i);
div-int/lit8 v10, v7, 0x2
#v10 = i/2;
invoke-virtual {v3, v10}, Ljava/lang/String;->charAt(I)C
#v10 = lastHalf.charAt(v10);
move-result v10
if-ne v9, v10, :cond_0
#关键比较,将serial前半部分处理后的数据,与后半部分比较
#sFinalString是经过MD5处理后32的字符串, lastHalf是序列号后半部分且去除“-”的字符串
#sFinalString[i*2] cmp lastHalf
.line 84
add-int/lit8 v7, v7, 0x2
#i += 2;
goto :goto_5
.line 69
.end local v2 # "foo":Ljava/lang/String;
.end local v3 # "lastHalf":Ljava/lang/String;
.end local v4 # "md":Ljava/security/MessageDigest;
.end local v6 # "result":[B
:catch_1
move-exception v1
.restart local v1 # "e":Ljava/lang/Exception;
goto/16 :goto_0
.line 78
.end local v1 # "e":Ljava/lang/Exception;
.restart local v6 # "result":[B
:catch_2
move-exception v1
.restart local v1 # "e":Ljava/lang/Exception;
goto/16 :goto_0
算法到此结束可以发现整个过程与用户名毫无关系。
smlia 注入
java代码:
log.v("H_ONE3", v2);
smlia代码:
const-string v12, "H_ONE3"
invoke-static{v12, v2}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
输入序列号1111-1111-1111-1111-2222-2222-2222-2222,然后logcat 查看.
序列号为1111-1111-1111-1111-890D-0171-0C54-E3A1 通关!
0x4
这个也是java层代码,且没有任何混淆和反调试,可以直接得到源码
发现又是一大坨算法. 这些都是浮云. 注意上面圈出的
localBigInteger1是经过一些列的算法处理paramString1后得到的结果,localBigInteger2是序列号字符串转化成BigInteger类型数据
boolean bool = localBigInteger1.xor(localBigInteger2).equals(new BigInteger("0"));一句可以说明localBigInteger1就是正确的序列号。啥也不说了,直接注入干掉。
对应的smali代码以及注入
[AppleScript] 纯文本查看 复制代码
.line 62
.local v5, "serialInt":Ljava/math/BigInteger;
sget-object v7, Ljava/lang/System;->out:Ljava/io/PrintStream;
#BigInteger serialInt = new BigInteger(serial);
invoke-virtual {v2}, Ljava/math/BigInteger;->toString()Ljava/lang/String;
#bigOne.toString();
move-result-object v8 #key
#此处smlia注入
const-string v10, "H_ONE4"
invoke-static{v10, v8}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-virtual {v7, v8}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
#PrintStream.println(bigOne.toString);
.line 63
invoke-virtual {v2, v5}, Ljava/math/BigInteger;->xor(Ljava/math/BigInteger;)Ljava/math/BigInteger;
#bigOne.xor(serialInt)
move-result-object v7
#v7 = bigOne.xor(serialInt)
new-instance v8, Ljava/math/BigInteger;
const-string v9, "0"
invoke-direct {v8, v9}, Ljava/math/BigInteger;-><init>(Ljava/lang/String;)V
#BigInteger v8 = new BigInteger("0"); v8也就是数字0
invoke-virtual {v7, v8}, Ljava/math/BigInteger;->equals(Ljava/lang/Object;)Z
:try_end_0
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
#BigOne xor serialInt等于0 说明BigOne 等于 serialInt 而serialInt就是输入的字符串
#所以此处可smila注入
move-result v7
.line 64
.end local v5 # "serialInt":Ljava/math/BigInteger;
:goto_2
return v7
eg:
用户名:h_one
序列号: 94449870367585271808
0x5
第5关同样只有是java层代码,同样可以编译出源码,但是编译出的源码有时流程会出问题,说以关键地放最好还是对比smali看
比如:
上面圈出地方,编译的源码对比smali看可以发现出错了
第五关也就从这里分析起走。设备ID作为Challeng5Verifier参数传入,并由id变量保存,接下来分析接口函数isValid分析
好吧又可以直接注入上面Long.toString(l); 则是对应的正确序列号
对应smlia以及注入
[AppleScript] 纯文本查看 复制代码
.line 52
.end local v8 # "y":I
:cond_2
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->matrix:[[I
invoke-direct {p0, v9}, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->normalizeMatrix([[I)V
.line 53
iget-object v9, p0, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->matrix:[[I
invoke-direct {p0, v9}, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->mst([[I)J
move-result-wide v5
#long val = mst(this.matrix); 这个就是经过用户名处理后得到的正常序列号
.line 54
.local v5, "val":J
sget-object v9, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v9, v5, v6}, Ljava/io/PrintStream;->println(J)V
.line 55
invoke-direct {p0, p1, p2}, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->makeKey(Ljava/lang/String;Ljava/lang/String;)[B
move-result-object v3
#byte[] input = makeKey(name, serial);
.line 56
.local v3, "input":[B
invoke-static {v5, v6}, Ljava/lang/Long;->toString(J)Ljava/lang/String;
move-result-object v9
# 这里经行注入
const-string v11, "H_ONE5"
invoke-static{v11, v9}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
invoke-direct {p0, p1, v9}, Lcom/me/keygen/verifiers/challenge/Challenge5Verifier;->makeKey(Ljava/lang/String;Ljava/lang/String;)[B
move-result-object v2
eg:
用户名:h_one
三.总结: 个人觉得andoird程序若只是在java层面编写的,对于一个crack来说相当好搞。核心代码以及反调试最好用c/c++编写,NDK编译.so链接库。
免费评分
查看全部评分