题目概述
本题目为Android简单逆向题目,目的考察对Android应用的基础了解
Tag:#Android逆向 #Activity #Java #Aes加密 #Random
难度:简单
预期解:0 ~ 1
实际解出:1
题目附件
官方完整WirteUP
方法一:直接打开Activity,获取Flag(针对题目,简单方法)
首先,安装应用,查看发现拥有4个Activity
其中Activity
有两个:
分别为Main
与Home
,其中MainActivity
为Laucher
即 启动/登录页面
我们如果想绕过登录,也就是绕过MainActivity
所以 我们直接打开HomeActivity
发现我们直接绕过了登录,直接拿到flag
方法二:Hook程序,始终返回True,强制登录(万能简单方法)
首先,打开应用包,反编译Dex文件:
直接定位到MainActivity打开
发现有以下代码有些可疑:
经过分析,发现用户名始终为“admin
” 但是这并不重要
我们直接查看密码判断逻辑 这里就是判断登录的函数
如果正确(True)则直接进入主页面
如果错误(False)则报错,提示不正确
我们先hook主程序,查看一下该函数的返回值:
之后随意输入密码,返回到应用查看日志:
发现为False,并且提示报错,登陆失败
于是我们直接Hook该函数,让该函数始终返回True:
Hook成功后,我们直接随便输入密码,均可成功登录
实现绕过鉴权验证,拿到flag:
方法三:逆向Android应用,分析加密过程,进行解密(正规硬核方法)
首先,我们使用逆向工具打开apk文件,反编译查看源代码
我们直接定位到MainActivity查看源码
发现登陆页面,用户名始终为admin
,密码定义了一个函数generatedPassword
查看函数:
Random random = new Random("thisispassword".hashCode());
Iterable $this$map$iv = new IntRange(1, 16);
函数定义了一个随机函数,其中thisispassword
为随机种子(seed
)
密码长度为16位
这里定义了加密字典:
while (it.hasNext()) {
((IntIterator) it).nextInt();
destination$iv$iv.add(Character.valueOf("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".charAt(random.nextInt("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".length()))));
}
所以,我们可以直接写一个脚本来生成密码:
import java.util.Random;
public class PasswordGenerator {
public static void main(String[] args) {
String generatedPassword = generatePassword();
System.out.println("Generated Password: " + generatedPassword);
}
private static String generatePassword() {
String seed = "thisispassword";
Random random = new Random(seed.hashCode());
int passwordLength = 16;
String charPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()";
StringBuilder password = new StringBuilder(passwordLength);
for (int i = 0; i < passwordLength; i++) {
password.append(charPool.charAt(random.nextInt(charPool.length())));
}
return password.toString();
}
}
由于Android应用是Java/Kotlin写的
所以我们脚本也必须是Java/Kotlin
编译运行,得到密码:
所以,我们使用 admin,3f%Qn5ELCCQtLnl3
即可正确登录系统拿到flag
这就结束了吗?
是的,拿到flag了,但是有没有想过flag是如何加密的呢?
接下来分析一下flag的加密方式
首先我们反编译homeactivity:
我们会发现,flag加密方式非常简单,仅仅使用了AES
放入在线工具,也能直接解密获得flag
flag加密比登录加密简单太多了,不会真的有人去破解登录加密吧
出题人小记
本次也是第一次接触Android开发,出题也遇到了不少问题,挺有意思的
包里面有很多默认的东西都没删除,这让包体变得很大,更不容易解出(?)
预期 0~1 解,还真没想到有人可以做出来,从零开始现学能做出来是真的不容易了,为他点个赞
最后附上程序的源代码,可以供你们参考一下
Activities:
package com.xjw.ezandroidre
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.util.Random
import android.util.Log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val username = findViewById<EditText>(R.id.username)
val password = findViewById<EditText>(R.id.password)
val loginButton = findViewById<Button>(R.id.loginButton)
val generatedPassword = generatePassword()
loginButton.setOnClickListener {
if (username.text.toString() == "admin" && password.text.toString() == generatedPassword) {
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(this, "想直接登录?没门!", Toast.LENGTH_SHORT).show()
}
}
}
private fun generatePassword(): String {
val seed = "thisispassword"
val random = Random(seed.hashCode().toLong())
val hashCode = seed.hashCode()
Log.d("PasswordGenerator", "HashCode: $hashCode")
val passwordLength = 16
val charPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()"
return (1..passwordLength)
.map { charPool[random.nextInt(charPool.length)] }
.joinToString("")
}
}
package com.xjw.ezandroidre
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import android.util.Base64
class HomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
val getFlagButton = findViewById<Button>(R.id.getFlagButton)
val flagText = findViewById<TextView>(R.id.flagText)
getFlagButton.setOnClickListener {
val encryptedFlag = "BPSRhCCmxqbB9JzVngPDJHOP41GOfvKtr7K8DYL5bNmUflxj/MtvouWXzFp+n261"
val decryptedFlag = decryptFlag(encryptedFlag)
flagText.text = decryptedFlag
}
}
private fun decryptFlag(encrypted: String): String {
val key = "zheshimimazhende"
val secretKey = SecretKeySpec(key.toByteArray(Charsets.UTF_8), "AES")
val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, secretKey)
val decodedBytes = Base64.decode(encrypted, Base64.DEFAULT)
val decryptedBytes = cipher.doFinal(decodedBytes)
return String(decryptedBytes).trim()
}
}
Aes加密脚本:
from Crypto.Cipher import AES
import base64
def pad(s):
return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
def encrypt_flag(flag, key):
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
encrypted = cipher.encrypt(pad(flag).encode('utf-8'))
return base64.b64encode(encrypted).decode('utf-8')
if __name__ == "__main__":
flag = "flag{4ndr01d_r3_4ct1v1ty_50_c00l!}"
key = "zheshimimazhende"
encrypted_flag = encrypt_flag(flag, key)
print(f"Encrypted flag: {encrypted_flag}")