吾爱破解2024红包题部分WriteUP
本帖最后由 cattie 于 2024-2-27 16:36 编辑操作日志:24.2.27:合并Web题解
# Windows与安卓CM部分
## 前言
**第一年参加红包题目,其中有些操作可能不是那么熟练,感谢各位大佬批评指正,嗯。**
(后面的高级题太菜了不会做)
## 解题领红包之二 {Windows 初级题}
**新手题:送分题完成啦来试试新手题吧,点击下方“立即申请”任务,即可获得本题Windows CrackMe题目下载地址,通过分析CrackMe获得本题正确口令的解题方法。**
### 查壳
嗯,很好,没有壳,直接分析。
![](https://imgsrc.baidu.com/forum/pic/item/500fd9f9d72a60599f9684236e34349b033bba01.png)
### IDA静态分析
(这是什么鬼啊,撤了撤了)
**后来发现`ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?`这一串东西是凯撒密码加密过的密文,偏移量为3,但字典是自定义的,懒得跟踪了,直接用OD了**
```cpp
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v4; // ebx
void **v5; // edi
int v6; // eax
void **v7; // ecx
void **v8; // edx
unsigned int v9; // esi
bool v10; // cf
int v11; // eax
void *v12; // ecx
void **v13; // eax
void *v14; // ecx
void *Src; // BYREF
unsigned int v17; //
void *Block; // BYREF
unsigned int v19; //
void *v20; // BYREF
int v21; //
unsigned int v22; //
int v23; //
Src = 0;
v17 = 15;
LOBYTE(Src) = 0;
sub_402560(Src, "ioCj~KCss|bQ6zbhCu$5r57$Iljkwlqj$$$?", 0x24u);
v23 = 0;
SetConsoleTitleA(&ConsoleTitle);
v21 = 0;
v22 = 15;
LOBYTE(v20) = 0;
LOBYTE(v23) = 1;
v3 = sub_4027D0();
sub_402A80(v3);
sub_4031E0(&dword_42E088, v20);
v4 = v22;
v5 = (void **)v20;
if ( v21 == 36 )
{
sub_402490(Src);
sub_401FE0();
LOBYTE(v23) = 2;
v7 = Block;
v8 = v20;
if ( v19 >= 0x10 )
v7 = (void **)Block;
if ( v4 >= 0x10 )
v8 = v5;
if ( Block == (void *)36 )
{
v9 = 32;
do
{
if ( *v8 != *v7 )
break;
++v8;
++v7;
v10 = v9 < 4;
v9 -= 4;
}
while ( !v10 );
}
v11 = sub_4027D0();
sub_402A80(v11);
if ( v19 >= 0x10 )
{
v12 = Block;
if ( v19 + 1 >= 0x1000 )
{
v12 = (void *)*((_DWORD *)Block - 1);
if ( (unsigned int)(Block - v12 - 4) > 0x1F )
_invalid_parameter_noinfo_noreturn();
}
sub_406010(v12);
}
sub_40A6EE("Pause");
}
else
{
v6 = sub_4027D0();
sub_402A80(v6);
sub_40A6EE("Pause");
}
if ( v4 >= 0x10 )
{
v13 = v5;
if ( v4 + 1 >= 0x1000 )
{
v5 = (void **)*(v5 - 1);
if ( (unsigned int)((char *)v13 - (char *)v5 - 4) > 0x1F )
_invalid_parameter_noinfo_noreturn();
}
sub_406010(v5);
}
if ( v17 >= 0x10 )
{
v14 = Src;
if ( v17 + 1 >= 0x1000 )
{
v14 = (void *)*((_DWORD *)Src - 1);
if ( (unsigned int)(Src - v14 - 4) > 0x1F )
_invalid_parameter_noinfo_noreturn();
}
sub_406010(v14);
}
return 0;
}
```
### OD动态调试
1. 打开OD,导入文件
2. 使用中文搜索引擎,找到一个Success和两个Try again,以及一个Tip
![](https://imgsrc.baidu.com/forum/pic/item/b03533fa828ba61eb313ad160734970a304e590d.png)
(Caesar Cipher恺撒密码......怪不得IDA看不懂)
3. 先看看第一个Try again
上方有个cmp和je,也就是说比较当前字符串长度是否长为36,如果是的话就继续判断,否则就输出 "Error, please try again"
![](https://imgsrc.baidu.com/forum/pic/item/3812b31bb051f819f3d366739cb44aed2e73e711.png)
我们输入36个1,输出变为了"Wrong, please try again",证明上述分析没有问题
![](https://imgsrc.baidu.com/forum/pic/item/d1a20cf431adcbefabdb1b3feaaf2edda3cc9f18.png)
4. 找到"Wrong, please try again"所在位置,找到判断条件,下断点,再次输入36个1,在MMX中即可找到CM的flag
![](https://imgsrc.baidu.com/forum/pic/item/9c16fdfaaf51f3de4f57dc5dd2eef01f3a297926.png)
> **Flag:fl@g{H@ppy\_N3w\_e@r!2o24!Fighting!!!}**
![](https://imgsrc.baidu.com/forum/pic/item/a50f4bfbfbedab645d9128f9b136afc379311e23.png)
## 解题领红包之三 {Android 初级题}
**题目简介:小明和李华是同学,最近小明发现李华技术进步很快,他太想进步了,于是他一直在观察李华,却发现他老是在玩圈小猫,直到一次偶然发现,小明惊呼:“WC,原。。。”**
### 游戏
大家一定玩过论坛的抓小猫吧(404界面),没玩过的也没事,现在[打开链接](https://www.52pojie.cn/1)也能玩【此处感谢Ganlv佬提供的游戏】
作为常在水区抓猫的“抓猫高手”,是时候展现真正的“寄”术了
(emmm,确实有些汗流浃背)
![](https://imgsrc.baidu.com/forum/pic/item/e61190ef76c6a7ef27d2c01fbbfaaf51f3de6617.png)
### Hacker(破解游戏,调低难度)
**用7-zip打开apk文件,发现里面有抓猫猫的主程序`catch-the-cat.js`,**
![](https://imgsrc.baidu.com/forum/pic/item/9a504fc2d5628535031bbc18d6ef76c6a7ef6314.png)
这和论坛的抓猫游戏是一样诶,那就好办了
我们直接参照Ganlv佬的[代码](https://github.com/ganlvtech/phaser-catch-the-cat)
修改一下抓猫猫的js主程序`catch-the-cat.js`,定位到变量`initialWallCount`
把墙的数量改多一点,初始是10个所以抓不住,那么就把墙的数量改一下变成30吧(doge)
![](https://imgsrc.baidu.com/forum/pic/item/21a4462309f7905201062c074af3d7ca7bcbd5ea.png)
然后替换js文件,因为文件被修改过了,所以apk需要重新签名一下
这里采用(https://www.luckypatchers.com/download/),打个测试签名就行了,以下安卓部分修改后都用此方式打签名,不再重复赘述
![](https://imgsrc.baidu.com/forum/pic/item/0b46f21fbe096b63540d35694a338744ebf8acf7.png)
**“这下是谁该汗流浃背了呢......”**
![](https://imgsrc.baidu.com/forum/pic/item/a8014c086e061d95764b5c013df40ad162d9cafe.png)
抓住猫,熟悉的bgm响起,看描述就猜到的标准结局......
“Genshin,Start!”
![](https://imgsrc.baidu.com/forum/pic/item/d31b0ef41bd5ad6eeff10fd3c7cb39dbb6fd3cfa.png)
![](https://imgsrc.baidu.com/forum/pic/item/6d81800a19d8bc3eb8616df3c48ba61ea8d34599.png)
> **Flag:flag{happy_new_year_2024}**
### Disassemble(反编译dex看函数)
说完了上面的破解游戏本体,接下来就是直接对安装包本体下手了,上apktool
解包发现了一个以作者名字命名的文件夹,
里面有MainActivity和YSQDActivity(原神启动?)
![](https://imgsrc.baidu.com/forum/pic/item/9358d109b3de9c82d2c992b62a81800a19d8435e.png)
分析:
1. 主进程调用Webview运行抓猫游戏
![](https://imgsrc.baidu.com/forum/pic/item/2e2eb9389b504fc234d0db3ca3dde71190ef6d78.png)
2. 抓住猫以后播放ys.mp4
![](https://imgsrc.baidu.com/forum/pic/item/d4628535e5dde711755528cfe1efce1b9d166100.png)
3. 视频播放完以后用SetText显示Flag
![](https://imgsrc.baidu.com/forum/pic/item/ac4bd11373f08202116bfa060dfbfbedab641b09.png)
(最后的flag是通过字符操作得到的,没有直接给出,而在该文件中也没有提及具体操作步骤,猜想flag可能藏在那个播放的视频`ys.mp4`的文件末尾,因时间原因就不跟下去了)
![](https://imgsrc.baidu.com/forum/pic/item/e61190ef76c6a7ef26f6c31fbbfaaf51f3de6633.png)
## 解题领红包之四 {Android 初级题}
**寄语:如果不会解题还想拿分那赶紧来现学现卖吧,只要认真看完并动手练习,肯定能解出来本题,吾爱破解安卓逆向入门教程《安卓逆向这档事》。**
### 游戏
第二个小游戏,居然是抽卡
![](https://imgsrc.baidu.com/forum/pic/item/fcfaaf51f3deb48f6157cbe7b61f3a292df578c9.png)
(吐槽一句:这0.6%概率真有点低啊......)
### BUG玩法
这个程序没有采用数据库方式,**退出重新进入程序即刷新次数为10次**
**于是乎,“只要我不停地抽,0.6%也不算什么”(doge)**
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc379315e5aec10adc4b74543a911dc.png)
> **Flag:flag{52pj_HappyNewYear2024}**
### Hacker(破解游戏,调低难度)
**用英文含义命名Activity是个好习惯,至少对于CM而言是这样**
用apktool反编译apk文件,在程序中,我们又发现了作者的信息
![](https://imgsrc.baidu.com/forum/pic/item/203fb80e7bec54e7edf57027ff389b504fc26aea.png)
各位是否发现增加一抽所需要的时间在不断递增?我们简单修改一下增加一抽所需要时间(修改概率、保底同理,只要修改对应的数值就行了)
**将`array_1`中的数据全部改成0x1(即增加一抽仅需要1s),最坏情况下也只需要90s**
![](https://imgsrc.baidu.com/forum/pic/item/f703738da9773912d05bef58be198618367ae28e.png)
修改、编译一气呵成**(注意这时不要签名,因为flag在签名里面)**,直接核心破解安装即可,发现此时软件已经变成1秒增加1抽了(doge)
![](https://imgsrc.baidu.com/forum/pic/item/5ab5c9ea15ce36d361d9e9097cf33a87e950b12e.png)
## 解题领红包之五 {Android 中级题}
**题目简介:我,玄天帝,,解封!!!**
### 游戏
emmm,九宫格图形解锁
![](https://imgsrc.baidu.com/forum/pic/item/8ad4b31c8701a18be9f24776d82f07082838feea.png)
运用强大的搜索引擎,找到了一个类似的开源项目(https://github.com/7heaven/GestureLock)
**小知识,安卓系统的图形是以数字密码形式存放在文件中的,所以推断该图形代表的也是一个数字字符串**
### 静态分析
使用jde反编译得到一堆smali文件,打开以作者名字命名的文件夹
#### GestureUnlock本体
既然是游戏,那么我们就研究一下这款游戏的本体吧,找到以下定义:
```java
public GestureUnlock(Context context0, AttributeSet attributeSet0, int v) {
super(context0, attributeSet0, v);
this.cicleRadius = 10;
this.firstInit = false;
this.points = new ArrayList();
this.selectP = new ArrayList();
this.alreadyTouch = false;
this.isUp = false;
this.lockTouch = false;
this.returnFun = 0;
this.defaultKey = "01234";
this.setUpKey = "";
this.errorKey = "";
}
```
在MainActivity里面也没找到修改密码的Set函数,所以我们试试它的默认密码"01234"
嗯,看上去是对了,然后啥都没发生(我的flag呢!
【注意:此处为GestureUnlock(也就是这个密码锁)的密码,但不是Flag的密码,Flag的密码s在后续会说到】
![](https://imgsrc.baidu.com/forum/pic/item/8ad4b31c8701a18be25f4e76d82f07082838fe57.png)
后续分析发现输入该密码时会进入函数`isSuccessful`,补充一句该函数是GestureUnlock自带密码正确的回调函数,但此题关键不在此处
```java
public void isSuccessful(String s) {
Log.e("zj595", s);
}
```
而只有输入"错误"的密码才会进入真正的函数isError
```java
public void isError(String s) {
Log.e("zj595", s);
MainActivity.this.checkPassword(s); //checkPassword是个很重要的函数,接下来就会说到
}
```
#### 解密函数
如上部分所述,在MainActivity中提及了一个重要的函数:`checkPassword`,先看它的smali code:
```smali
.method public checkPassword(String)Z
.registers 11
00000000const/4 v0, 0
:try_2
00000002invoke-virtual MainActivity->getAssets()AssetManager, p0#读取Assets中的classes.dex
00000008move-result-objectv1
0000000Aconst-string v2, "classes.dex"
0000000Einvoke-virtual AssetManager->open(String)InputStream, v1, v2
00000014move-result-objectv1
00000016invoke-virtual InputStream->available()I, v1
0000001Cmove-result v2
0000001Enew-array v2, v2, [B
00000022invoke-virtual InputStream->read([B)I, v1, v2
00000028new-instance v3, File
0000002Cconst-string v4, "data"
00000030invoke-virtual MainActivity->getDir(String, I)File, p0, v4, v0
00000036move-result-objectv4
00000038const-string v5, "1.dex"
0000003Cinvoke-direct File-><init>(File, String)V, v3, v4, v5
00000042new-instance v4, FileOutputStream
00000046invoke-direct FileOutputStream-><init>(File)V, v4, v3
0000004Cinvoke-virtual FileOutputStream->write([B)V, v4, v2#将classes.dex释放至"/data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex"
00000052invoke-virtual FileOutputStream->close()V, v4
00000058invoke-virtual InputStream->close()V, v1
0000005Econst-string v1, "dex"
00000062invoke-virtual MainActivity->getDir(String, I)File, p0, v1, v0
00000068move-result-objectv1
0000006Ainvoke-virtual Object->getClass()Class, p0
00000070move-result-objectv2
00000072invoke-virtual Class->getClassLoader()ClassLoader, v2
00000078move-result-objectv2
0000007Anew-instance v4, DexClassLoader
0000007Einvoke-virtual File->getAbsolutePath()String, v3
00000084move-result-objectv3
00000086invoke-virtual File->getAbsolutePath()String, v1
0000008Cmove-result-objectv1
0000008Econst/4 v5, 0
00000090invoke-direct DexClassLoader-><init>(String, String, String, ClassLoader)V, v4, v3, v1, v5, v2
00000096const-string v1, "com.zj.wuaipojie2024_2.C"
0000009Ainvoke-virtual DexClassLoader->loadClass(String)Class, v4, v1# 加载classes.dex里面的C Activity,和上面的C.smali对应
000000A0move-result-objectv1
000000A2const-string v2, "isValidate"
000000A6const/4 v3, 3
000000A8new-array v4, v3, [Class
000000ACconst-class v6, Context
000000B0aput-object v6, v4, v0
000000B4const-class v6, String
000000B8const/4 v7, 1
000000BAaput-object v6, v4, v7
000000BEconst-class v6, [I
000000C2const/4 v8, 2
000000C4aput-object v6, v4, v8
000000C8invoke-virtual Class->getDeclaredMethod(String, [Class)Method, v1, v2, v4
000000CEmove-result-objectv1
000000D0new-array v2, v3, [Object
000000D4aput-object p0, v2, v0
000000D8aput-object p1, v2, v7
000000DCinvoke-virtual MainActivity->getResources()Resources, p0# actual call site: Landroidx/appcompat/app/AppCompatActivity;->getResources()Landroid/content/res/Resources;
000000E2move-result-objectp1
000000E4sget v3, R$array->A_offset:I
000000E8invoke-virtual Resources->getIntArray(I)[I, p1, v3# 传入Gesture构成的数字数组
000000EEmove-result-objectp1
000000F0aput-object p1, v2, v8
000000F4invoke-virtual Method->invoke(Object, [Object)Object, v1, v5, v2
000000FAmove-result-objectp1
000000FCcheck-cast p1, String
00000100if-eqz p1, :12E # 如果比较结果等于0,则跳转12E
:104
00000104const-string v1, "唉!"
00000108invoke-virtual String->startsWith(String)Z, p1, v1
0000010Emove-result v1
00000110if-eqz v1, :12E
:114
00000114iget-object v1, p0, MainActivity->tvText:TextView
00000118invoke-virtual TextView->setText(CharSequence)V, v1, p1
0000011Eiget-object p1, p0, MainActivity->myunlock:GestureUnlock
00000122const/16 v1, 8
00000126invoke-virtual GestureUnlock->setVisibility(I)V, p1, v1
.catch Exception {:try_2 .. :tryend_12C} :catch_130
:tryend_12C
0000012Creturn v7
:12E
0000012Ereturn v0
:catch_130# used for: Ljava/lang/Exception;
00000130move-exception p1
00000132invoke-virtual Exception->printStackTrace()V, p1
00000138return v0
.end method
```
反编译为java就是:
```java
public class MainActivity extends AppCompatActivity {
private GestureUnlock myunlock;
private TextView tvText;
static {
System.loadLibrary("52pj");
}
public boolean checkPassword(String s) {
try {
InputStream inputStream0 = this.getAssets().open("classes.dex");
byte[] arr_b = new byte;
inputStream0.read(arr_b);
File file0 = new File(this.getDir("data", 0), "1.dex");
FileOutputStream fileOutputStream0 = new FileOutputStream(file0);
fileOutputStream0.write(arr_b);
fileOutputStream0.close();
inputStream0.close();
File file1 = this.getDir("dex", 0);
ClassLoader classLoader0 = this.getClass().getClassLoader();
String s1 = (String)new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, s, this.getResources().getIntArray(array.A_offset));
if(s1 != null && (s1.startsWith("唉!"))) {
this.tvText.setText(s1);
this.myunlock.setVisibility(8);
return true;
}
}
catch(Exception exception0) {
exception0.printStackTrace();
return false;
}
return false;
}
@ Override// androidx.fragment.app.FragmentActivity
protected void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(layout.activity_main);
TextView textView0 = (TextView)this.findViewById(id.tv_text);
this.tvText = textView0;
textView0.setText(" 吾名玄天帝,昔为诸界之尊,因古诅咒,沉睡亿载。今幸苏醒,欲召百万神兵仙将,复掌万界,重铸天序。此举,需汝解封印,贡力之源。若助吾破诅归位,赐汝万界神尊,封一神域为土,永居众神之巅。");
GestureUnlock gestureUnlock0 = (GestureUnlock)this.findViewById(id.myunlock);
this.myunlock = gestureUnlock0;
gestureUnlock0.setIGestureListener(new IGestureListener() {
@ Override// com.example.gesturelock.IGestureListener
public void isError(String s) {
Log.e("zj595", s);
MainActivity.this.checkPassword(s);
}
@ Override// com.example.gesturelock.IGestureListener
public void isSetUp(String s) {
}
@ Override// com.example.gesturelock.IGestureListener
public void isSuccessful(String s) {
Log.e("zj595", s);
}
});
}
}
```
程序逻辑还是比较明确的,复制一份classes.dex,命名为1.dex,动态加载dex文件中“com.zj.wuaipojie2024_2.C”类中的“isValidate”函数,传入图形密码字符串,读取返回字符串,如果返回字符串以“诶!”开头,则隐藏密码锁,展示该字符串。
接下来分析这个“isValidate”函数,同样转为java方便观看
```java
public static String isValidate(Context context0, String s, int[] arr_v) throws Exception {
try {
return (String)C.getStaticMethod(context0, arr_v, "com.zj.wuaipojie2024_2.A", "d", new Class[]{Context.class, String.class}).invoke(null, context0, s);
}
catch(Exception exception0) {
Log.e("ZJ595", "咦,似乎是坏掉的dex呢!");
exception0.printStackTrace();
return "";
}
}
```
"isValidate"这个函数调用了同文件下的"getStaticMethod"函数
```java
private static Method getStaticMethod(Context context0, int[] arr_v, String s, String s1, Class[] arr_class) throws Exception {
try {
File file0 = C.fix(C.read(context0), arr_v, arr_v, arr_v, context0);
ClassLoader classLoader0 = context0.getClass().getClassLoader();
File file1 = context0.getDir("fixed", 0);
Method method0 = new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass(s).getDeclaredMethod(s1, arr_class);
file0.delete();
new File(file1, file0.getName()).delete();
return method0;
}
catch(Exception exception0) {
exception0.printStackTrace();
return null;
}
}
```
"getStaticMethod""函数又调用了"fix"函数对dex文件进行修复,生成`2.dex`后加载修复后的`com.zj.wuaipojie2024_2.A`类中的`d`函数至method0,**然后删除生成的`2.dex`文件后返回(此处为重点)**
**注:修复前的A类是这样的**
```java
public class A {
private static final String SUCCESS_TAG = "唉!";
public static boolean b() {
return false;
}
public static String c(String s) {
return "?" + s + "?";
}
public static String d(Context context0, String s) {
return "?" + s + "?";
}
}
```
"fix"函数以及与之对应的"read"函数是这样的
```java
private static File fix(ByteBuffer byteBuffer0, int v, int v1, int v2, Context context0) throws Exception {
try {
File file0 = context0.getDir("data", 0);
int v3 = (int)(((Integer)D.getClassDefData(byteBuffer0, v).get("class_data_off")));
HashMap hashMap0 = D.getClassData(byteBuffer0, v3);
((int[][])hashMap0.get("direct_methods")) = v2;
byte[] arr_b = D.encodeClassData(hashMap0);
byteBuffer0.position(v3);
byteBuffer0.put(arr_b);
byteBuffer0.position(0x20);
byte[] arr_b1 = new byte;
byteBuffer0.get(arr_b1);
byte[] arr_b2 = Utils.getSha1(arr_b1);
byteBuffer0.position(12);
byteBuffer0.put(arr_b2);
int v4 = Utils.checksum(byteBuffer0);
byteBuffer0.position(8);
byteBuffer0.putInt(Integer.reverseBytes(v4));
byte[] arr_b3 = byteBuffer0.array();
File file1 = new File(file0, "2.dex");
FileOutputStream fileOutputStream0 = new FileOutputStream(file1);
fileOutputStream0.write(arr_b3);
fileOutputStream0.close();
return file1;
}
catch(Exception exception0) {
exception0.printStackTrace();
return null;
}
}
private static ByteBuffer read(Context context0) {
try {
File file0 = new File(context0.getDir("data", 0), "decode.dex");
if(!file0.exists()) {
return null;
}
FileInputStream fileInputStream0 = new FileInputStream(file0);
byte[] arr_b = new byte;
fileInputStream0.read(arr_b);
ByteBuffer byteBuffer0 = ByteBuffer.wrap(arr_b);
fileInputStream0.close();
return byteBuffer0;
}
catch(Exception unused_ex) {
return null;
}
}
```
看看chatgpt对以上代码的分析:
这段代码是一个Android应用中的一些Java代码,主要涉及到动态加载Dex文件的操作。下面我来逐个分析这几个函数的作用:
1. `fix(ByteBuffer byteBuffer0, int v, int v1, int v2, Context context0)`
- 这个函数的作用是修复一个ByteBuffer中的Dex文件,其中包括了修改Dex文件中的一些数据。
- 参数:
- `ByteBuffer byteBuffer0`:包含Dex文件内容的缓冲区。
- `int v, int v1, int v2`:用于指定要修改的数据的位置和值。
- `Context context0`:上下文对象,用于获取应用程序的目录等信息。
- 主要步骤:
1. 解析Dex文件中的类数据和方法数据。
2. 根据传入的参数修改方法数据中的某个值。
3. 将修改后的数据写回ByteBuffer中。
4. 计算并更新Dex文件的SHA1校验和和校验码。
5. 将修改后的ByteBuffer写入到应用程序的私有目录中,并返回该文件。
2. `getStaticMethod(Context context0, int[] arr_v, String s, String s1, Class[] arr_class)`
- 这个函数的作用是获取一个静态方法。
- 参数:
- `Context context0`:上下文对象。
- `int[] arr_v`:一个整型数组,用于指定修复Dex文件时所需的参数。
- `String s, String s1`:要调用的类名和方法名。
- `Class[] arr_class`:方法参数的类型数组。
- 主要步骤:
1. 调用`fix()`函数修复Dex文件,并获取修复后的文件。
2. 使用修复后的Dex文件创建一个DexClassLoader。
3. 加载指定类名的类,并获取其中声明的静态方法。
4. 删除修复后的文件和其在私有目录中的引用。
5. 返回获取到的静态方法。
3. `isValidate(Context context0, String s, int[] arr_v)`
- 这个函数的作用是验证某个字符串。
- 参数:
- `Context context0`:上下文对象。
- `String s`:要验证的字符串。
- `int[] arr_v`:一个整型数组,用于指定修复Dex文件时所需的参数。
- 返回值:返回验证结果的字符串。
- 主要步骤:
1. 调用`getStaticMethod()`函数获取指定类的静态方法。
2. 调用获取到的静态方法来进行验证,并返回结果。
4. `read(Context context0)`
- 这个函数的作用是读取Dex文件。
- 参数:
- `Context context0`:上下文对象。
- 返回值:返回读取到的Dex文件的ByteBuffer。
- 主要步骤:
1. 构造Dex文件的路径。
2. 读取Dex文件内容到一个字节数组中。
3. 将字节数组包装成ByteBuffer,并返回。
这些函数主要用于动态加载并修复Dex文件,然后执行其中的方法来实现某些功能。
### 思路
目前flag的关键在于两点,一个是图形锁代表的字符串s,一个是修复dex以后两个"?"的内容,
### 动态分析&dex修复\*3
#### 修复1
静态分析结束,下面用010Editor打开classes.dex,图中可以明显看到dex的Adler32校验值和sha1校验值是错误的
也就是说附件里的classes.dex文件是损坏的
**dex校验值损坏后,在反射加载类过程中会被系统拒绝,因此必须进行修复操作**
读取logcat可见如下错误
> E System : Unable to Load dex file: /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex
>
> E System : java.io.IOException: Failed to open dex files : from /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex because: Failure to verify dex file ' /data/user/0/com.zj.wuaipojie2024_2/app_data/1.dex': Bad Checksum (c607ea12, expected 22dcea4c)
可见系统确实因校验值异常,拒绝加载了assets下复制的classes.dex
![](https://imgsrc.baidu.com/forum/pic/item/0bd162d9f2d3572c6594c4e5cc13632762d0c391.png)
由于dex文件在010Editor中除了头部外并无明显异常情况,因此我们使用(https://github.com/luoyesiqiu/DexRepair)对dex头部进行修复:`java -jar DexRepair.jar /path/to/dex`
修复后头部变为全绿:
![](https://imgsrc.baidu.com/forum/pic/item/30adcbef76094b366d7e40d4e5cc7cd98d109d1f.png)
将修复完的dex命名为`classes.dex`,置入assets文件夹,重新打包、签名
打开jeb调试,发现依然无法正常运行
#### 修复2
单步调试,定位到错误发生点,发现是程序在
```java
try {
File file0 = new File(context0.getDir("data", 0), "decode.dex");
if(!file0.exists()) {
return null;
}
}
```
处返回了null,即这个file0,也就是`decode.dex`不存在
然后程序跳转到了
```java
catch(Exception exception0) {
Log.e("ZJ595", "咦,似乎是坏掉的dex呢!");
exception0.printStackTrace();
return "";
}
```
打开`adb logcat`工具可见返回的错误信息:
> E ZJ595 咦,似乎是坏掉的dex呢!
可见确实是文件不存在的原因导致程序运行异常
不存在我们就手动新建一个,为了方便起见我就直接复制了一份1.dex,将其改名为decode.dex,使用RE文件管理器放置于与1.dex同目录下(即`data/user/0/com.zj.wuaipojie2024_2/app_data`),这样这部分代码就能正常跑起来了
根据"静态分析"部分代码分析,由于`2.dex`文件在写入、加载后即会进行自我删除,于是需要在删除代码位置下断点以保存该中间文件进行分析,断点位置如下:
```smali
.method private static varargs getStaticMethod(Context, [I, String, String, [Class)Method
.registers 11
.annotation system Signature
value = {
"(",
"Landroid/content/Context;",
"[I",
"Ljava/lang/String;",
"Ljava/lang/String;",
"[",
"Ljava/lang/Class<",
"*>;)",
"Ljava/lang/reflect/Method;"
}
.end annotation
.annotation system Throws
value = {
Exception
}
.end annotation
00000000const/4 v0, 0
:try_2
00000002invoke-static C->read(Context)ByteBuffer, p0
00000008move-result-objectv1
0000000Aconst/4 v2, 0
0000000Caget v3, p1, v2
00000010const/4 v4, 1
00000012aget v4, p1, v4
00000016const/4 v5, 2
00000018aget p1, p1, v5
0000001Cinvoke-static C->fix(ByteBuffer, I, I, I, Context)File, v1, v3, v4, p1, p0
00000022move-result-objectp1
00000024invoke-virtual Object->getClass()Class, p0
0000002Amove-result-objectv1
0000002Cinvoke-virtual Class->getClassLoader()ClassLoader, v1
00000032move-result-objectv1
00000034const-string v3, "fixed"
00000038invoke-virtual Context->getDir(String, I)File, p0, v3, v2
0000003Emove-result-objectp0
00000040new-instance v2, DexClassLoader
00000044invoke-virtual File->getAbsolutePath()String, p1
0000004Amove-result-objectv3
0000004Cinvoke-virtual File->getAbsolutePath()String, p0
00000052move-result-objectv4
00000054invoke-direct DexClassLoader-><init>(String, String, String, ClassLoader)V, v2, v3, v4, v0, v1
0000005Ainvoke-virtual DexClassLoader->loadClass(String)Class, v2, p2
00000060move-result-objectp2
00000062invoke-virtual Class->getDeclaredMethod(String, [Class)Method, p2, p3, p4
00000068move-result-objectp2
0000006Ainvoke-virtual File->delete()Z, p1#在此处下断点,防止文件删除
00000070new-instance p3, File
00000074invoke-virtual File->getName()String, p1
0000007Amove-result-objectp1
0000007Cinvoke-direct File-><init>(File, String)V, p3, p0, p1
00000082invoke-virtual File->delete()Z, p3
.catch Exception {:try_2 .. :tryend_88} :catch_8A
:tryend_88
00000088return-object p2
:catch_8A# used for: Ljava/lang/Exception;
0000008Amove-exception p0
0000008Cinvoke-virtual Exception->printStackTrace()V, p0
00000092return-object v0
.end method
```
然后在`data/user/0/com.zj.wuaipojie2024_2/app_data`下即可找到修复后的`2.dex`文件
修复后的`2.dex`中的A类如下:
```java
package com.zj.wuaipojie2024_2;
import android.content.Context;
public class A {
private static final String SUCCESS_TAG = "唉!";
public static boolean b() {
return false;
}
public static String c(String s) {
return "?" + s + "?";
}
public static String d(Context context0, String s) {
MainActivity.sSS(s);
String s1 = Utils.getSignInfo(context0);
if(s1 != null && (s1.equals("fe4f4cec5de8e8cf2fca60a4e61f67bcd3036117"))) {
StringBuffer stringBuffer0 = new StringBuffer();
for(int v = 0; stringBuffer0.length() < 9 && v < 40; ++v) {
String s2 = "0485312670fb07047ebd2f19b91e1c5f".substring(v, v + 1);
if(!stringBuffer0.toString().contains(s2)) {
stringBuffer0.append(s2);
}
}
return s.equals(stringBuffer0.toString().toUpperCase()) ? "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d" : "";
}
return "";
}
}
```
此处的A.d函数给出了密码`s`的真值以及一个提示
写个小程序获取解锁密码`s`:
```java
import java.io.*;
public class GetPass {
public static void main(String[] args) {
StringBuffer stringBuffer0 = new StringBuffer();
for(int v = 0; stringBuffer0.length() < 9 && v < 40; ++v) {
String s2 = "0485312670fb07047ebd2f19b91e1c5f".substring(v, v + 1);
if(!stringBuffer0.toString().contains(s2)) {
stringBuffer0.append(s2);
}
}
System.out.println(stringBuffer0.toString().toUpperCase());
}
}
```
输出为:
> 048531267 (九宫格密码的真值,后续会用到)
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc37931342bc610adc4b74543a91143.png)
下面来看看提示:
> "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d"
找到B类......
(怎么还是讨厌的"?"+s+"?")
```java
public class B {
public static String d(String s) {
return "?" + s + "?";
}
}
```
看来这个B.d还是坏的,还得修复一次
#### 修复3
原先程序里的那个fix函数只能修复A.d部分,接下来修复B.d
回到MainActivity,还是定位到这句
``` java
String s1 = (String)new DexClassLoader(file0.getAbsolutePath(), file1.getAbsolutePath(), null, classLoader0).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, s, this.getResources().getIntArray(array.A_offset));
```
语句中出现了A_offset,说明是修复A类型的,定位到array.A_offset这个变量
```java
public static final class array {
public static int A_offset = 0x7F030000;// array:A_offset
public static int B_offset = 0x7F030001;// array:B_offset
}
````
(诶,A_offset下面那个B_offset是啥?这不是我们要找的“B类”的偏移嘛,这下不用手算了)
直接jeb跑起来,在A_offeset处下断点,把A_offset的值换成B_offset
```smali
000000E4sget v3, R$array->A_offset:I#此处下断点,修改Locals中v3的值为B_offset,即将其改为7F30001h,如图
000000E8invoke-virtual Resources->getIntArray(I)[I, p1, v3
```
![](https://imgsrc.baidu.com/forum/pic/item/8cb1cb1349540923360d2647d458d109b3de49a7.png)
运行程序,断点至本文"修复2"部分提及的删除函数,得到B.d修复后的`2_new.dex`(此处为了避免与上部分中2.dex混淆,将其命名为2_new.dex,实际由程序生成的文件名仍为2.dex)
打开2_new.dex,反编译得到其中的B类,定位至B.d
```java
public class B {
public static String d(String s) {
return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
}
}
```
> **Flag:"{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}"**
> 其中的password上面已经提到了,是"048531267"
到此,这个CM终于完事了
**此处特别感谢 正己 佬提供的题目以及指点**
### 附——Utils中的两个算法(由smali代码反编译得到,无法直接运行):
```java
public static byte[] getSha1(byte[] arr_b) {
try {
return MessageDigest.getInstance("SHA").digest(arr_b);
}
catch(Exception unused_ex) {
return null;
}
}
public static String md5(byte[] arr_b) {
int v;
try {
String s = new BigInteger(1, MessageDigest.getInstance("md5").digest(arr_b)).toString(16);
v = 0;
while(true) {
label_2:
if(v >= 0x20 - s.length()) {
return s;
}
s = "0" + s;
break;
}
}
catch(NoSuchAlgorithmException unused_ex) {
throw new RuntimeException("ops!!");
}
++v;
goto label_2;
}
```
### 写个小程序计算一下flag(注册机)
```java
import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Scanner;
public class GetFlag {
public static byte[] getSha1(byte[] arr_b) {
try {
return MessageDigest.getInstance("SHA").digest(arr_b);
}
catch(Exception unused_ex) {
return null;
}
}
public static String md5(byte[] arr_b) {
try {
String s = new BigInteger(1, MessageDigest.getInstance("md5").digest(arr_b)).toString(16);
return s;
}
catch(NoSuchAlgorithmException unused_ex) {
throw new RuntimeException("ops!!");
}
}
public static void main(String args[]) {
try (Scanner scanner = new Scanner(System.in)) {
System.out.println("Please Input Your UID:");
String s = "048531267" + scanner.next();
System.out.println("Flag:" + "{" + GetFlag.md5(GetFlag.getSha1(s.getBytes())) + "}" );
}
}
}
```
![](https://imgsrc.baidu.com/forum/pic/item/a9d3fd1f4134970acacc5016d3cad1c8a7865d48.png)
## 总结
正如开头所说,这是我第一次参与红包活动,也是第一次接触安卓类的逆向
因文章篇幅所限,一些细节的内容(比如环境配置、设置apk为可调试等)不能详尽地涉及,如果有对以上部分有疑问和建议也欢迎各位在楼下交流讨论
总体而言这几道题目相对来说难度还行(主要还是我太菜了)
# Web部分
### 前言
好家伙,这下Web题越来越像猜灯谜了,而且还是眼睛有点酸的那种......
(说的就是flag1和3,愿称之为opencv独家赞助伙伴【bushi】)
### 题目简介
活动地址:(https://www.52pojie.cn/thread-1889163-1-1.html)
解题线索视频:(https://www.bilibili.com/video/BV1ap421R7VS)
题目共包含 12 个静态 flag: flag1~flag12,另外还需要寻找到 3 个动态 flag: flagA~flagC,每个难度需提交对应的4个静态flag和1个动态flag
### 准备工作
本次的视频隐藏的信息有点复杂,有两个flag都需要逐帧分析,因此我们先用ffmpeg工具把视频转换为图帧
(我没有阿B的大会员,下载的视频是30fps的,因此r的参数是30,若下载的是60fps的,只要调整以下命令中r的参数为60就行了)
`ffmpeg -i inputfile.mp4 -r 30 ./images/%1d.jpg`
得到一文件夹的图片:
![]( https://imgsrc.baidu.com/forum/pic/item/91ef76c6a7efce1b386d5ff3e951f3deb48f6541.png)
接下来就是处理那个切成了四段的二维码,
认真看的小伙伴应该已经发现了,这个二维码的几个帧拼在一起就是一个完整的二维码
使用修图软件框选太麻烦了,有的二维码部分甚至和文字的白色部分连在了一起,
既然我对cv领域比较熟悉,因此我还是用opencv叠加吧
下面是一个用python写的叠加小程序:
```python
import cv2
import os
def add_images_in_folder(folder_path):
# Get a list of all files in the folder
image_files =
if len(image_files) == 0:
print("No JPG images found in the folder.")
return None
# Read the first image to initialize the accumulator
result = cv2.imread(os.path.join(folder_path, image_files))
# Loop through the rest of the images and add them to the accumulator
for image_file in image_files:
image_path = os.path.join(folder_path, image_file)
image = cv2.imread(image_path)
result = cv2.add(result, image)
return result
folder_path = "./images"
result_image = add_images_in_folder(folder_path)
if result_image is not None:
cv2.imwrite("./result.png",result_image)
```
手动处理去除文字部分,得到完整的二维码如图:
![](https://imgsrc.baidu.com/forum/pic/item/1c950a7b02087bf431a6c0d0b4d3572c11dfcf45.png)
解码得到网址:https://2024challenge.52pojie.cn/
### Flag1
视频中出现52pojie四个字的时候,后面点阵散开处点阵缺少了一些点,而这些缺少的点就隐藏了flag1,我们用准备部分提到的“叠加小程序”对此部分帧图片进行叠加,得到可见flag
(这个flag也可以直接看,就是有点费眼【doge】)
拼接结果:![](https://imgsrc.baidu.com/forum/pic/item/d6ca7bcb0a46f21fe230c96ab0246b600c33ae87.png)
> **flag1{52pj2024}**
### Flag2
参考去年的[官方题解](https://www.52pojie.cn/thread-1742787-1-1.html)中flag2部分解释:
> 因为页面会自动重定向,我本来想将 X-Dynamic-Flag: flagA{Header X-52PoJie-Uid Not Found} 藏在这个重定向之前的页面的,但是我怕藏得太深了,没这么搞。
今年的flag2果然藏在了重定向页面前......
访问上述二维码指向的的[网页](https://2024challenge.52pojie.cn/)会产生重定向,打开F12打开控制台后重新访问该链接即可看到X-Flag2:
![](https://imgsrc.baidu.com/forum/pic/item/8cb1cb13495409236527f547d458d109b3de49b9.png)
> **flag2{xHOpRP}**
### Flag3
视频开头那段东西就是,一看到就想到了这个[视频](https://www.bilibili.com/video/BV1GM4y1b7JB/),
二值杂色视觉暂留效应嘛,“二值杂色”+“视觉暂留”。
前者是指把一个复杂的图像,按照灰度不同去四舍五入为黑与白两种噪点。后者则意味着需要借助你眼睛与脑子的时间差,去串联起前后的噪点位置的变化,让你的脑子中形成轨迹图片。
这玩意常用在验证码上,用来过AI的,所以只能人工看了。
> **flag3{GRsgk2}**
### Flag4
打开上述提到的[网址](https://2024challenge.52pojie.cn),F12,查看网络,会发现有一个文件名叫做`flag4_flag10.png`的空白图片作为背景(实际上是透明的,浏览器看不出而已)
```css
body {
margin: 0;
padding: 0;
background: url("flag4_flag10.png") white center center no-repeat;
background-size: contain;
height: 100vh;
overflow: hidden;
}
```
![](https://imgsrc.baidu.com/forum/pic/item/7e3e6709c93d70cfede23c34bedcd100baa12beb.png)
下载下来使用图片应用打开即可看到flag4;
![](https://imgsrc.baidu.com/forum/pic/item/b8014a90f603738db4569a1bf51bb051f819ecef.png)
> **flag4{YvJZNS}**
### FlagA
同样是开上述提到的[网址](https://2024challenge.52pojie.cn),F12,查看网络,输入uid登录,会发现有一个叫做`login`的网页写入了cookie信息
而cookie信息中提到了flagA
![](https://imgsrc.baidu.com/forum/pic/item/1c950a7b02087bf43097c1d0b4d3572c11dfcf94.png)
`flagA=guFOgjwXg5haqETMpDMLPyHfY7sP5sf32rW7l3XtVr+9T+LyBKQhmslLNA==; expires=Sat, 17 Feb 2024 06:00:00 GMT; path=/; SameSite=Lax`
看起来flagA被加密了,而且不是base64
但与此同时,uid好像也采用了类似的加密方式,而且网址里好像有个script API用来把cookie转换为uid(嗯?)
```js
fetch('/auth/uid').then(res => res.text()).then(res => {
if (res) {
document.querySelector('#uid').textContent = res;
document.querySelector('#logout-form').style.display = '';
document.querySelector('#login-form').style.display = 'none';
}
});
```
那么就好办了,我们来**偷梁换柱**一下,把cookie中的flagA设置到uid中,再fetch......
![](https://imgsrc.baidu.com/forum/pic/item/a686c9177f3e67091d90d3c67dc79f3df8dc5573.png)
> **flagA{f96a1e5e}**
**注:flagA每10分钟刷新**
### Flag5
还是那个网页,F12,网页里用注释提到了Flag5
![](https://imgsrc.baidu.com/forum/pic/item/d1a20cf431adcbeffccae83feaaf2edda3cc9f09.png)
我们把style中的属性全部去掉,得到以下一串东西(用图片了,直接发论坛MD渲染会崩):
![](https://imgsrc.baidu.com/forum/pic/item/79f0f736afc379311a8c1010adc4b74543a91126.png)
我们暂且不管` . _ / \ `那一堆符号(flag9的地方会说),剩下的就是flag5
> **flag5{P3prqF}**
### Flag6
还是那个网页,下方有个flag6的按钮,点击进入flag6
网页很干净,就一个按钮,点了就开始炼丹,电脑风扇呼呼响(doge)
![](https://imgsrc.baidu.com/forum/pic/item/4d086e061d950a7b3aecd8fd4cd162d9f2d3c92a.png)
还是来看看源码吧:
```javascript
document.querySelector("button").addEventListener("click", () => {
const t0 = Date.now();
for (let i = 0; i < 1e8; i++) {
if ((i & 0x1ffff) === 0x1ffff) {
const progress = i / 1e8;
const t = Date.now() - t0;
console.log(
`${(progress * 100).toFixed(2)}% ${Math.floor(
t / 1000
)}s ETA:${Math.floor(t / progress / 1000)}s`
);
}
if (MD5(String(i)) === "1c450bbafad15ad87c32831fa1a616fc") {
document.querySelector("#result").textContent = `flag6{${i}}`;
break;
}
}
});
```
简而言之就是它跑了一个从0到$10^8 - 1$的数字字符串i,当该字符串的md5为`1c450bbafad15ad87c32831fa1a616fc`时,输出`flag6{${i}}`,否则在console中定期输出计算进度
(好家伙,暴力破解md5,真有你的)
直接md5彩虹表反查,发现是今天的日期,绝了......
![](https://imgsrc.baidu.com/forum/pic/item/7aec54e736d12f2e20cc385909c2d5628535683a.png)
> **flag6{20240217}**
### Flag7
作者在视频里面留下了一个(https://github.com/ganlvtech/52pojie-2024-challenge),打开发现这个:
![](https://imgsrc.baidu.com/forum/pic/item/d4628535e5dde711316bd4cfe1efce1b9d16614a.png)
> "删除不小心提交的flag内容"
提示够明显了,我们直接点击commit寻找历史提交记录,找到了这个
![](https://imgsrc.baidu.com/forum/pic/item/0eb30f2442a7d933c91d734aeb4bd11373f00155.png)
> **flag7{Djl9NQ}**
### Flag8 & FlagB
2048小游戏
![](https://imgsrc.baidu.com/forum/pic/item/dc54564e9258d10908a7388b9758ccbf6c814d47.png)
首先肯定是玩咯,轻轻松松通过玩游戏顺利拿到 flag8
![](https://imgsrc.baidu.com/forum/pic/item/f636afc379310a55a3b04fcdf14543a982261076.png)
> **flag8{OaOjIK}**
接着拿剩下的金币V了作者50(doge)
> 竟然真的有人v我50,真的太感动了。作为奖励呢,我就提示你一下吧,关键词是“溢出”。
首先想到的肯定是多买点,然后让它溢出,可惜有可能弄得太猛了,导致溢出后金币数量增加了,作者也想到了这个,购买请求直接被拦截了
![](https://imgsrc.baidu.com/forum/pic/item/4afbfbedab64034f7341533fe9c379310a551d7e.png)
猜想是做了检验,即购买后金币数量不能高于现有数量。
手算了几个临界值,罢了,完全没用,
因为题目没有写明白它后端到底用的啥数据类型,因此放弃思考,直接用request组件爆破
```python
# 导入requests模块
import requests
for i in :
print(i)
# 请求的url地址
url = 'https://2024challenge.52pojie.cn/flagB/buy_item'
# 请求头
headers = {"content-type":"application/x-www-form-urlencoded","cookie":"Hm_lvt_46d556462595ed05e05f009cdafff31a=1707280828,1707352290,1707440981,1708065094; wzws_sessionid=gmY5MmRiY4AxODMuMTkzLjE1My4yMjCBMTNmOWIzoGXQNBg=; guFOgjwXg5haqETMpDMLPyHfY7sP5sf32rW7l3XtVr+9T+LyBKQhmslLNA==; uid=BTtCuUGDQGSkBsn/UatmT1VT4wNkVf1j4O5UsVxg9yguZA==; game2048_user_data=I1xnNzcQVLZgwF2jXweH+0MFEE3RglZSqpAhElrNkr5VWSjGb885YMYIqMyGAZJGqCFvZ1oCV50LnAJbBvQuPLM0deHxcni4v3dvVKohNEaWNui6WbpPusQ2ff13MWv7wkO1jX/cfa0fZQOJK7UtfQvrUlJD+1GqDCYs7TCYLLEtrObxDt74D2Jswg4ViV9/1o5HHtDI"}
# payload 为传入的参数
payload = {"shop_item_id":5,"buy_count":i}
# json形式,参数用json
res = requests.post(url,data=payload,headers=headers)
print(res.text)
```
运气还不错,跑到2^62 = 4611686018427387904时返回值为`{"code":0,"msg":"OK"}`,也就是说买4611686018427387904个flagB时符合要求
(其他的要不返回`{"code":1,"msg":"购买商品之后钱怎么还变多了?不知道出什么 bug 了,暂时先拦一下 ^_^"}`,要不返回`{"code":1,"msg":"钱不够"}`
后来尝试发现此时并未扣除任何金币,*猜测此时乘上任何单价都会溢出,溢出后花销值变为0*
![](https://imgsrc.baidu.com/forum/pic/item/43a7d933c895d143522d771a35f082025aaf07b4.png)
> **flagB{2a3ec954} 过期时间: 2024-02-17 12:10:00**
### Flag9
之前说到的那一串符号,调节窗口大小,即可看到立体的Flag9
![](https://imgsrc.baidu.com/forum/pic/item/a8014c086e061d953eb7a4013df40ad162d9ca12.png)
> **flag9{KHTALK}**
### Flag10
和Flag4是一张图片,图片misc类嘛,首先用(https://github.com/ReFirmLabs/binwalk)看看是不是有什么不对劲的地方。
好像还挺正常的,没有隐藏压缩包也没有藏图
![](https://imgsrc.baidu.com/forum/pic/item/d52a2834349b033b387865e353ce36d3d539bd13.png)
**小插曲,这边diss一波edge浏览器,打开图片链接他会自动给你跳到它这个“边缘图像查看器”,即使你选择其中的“另存为”功能保存图片,下载到的图片也是被处理过的,隐写数据就丢了!!**
(“强大”???)
![](https://imgsrc.baidu.com/forum/pic/item/83025aafa40f4bfbfb22076d454f78f0f736181c.png)
**所以,做这题时 千 万 不 要 用edge下载图片!!**
![两个文件的对比图](https://imgsrc.baidu.com/forum/pic/item/faf2b2119313b07ebe50c9314ad7912397dd8c1e.png)
发现这个问题以后,直接用curl命令下载图片`curl -l https://2024challenge.52pojie.cn/flag4_flag10.png -o ./flag4_flag10.png`
接下来就简单了,用(https://github.com/Giotino/stegsolve/releases)中的Analyse-Stereogram Solver(立体视图)工具,设置偏移量为1或2即可看到隐藏的flag10
上图是curl下载图片(含隐写信息);下图是edge下载图片(无隐写信息)
!(https://imgsrc.baidu.com/forum/pic/item/bd3eb13533fa828b18cf04dabb1f4134970a5a1f.png)
!(https://imgsrc.baidu.com/forum/pic/item/0dd7912397dda144d7e7f58bf4b7d0a20cf48619.png)
> **flag10{6BxMkW}**
### Flag11
拼图游戏,修改html:root中的css属性`--var1`和`--var2`,复原图片即可【两个变量的值分别为71和20】
(小技巧,使用鼠标滚轮滚动参数,使图片块往聚合方向运动就没啥问题了)
![](https://imgsrc.baidu.com/forum/pic/item/e824b899a9014c0842eab19c4c7b02087bf4f432.png)
> **flag11{HPQfVF}**
### Flag12
WebAssembly(Wasm)技术,直接看关键部分
```wasm
(func $get_flag12 (;0;) (export "get_flag12") (param $var0 i32) (result i32)
i32.const 1213159497
i32.const 0
local.get $var0
i32.const 1103515245
i32.mul
i32.const 1
i32.eq
select
)
```
用chatgpt解释一下代码:
这段WebAssembly(Wasm)代码定义了一个名为get_flag12的函数,该函数接受一个32位整数作为参数\$var0,并返回一个32位整数作为结果。这个函数的作用是检查传入的参数是否与特定值相关联,如果是则返回1,否则返回0。
具体而言,代码执行以下步骤:
1. i32.const 1213159497:将值1213159497(0x483CEEE9)压入堆栈。
2. i32.const 0:将值0压入堆栈。
3. local.get \\$var0:将函数参数\$var0的值压入堆栈。
4. i32.const 1103515245:将值1103515245(0x41C64E6D)压入堆栈。
5. i32.mul:将栈顶两个值相乘。
6. i32.const 1:将值1压入堆栈。
7. i32.eq:比较栈顶两个值是否相等,如果相等则将1压入堆栈,否则将0压入堆栈。
8. select:根据栈顶的布尔值选择两个值中的一个放回栈顶。
因此,这段代码的主要目的是将参数\$var0与特定的数值进行处理,并根据结果返回1或0。
又是溢出问题,即1103515245*`输入值` = 1,那么这个输入值一定很大(超出上限),
这边提到了i32,即32位整型数据,最大值为2^31-1 = 2147483647
写个C语言程序跑一下
```cpp
#include <bits/stdc++.h>
#include <iostream>
using namespace std;
int main()
{
for(long j = 0; j<= 4294967295; j++){
int i = 1103515245;
if(i*j == 1) {
cout<<j<<endl;
break;
}
}
return 0;
}
```
嗯?结果跑出来个负数?不管了,填进去。
> -289805467
这也能出答案是我没想到的......看来是满足条件就能出......
![](https://imgsrc.baidu.com/forum/pic/item/10dfa9ec8a1363275c94500ad78fa0ec08fac78a.png)
> **flag12{HOXI}**
### FlagC
好家伙,直接内嵌TF.js跑Yolo目标检测模型了(不过看起来好像是yolov5n,没上v8不是很认可【doge】)......
当小游戏直接玩吧,它都直接告诉你那些正确了,调整一下位置即可。
![](https://im.gurl.eu.org/file/03c67b7a8792e3861e41c.png)
我搭出的阵:
![](https://imgsrc.baidu.com/forum/pic/item/72f082025aafa40fb43357e4ed64034f78f01916.png)
下面来简单看一下这个网页的逻辑:
```javascript
fetch('/flagC/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
boxes,
scores,
classes,
}),
credentials: "include",
}).then((res) => res.json()).then((res) => {
const {hint, labels, colors} = res;
document.querySelector('#result').textContent = hint; // 错误时显示提示,正确时显示 flag
})
```
简而言之就是调用TF.js(Tensorflow,Google开发的一款深度学习框架)加载yolo目标检测模型,对上传的图片执行本地目标检测
然后将检测结果(boxes->【目标框】,scores【置信度】,classes【分类标签】)以POST形式传递至后端,接受后端返回的提示,并在图中框出来
emmm,这个玩玩就好了,修改也没啥意思,因为判断逻辑在后端,程序能帮你做的也只能是根据提示不断修改,意义不大(doge)
> **flagC{ce92f978} 过期时间: 2024-02-18 10:10:00**
cattie牛批! PRETEXT 发表于 2024-2-25 21:00
有什么高级工具,大佬推荐下
看我课程里的 多谢大佬的分析教程 大佬带带安卓高级的{:1_918:} 本帖最后由 cattie 于 2024-2-25 16:53 编辑
debug_cat 发表于 2024-2-25 09:17
大佬带带安卓高级的
后面的高级题太菜了不会做
最主要后来没时间分析了{:301_1004:}有空一定玩玩
@正己 cattie 发表于 2024-2-25 09:27
最主要后来没时间分析了有空一定玩玩
高级题没头绪,头大:'(weeqw 修改、编译一气呵成(注意这时不要签名,因为flag在签名里面),直接核心破解安装即可,发现此时软件已经变成1秒增加1抽了(doge)
大佬,直接核心破解安装是咋弄的 本帖最后由 cattie 于 2024-2-25 12:41 编辑
长得帅活得久 发表于 2024-2-25 12:32
大佬,直接核心破解安装是咋弄的
lucky patcher里面去除签名校验,
然后系统安装相应的核心破解模块(lsp,禁用安卓的签名检验)
参考这个:https://zhuanlan.zhihu.com/p/654235511 两个Android初级题,简单看了一下代码,发现一个更bug的
初级题一:直接解压apk,找到ys.mp4文件,放入Winhex中查看,直接滑到最下面,就是flag
初级题二:用objection ,查看当前Activity列表,发现有个FlagActivity,直接启动flagActivity就行了 大佬可以看看新工具,Lucky Patcher应该没有那么好用 正己 发表于 2024-2-25 17:04
大佬可以看看新工具,Lucky Patcher应该没有那么好用
嗯,因为模拟器里面有这个所以直接用了,后来我就用别的打测试签名了。