Hgame-re-week2

发布于 2024-02-27  171 次阅读


babyre:

首先用die查一下壳:

发现是64位的,用64位的ida打开看一下,找到main函数的位置:

可以看见里面是这样的

我们可以发现sub_1708(a1, a2, a3);这个函数里面是这个样子的:

所以说我们输入进去的flag是32位的,我们再返回看main函数,里面的dword_40A0,按x查看可以知道里面的初始值被替换成了feifei

然后再进行了一段看似是6位的异或加密(这里面有陷阱一会再来细说)

这里面这四个都是加密过程

然后函数 sub_1803();便是flag的判断函数

其中dword_4020可以用lazyida的插件提取出来:unsigned int dword_4020[32] = {
   0x00002F14, 0x0000004E, 0x00004FF3, 0x0000006D, 0x000032D8, 0x0000006D, 0x00006B4B, 0xFFFFFF92,
   0x0000264F, 0x0000005B, 0x000052FB, 0xFFFFFF9C, 0x00002B71, 0x00000014, 0x00002A6F, 0xFFFFFF95,
   0x000028FA, 0x0000001D, 0x00002989, 0xFFFFFF9B, 0x000028B4, 0x0000004E, 0x00004506, 0xFFFFFFDA,
   0x0000177B, 0xFFFFFFFC, 0x000040CE, 0x0000007D, 0x000029E3, 0x0000000F, 0x00001F11, 0x000000FF
};

然后就是对于加密过程的一个分析了:

使用linux虚拟机进行远程调试:

我们可以看到,那一段静态分析出来的6位加密实际上只加密了前三位

然后就是那一段四个线程对flag逐步加密的过程

是通过信号量机制实现的线程同步

信号量的规则是

  • 如果当前资源计数器大于0,那么信号量处于触发状态
  • 如果当前资源计数器等于0,那么信号量处于未触发状态
  • 系统不会让当前资源计数器变为负数
  • 当前资源计数器决定不会大于最大资源计数

信号量机制

打个比方就是有三个位置但是有五个人想进去那么就只放三个人先进去剩下的在外面等,直到里面有人离开那么就把外面的一个放进去,以此类推。

调试的过程可以知道是循环了32次,因此可以写脚本:

#include <stdio.h>
char key[] = {119, 116, 120, 102, 101, 105};
int main(){
int enc[33] = {12052, 78, 20467, 109, 13016, 109, 27467, -110, 9807, 91,
21243, -100, 11121, 20, 10863, -107, 10490, 29, 10633, -101, 10420, 78, 17670,
-38, 6011, -4, 16590, 125, 10723, 15, 7953, 255, 250};
for (int i = 28; i >= 0; i -= 4) {
enc[i + 3] = enc[i + 3] ^ (enc[i + 4] - key[(i + 4) % 6]);
enc[i + 2] = enc[i + 2] / (enc[i + 3] + key[(i + 3) % 6]);
enc[i + 1] = enc[i + 1] + (enc[i + 2] ^ key[(i + 2) % 6]);
enc[i + 0] = enc[i + 0] - (enc[i + 1] * key[(i + 1) % 6]);
}
for (int i = 0; i < 32; i++) {
printf("%c", enc[i]);
}
}

key为什么只执行了三次异或的原因是在这个地方:

这里可以看见如果等于零的话会执行除零的操作所以会退出

ezcpp:

首先查一下壳发现是64位的,用64位的ida打开:

点开函数 sub_140001070(v8);可以发现,里面其实是一个tea加密,而且只对数据的前几位进行了加密

#include<bits/stdc++.h>
using namespace std;

void decrypt(uint32_t* v, uint32_t* k)
{
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0xdeadbeef, sum = delta * 32;
for (int i = 0; i < 32; i++)
{
v1 -= (v0 + sum) ^ (k[2] + (v0 << 4)) ^ (k[3] + (v0 << 5));
v0 -= (v1 + sum) ^ (k[0] + (v1 << 4)) ^ (k[1] + (v1 << 5));
sum -= delta;
}
v[0] = v0, v[1] = v1;
15}

int main()
{
uint32_t key[] =
{
1234,
2341,
3412,
4123
};
unsigned char cipher[] = {0x88, 0x04, 0xC6, 0x6A, 0x7F, 0xA7, 0xEC, 0x27,
0x6E, 0xBF,
0xB8, 0xAA, 0x0D, 0x3A, 0xAD, 0xE7, 0x7E, 0x52, 0xFF, 0x8C,
0x8B, 0xEF, 0x11, 0x9C, 0x3D, 0xC3, 0xEA, 0xFD, 0x23, 0x1F,
0x71, 0x4D};
decrypt((uint32_t*)&cipher[24], key);
decrypt((uint32_t*)&cipher[16], key);
decrypt((uint32_t*)&cipher[8], key);
decrypt((uint32_t*)&cipher[0], key);
printf("%s", cipher);
}

key和加密的数据可以很轻松的找到

babyAndroid:

用jdax打开,找到mainactivity的位置:

可以看见check1是java层中的:

package com.feifei.babyandroid;

import java.util.Arrays;

/* loaded from: classes.dex */
public class Check1 {

   /* renamed from: S */
   private byte[] f104S = new byte[256];

   /* renamed from: i */
   private int f105i;

   /* renamed from: j */
   private int f106j;

   public Check1(byte[] bArr) {
       for (int i = 0; i < 256; i++) {
           this.f104S[i] = (byte) i;
      }
       int i2 = 0;
       for (int i3 = 0; i3 < 256; i3++) {
           byte[] bArr2 = this.f104S;
           i2 = (i2 + bArr2[i3] + bArr[i3 % bArr.length]) & 255;
           swap(bArr2, i3, i2);
      }
       this.f105i = 0;
       this.f106j = 0;
  }

   private void swap(byte[] bArr, int i, int i2) {
       byte b = bArr[i];
       bArr[i] = bArr[i2];
       bArr[i2] = b;
  }

   public byte[] encrypt(byte[] bArr) {
       byte[] bArr2 = new byte[bArr.length];
       for (int i = 0; i < bArr.length; i++) {
           int i2 = (this.f105i + 1) & 255;
           this.f105i = i2;
           int i3 = this.f106j;
           byte[] bArr3 = this.f104S;
           int i4 = (i3 + bArr3[i2]) & 255;
           this.f106j = i4;
           swap(bArr3, i2, i4);
           byte[] bArr4 = this.f104S;
           bArr2[i] = (byte) (bArr4[(bArr4[this.f105i] + bArr4[this.f106j]) & 255] ^ bArr[i]);
      }
       return bArr2;
  }

   public boolean check(byte[] bArr) {
       return Arrays.equals(new byte[]{-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72}, encrypt(bArr));
  }
}

而check2是native层的:

可以发现check1是rc4加密,要先找到key只需要在这个位置寻找就可以了:

rc4密文里面有负数,将其转化为十六进制之后再用工具解密

把文件重新命名后缀为zip文件然后解压把libbabyandroid.so文件拖入ida里面

可以看到这里check2函数和sub_BF0函数有联系,打开

可以看到其实这里是AES加密

密文是:

key就是上面java层计算出来的结果,在网上直接找工具就可以计算出来了

arithmetic:

用die查壳可发现,有upx壳:

当我直接使用upx脱壳的时候出现了报错:

那么我们就可以猜测这个upx是非标准形式的,我们用010打开来看一下:

可以发现,这里的区段名被修改过,应该是UPX才对,所以我们把ari改为UPX就行了:

这样就可以使用UPX -d来去壳了()

(upx防脱壳文章https://www.52pojie.cn/thread-326995-1-1.html

放入ida可以看到,程序从out里卖弄读取了数据:

那么我们用010看一下out里面的数据是什么样子的:

可以看见是三角形的数据

根据代码可以看到,逻辑是这样的:

取随机的书,1和2,如果是1则加正下方的数,2就加右下方的数,v10为由首层加到末层的路径和,当v10 >= 6752833的路径再MD5一下就可以得到结果了

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
#include<time.h>
#define MAX 6752833
using namespace std;
long a[500][500], f[510][510], last[510][510], lis[510];
int path[510];
int main()
{
srand(time(NULL));
int x = 1, y = 1;
FILE * fp = fopen("out", "rb");
while (fscanf(fp, "%d", &a[x][y]) != EOF)
{
if (x == y)
{
y = 1;
x++;
continue;
}
y++;
}
x--;
//cout << x << endl;
f[1][1] = a[1][1];
for (int i = 2; i <= x; i++)
{
for (int j = 1; j <= i; j++)
{
f[i][j] = f[i - 1][j] + a[i][j];
last[i][j] = j;
if (f[i - 1][j - 1] + a[i][j] >= f[i][j])
{
f[i][j] = f[i - 1][j - 1] + a[i][j];
last[i][j] = j - 1;
}
}
}
for (int i = 1; i <= x; i++)
{
if (f[x][i] == 6752833)
{
x = 500, y = i;
while (x > 1)
{
lis[x] = a[x][y];
if (last[x][y] == y - 1)
{
path[x] = 2;
y = y - 1;
}
else
{
path[x] = 1;
}
x--;
}
}
}
for (int i = 2; i <= 500; i++)
{
 printf("%d", path[i]);
}
return 0;
}
//hgame{934f7f68145038b3b81482b3d9f3a355}

使用脚本求出最大的路径即可


The world's full of lonely people afraid to make the first move.