1. apk 安装到手机,提示输入注册码

2. jadx打开apk

MainActivity.java

 @Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
String str2;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("com.gdufs.xman m=", "Xman");
MyApp myApp = (MyApp) getApplication();
int m = MyApp.m;
if (m == 0) {
str2 = "未注册";
} else if (m == 1) {
str2 = "已注册";
} else {
str2 = "已混乱";
}
setTitle("Xman" + str2);
this.btn1 = (Button) findViewById(R.id.button1);
this.btn1.setOnClickListener(new View.OnClickListener() { // from class: com.gdufs.xman.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
MyApp myApp2 = (MyApp) MainActivity.this.getApplication();
int m2 = MyApp.m;
if (m2 == 0) {
MainActivity.this.doRegister();
return;
}
((MyApp) MainActivity.this.getApplication()).work();
Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
}
});
} public void doRegister() {
new AlertDialog.Builder(this).setTitle("注册").setMessage("Flag就在前方!").setPositiveButton("注册", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.3
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
ComponentName cpname = new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity");
intent.setComponent(cpname);
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
}
}).setNegativeButton("不玩了", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.2
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Process.killProcess(Process.myPid());
}
}).show();
} public void work(String str) {
workString = str;

MyApp.java

public class MyApp extends Application {
public static int m = 0; public native void initSN(); public native void saveSN(String str); public native void work(); static {
System.loadLibrary("myjni");
} @Override // android.app.Application
public void onCreate() {
initSN();
Log.d("com.gdufs.xman m=", String.valueOf(m));
super.onCreate();
}
}

大概理了一下逻辑,App 先执行 initSN ,然后点击界面按钮,按钮根据APP.m的值判断是走RegActivity还是往下执行App.work(),然后弹出toast

3. IDA 打开so看看

函数都是JNI_OnLoad中注册的

int __fastcall initSN(JNIEnv *env)
{
FILE *v2; // r0
FILE *v3; // r4
JNIEnv *t_env; // r0
int file_len; // r7
void *v6; // r5
JNIEnv *tt_env; // r0
int v9; // r1 v2 = fopen("/sdcard/reg.dat", "r+");
v3 = v2;
if ( !v2 )
{
t_env = env;
return setValue(t_env, 0);
}
fseek(v2, 0, 2);
file_len = ftell(v3);
v6 = malloc(file_len + 1);
if ( !v6 )
{
fclose(v3);
t_env = env;
return setValue(t_env, 0);
}
fseek(v3, 0, 0);
fread(v6, file_len, '\x01', v3);
*((_BYTE *)v6 + file_len) = 0;
if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") )
{
tt_env = env;
v9 = 1;
}
else
{
tt_env = env;
v9 = 0;
}
setValue(tt_env, v9);
return j_fclose(v3);
}

initSn 大概逻辑,就是读一个文件,看文件的值是不是EoPAoY62@ElRD,是就反射写App.m = 1 其他情况写0;(得到关键信息EoPAoY62@ElRD)

jmethodID __fastcall n3Work(JNIEnv *env)
{
jint Value; // r0
JNIEnv *t_env; // r0
const char *v4; // r1
bool v5; // zf initSN(env);
Value = getValue(env);
if ( Value )
{
v5 = Value == 1;
t_env = env;
if ( v5 )
v4 = "输入即是flag,格式为xman{……}!";
else
v4 = "状态不太对。。";
}
else
{
t_env = env;
v4 = "还不行呢!";
}
return callWork(t_env, (int)v4);
}

work的逻辑就是执行以下initSN 然后就判断m的值是不是1,如果是1,就输出输入即是flag,格式为xman{……}!

int __fastcall n2_saveSn(JNIEnv *env, jobject a2, jstring snKey)
{
FILE *v5_fp; // r7
_DWORD *p_v21; // r4
const char *v8; // r3
int v9; // r0
int v10; // r1
_WORD *v11; // r5
JNIEnv *v12; // r0
int v13; // r4
JNIEnv v14; // r3
signed int v15_i; // r6
const char *t_snKey_chars; // r9
char *p_snKey_chars; // r5
signed int v18_len; // r10
char v19; // r2
char v20; // r3
_BYTE v21[56]; // [sp+0h] [bp-38h] BYREF v5_fp = fopen("/sdcard/reg.dat", "w+");
if ( !v5_fp )
return j___android_log_print(3, "com.gdufs.xman", byte_D0C96DCA);
p_v21 = v21;
v8 = "W3_arE_whO_we_ARE";
do
{
v9 = *(_DWORD *)v8;
v8 += 8;
v10 = *((_DWORD *)v8 - 1);
*p_v21 = v9;
p_v21[1] = v10;
v11 = p_v21 + 2;
p_v21 += 2;
}
while ( v8 != "E" );
v12 = env;
v13 = 2016;
*v11 = *(_WORD *)v8;
v14 = *env;
v15_i = 0;
t_snKey_chars = v14->GetStringUTFChars(v12, snKey, 0);
p_snKey_chars = (char *)t_snKey_chars;
v18_len = strlen(t_snKey_chars);
while ( v15_i < v18_len )
{
if ( v15_i % 3 == 1 )
{
v13 = (v13 + 5) % 16;
v19 = v21[v13 + 1]; // v21[v13 - 23]
}
else if ( v15_i % 3 == 2 )
{
v13 = (v13 + 7) % 15;
v19 = v21[v13 + 2];
}
else
{
v13 = (v13 + 3) % 13;
v19 = v21[v13 + 3]; // v21[v13 -21]
}
v20 = *p_snKey_chars;
++v15_i;
*p_snKey_chars++ = v20 ^ v19;
}
fputs(t_snKey_chars, v5_fp);
return j_fclose(v5_fp);
}

里面有两个重要逻辑,

  1. 算出初始密码
  v8 = "W3_arE_whO_we_ARE";
do
{
v9 = *(_DWORD *)v8;
v8 += 8;
v10 = *((_DWORD *)v8 - 1);
*p_v21 = v9;
p_v21[1] = v10;
v11 = p_v21 + 2;
p_v21 += 2;
}
while ( v8 != "E" );
v12 = env;
v13 = 2016;
这里有个重要细节,这里卡了我很久: (_DWORD *)v8 相当于把v8 转为一个 _DWORD * 指针,对这种指针取值时 一次取四个字节的数据, *((_DWORD *)v8 - 1) 相当于指针值 - 4 后再取四个字节的数据

转换位python为

v8 = 'W3_arE_whO_we_ARE'

i = 0
key_v8 = v8[0]
ret = ''
while (v8[i] != 'E'):
v9 = v8[i:i+4]
i += 8
v10 = v8[i-4:i]
ret += v9
ret += v10 ret+='E'
print(ret) # 输出还是 W3_arE_whO_we_ARE,开始觉得搞错了,其实时障眼法

然后时加密计算逻辑

v18_len = strlen(t_snKey_chars);
while ( v15_i < v18_len )
{
if ( v15_i % 3 == 1 )
{
v13 = (v13 + 5) % 16;
v19 = v21[v13 + 1]; // v21[v13 - 23]
}
else if ( v15_i % 3 == 2 )
{
v13 = (v13 + 7) % 15;
v19 = v21[v13 + 2];
}
else
{
v13 = (v13 + 3) % 13;
v19 = v21[v13 + 3]; // v21[v13 -21]
}
v20 = *p_snKey_chars;
++v15_i;
*p_snKey_chars++ = v20 ^ v19;
}

从算法上看时不用细看时怎么操作的,可以发现输入和输出时一一对应的,且密钥也是一一对应的,那么把输出当作输入,就可以得到原来的输入

python还原

v8 = 'W3_arE_whO_we_ARE'

i = 0
key_v8 = v8[0]
ret = ''
while (v8[i] != 'E'):
v9 = v8[i:i+4]
i += 8
v10 = v8[i-4:i]
ret += v9
ret += v10 ret+='E'
print(ret) in_str = 'EoPAoY62@ElRD'
v13 = 2016
result = ''
for i in range(0, len(in_str)):
if (i % 3 == 1):
v13 = (v13 + 5) % 16
v19 = ret[v13+1]
elif i % 3 == 2:
v13 = (v13 + 7) % 15
v19 = ret[v13+2]
else:
v13 = (v13 + 3) % 13
v19 = ret[v13+3]
v20 = in_str[i]
result += chr(ord(v20) ^ ord(v19)) print(result) # 日志
W3_arE_whO_we_ARE
201608Am!2333

成功得到flag

【Android 逆向】【攻防世界】黑客精神的更多相关文章

  1. 逆向-攻防世界-crackme

    查壳,nSpack壳,直接用软件脱壳,IDA载入程序. 很明显,就是将402130的数据和输入的数据进行异或,判断是否等于402150处的数据.dwrd占4字节. 这道题主要记录一下刚学到的,直接在I ...

  2. 逆向-攻防世界-maze

    题目提示是走迷宫. IDA载入程序分析. 输入字符长度必须是24,开头必须是nctf{,结尾必须是}.在125处按R就可以变成字符. sub_400650和sub_400660是关键函数,分析sub_ ...

  3. 逆向-攻防世界-CSAW2013Reversing2

    运行程序乱码,OD载入搜索字符串,断电到弹窗Flag附近. 发现跳过00B61000函数,弹窗乱码,我们试试调用00B61000函数.将00B61094的指令修改为JE SHORT 00B6109b. ...

  4. 逆向-攻防世界-logmein

    iDA载入程序,shift+F12查看关键字符串,找到双击来到所在地址,进入函数 然后进入主函数, 经过分析,可以得出:输入的字符要等于  经过处理的v7和v8的异或.v8很明显,但是v7是怎么回事呢 ...

  5. 逆向-攻防世界-no-strings-attached

    看题目就知道查找不到关键字符串,为防止踩坑,strings命令查看,没有找到有用的字符串.IDA载入程序查找入口函数, main函数中有4个函数,经过分析判断authenticate()为关键函数,跟 ...

  6. 攻防世界逆向——game

    攻防世界逆向:game wp 攻防世界逆向新手区的一道题目. 是一道windows的creak,动态调试打开是这样的: 题目说明是让屏幕上所有的图像都亮之后,会出现flag,看来应该是可以玩出来的. ...

  7. [转]Android逆向之动态调试总结

    一.在SO中关键函数上下断点 刚学逆向调试时.大多都满足于在SO中某关键函数上下断点.然后通过操作应用程序,去触发这个断点,然后进行调试 详细的步骤可以参见非虫大大的<Android软件安全与逆 ...

  8. Android逆向系列文章— Android基础逆向(6)

    本文作者:HAI_ 0×00 前言 不知所以然,请看 Android逆向-Android基础逆向(1) Android逆向-Android基础逆向(2) Android逆向-Android基础逆向(2 ...

  9. Android逆向-Android基础逆向(5)

    本文作者:i春秋作家——HAI_ 0×00 前言 不知所以然,请看 Android逆向-Android基础逆向(1)Android逆向-Android基础逆向(2)Android逆向-Android基 ...

  10. Android逆向-java代码基础

    作者:I春秋作家——HAI_ 0×00 前言 看这篇可以先看看之前的文章,进行一个了解.Android逆向-java代码基础(1)Android逆向-java代码基础(2) 之前看到有大佬用smali ...

随机推荐

  1. [转帖]ls 只显示目录

    https://www.cnblogs.com/lavin/p/5912369.html 只显示目录: ls -d */ 在实际应用中,我们有时需要仅列出目录,下面是 4 种不同的方法. 1. 利用 ...

  2. [转帖]Split Region 使用文档

    https://docs.pingcap.com/zh/tidb/stable/sql-statement-split-region 在 TiDB 中新建一个表后,默认会单独切分出 1 个 Regio ...

  3. [转帖]怎么查看Linux服务器硬件信息,这些命令告诉你

    https://zhuanlan.zhihu.com/p/144368206 Linux服务器配置文档找不到,你还在为查询Linux服务器硬件信息发愁吗?学会这些命令,让你轻松查看Linux服务器的C ...

  4. [转帖]Linux遇到一个内存过高的报警——释放buff/cache

    前些天一直受到内存报警,过一段时间就会恢复.由于开发工作有些多,就一直没理它,但是最近几天开始有些频繁了.虽然不影响业务,但是天天报警,还是让人提心吊胆的.因此就抽了一个上午的时间去解决一下这个问题. ...

  5. 【转帖】Linux性能优化(四)——BCC性能监控工具

    一.BCC简介 1.BCC简介 BCC是一个Python库,简化了eBPF应用的开发过程,并收集了大量性能分析相关的eBPF应用.BCC为BPF开发提供了不同的前端支持,包括Python和Lua,实现 ...

  6. Jupyter Notebook支持Go

    在执行下列命令之前,请确保你已经安装了Go和Jupyter. gophernotes是针对Jupyter和nteract的Go内核,它可以让你在基于浏览器的笔记本或桌面app上交互式地使用Go.下面介 ...

  7. Mysql索引失效场景

    Mysql索引失效场景   序言   众所周知在Mysql中,通过使用索引的方式可以加快查询速度,从而避免全文搜索:而索引本身就像图书馆中所有书籍目录,通过查询关键字就能快速找到目标书籍在几列几行,这 ...

  8. 【二】分布式训练---参数服务器训练(飞桨paddle1.8)

    1.参数服务器训练简介 参数服务器训练是分布式训练领域普遍采用的编程架构,主要解决以下两类问题: 模型参数过大:单机内存空间不足,需要采用分布式存储. 训练数据过多:单机训练太慢,需要加大训练节点,来 ...

  9. 强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示

    强化学习从基础到进阶-案例与实践[4.2]:深度Q网络DQN-Cart pole游戏展示 强化学习(Reinforcement learning,简称RL)是机器学习中的一个领域,区别与监督学习和无监 ...

  10. Python Selenium 库使用技巧

    Selenium 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE,Mozilla Firefox,Safari,Google ...