【Android 逆向】【攻防世界】黑客精神
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);
}
里面有两个重要逻辑,
- 算出初始密码
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 逆向】【攻防世界】黑客精神的更多相关文章
- 逆向-攻防世界-crackme
查壳,nSpack壳,直接用软件脱壳,IDA载入程序. 很明显,就是将402130的数据和输入的数据进行异或,判断是否等于402150处的数据.dwrd占4字节. 这道题主要记录一下刚学到的,直接在I ...
- 逆向-攻防世界-maze
题目提示是走迷宫. IDA载入程序分析. 输入字符长度必须是24,开头必须是nctf{,结尾必须是}.在125处按R就可以变成字符. sub_400650和sub_400660是关键函数,分析sub_ ...
- 逆向-攻防世界-CSAW2013Reversing2
运行程序乱码,OD载入搜索字符串,断电到弹窗Flag附近. 发现跳过00B61000函数,弹窗乱码,我们试试调用00B61000函数.将00B61094的指令修改为JE SHORT 00B6109b. ...
- 逆向-攻防世界-logmein
iDA载入程序,shift+F12查看关键字符串,找到双击来到所在地址,进入函数 然后进入主函数, 经过分析,可以得出:输入的字符要等于 经过处理的v7和v8的异或.v8很明显,但是v7是怎么回事呢 ...
- 逆向-攻防世界-no-strings-attached
看题目就知道查找不到关键字符串,为防止踩坑,strings命令查看,没有找到有用的字符串.IDA载入程序查找入口函数, main函数中有4个函数,经过分析判断authenticate()为关键函数,跟 ...
- 攻防世界逆向——game
攻防世界逆向:game wp 攻防世界逆向新手区的一道题目. 是一道windows的creak,动态调试打开是这样的: 题目说明是让屏幕上所有的图像都亮之后,会出现flag,看来应该是可以玩出来的. ...
- [转]Android逆向之动态调试总结
一.在SO中关键函数上下断点 刚学逆向调试时.大多都满足于在SO中某关键函数上下断点.然后通过操作应用程序,去触发这个断点,然后进行调试 详细的步骤可以参见非虫大大的<Android软件安全与逆 ...
- Android逆向系列文章— Android基础逆向(6)
本文作者:HAI_ 0×00 前言 不知所以然,请看 Android逆向-Android基础逆向(1) Android逆向-Android基础逆向(2) Android逆向-Android基础逆向(2 ...
- Android逆向-Android基础逆向(5)
本文作者:i春秋作家——HAI_ 0×00 前言 不知所以然,请看 Android逆向-Android基础逆向(1)Android逆向-Android基础逆向(2)Android逆向-Android基 ...
- Android逆向-java代码基础
作者:I春秋作家——HAI_ 0×00 前言 看这篇可以先看看之前的文章,进行一个了解.Android逆向-java代码基础(1)Android逆向-java代码基础(2) 之前看到有大佬用smali ...
随机推荐
- [转帖]KingbaseES wal(xlog) 日志清理故障恢复案例
https://www.cnblogs.com/kingbase/p/16266365.html 案例说明:在通过sys_archivecleanup工具手工清理wal日志时,在control文件中查 ...
- [转帖]kafka 配置认证与授权
https://www.cnblogs.com/yjt1993/p/14739130.html 本例不使用kerberos做认证,使用用户名和密码的方式来进行认证 1.服务端配置 1.0 配置serv ...
- [转帖]Linux系统硬链接和软链接具体实例讲解(超详细)
简介 在 Linux 中,元数据中的 inode 号(inode 是文件元数据的一部分但其并不包含文件名,inode 号即索引节点号)才是文件的唯一标识而非文件名.文件名仅是为了方便人们的记忆和使用, ...
- [转帖]springboot中使用skywalking实现日志追踪
文章目录 SkyWalking分布式追踪系统 介绍 主要架构 环境 引入依赖 配置Log4j2 下载编译好的8.7.0版本包 使用探针实现日志追踪 启动脚本 启动Java服务 访问服务 使用UI 切换 ...
- pytest单元测试基本使用
一.pytest安装 pip install pytest:安装 pip install pytest==version:指定版本安装 pytest --version:查看版本 pip instal ...
- vue3中markRaw的使用
markRaw 作用:将一个对象标记为不可以被转化为代理对象.返回该对象本身. 应用场景: 1.有些值不应被设置成响应式时,例如复杂的第三方类库等 2.当渲染具有不可变数据源的大列表时,跳过响应式转换 ...
- .net fromwork连接rabbitmq发布消息
1.创建连接工厂类 var factory = new RabbitMQ.Client.ConnectionFactory() { HostName = "120.237.72.46&quo ...
- layui之静态表格的分页及搜索功能以及前端使用XLSX导出Excel功能
LayUI官方文档:https://layui.dev/docs/2/#introduce XLSX NPM地址:https://www.npmjs.com/package/xlsx XLSX 使用参 ...
- 主动学习(Active Learning)简介综述汇总以及主流技术方案
0.引言 在机器学习(Machine learning)领域,监督学习(Supervised learning).非监督学习(Unsupervised learning)以及半监督学习(Semi-su ...
- 9.2 Windows驱动开发:内核解析PE结构导出表
在笔者的上一篇文章<内核特征码扫描PE代码段>中LyShark带大家通过封装好的LySharkToolsUtilKernelBase函数实现了动态获取内核模块基址,并通过ntimage.h ...