hgame week2 re wp
Arithmetic
[关于upx壳的一点认知,以及upx壳的特征识别 - 北北软件园 - 博客园 (cnblogs.com)](https://www.cnblogs.com/xiazai/p/14295684.html#:~:text=一般是被识别出了upx的特征,然后直接再拖入到upx加壳器中,加壳器自己就可以识别出这是加过壳的,然后执行脱壳。 (upx加壳器就可以脱自己加密出来的upx) upx的特征,我们可以通过抹掉特征码来隐藏 特征码处1:upx字符串,特征码为55 50 58 一般存在于区段名称这里%2C等字节长度随意替换,对程序没有任何影响 特征码处2%3Aoep前几个字节,%3F%3F %3F%3F 00 8D BE %3F%3F %3F%3F %3F%3F)
upx特征码:55 50 58
首先,我们来讲一讲查壳的原理。不论是压缩壳,还是加密壳,都有特定的算法。加同种壳的不同应用程序,有一部分机器码是相同的,它们被称为“签名”或者“特征码”。有些壳还会将自身的一些信息写入可执行程序。
对比,改成upx特征码
然后保存文件,再次查壳发现文件由最开始的upx3.91+变成了upx3.96
打开发现是这样的(部分)
按照次规律下去,在最后一行第500行应该有500个数,只不过因为显示问题没用呈三角形排列
点开查看dword_1400048B0,发现和1400048B4是连续的
所以可以判断得到路径:
若为1则加正下⽅的数,若为2则加右下⽅的数
v10为由⾸层加⾄末层的路径值的和
路径和>=6752833的路径求md5即可得到路径
exp:
#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;//将 y 重置为 1,表示转到下一行的第一个位置
x++;//将 x 增加 1,表示转到下一行
continue;
}
y++;//当数字金字塔的位置不在对角线上时,将 y 增加 1,表示继续读取当前行的下一个位置的数字
}
x--;//在整个循环结束后,将 x 减去 1,因为在最后一次循环中,x 已经增加了一个额外的值,超过了实际的行数
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;
}
}
}//这部分代码使用动态规划的方法计算出从金字塔的顶部到每个位置的最大路径和。f[i][j] 表示从顶部到第 i 行第 j 列的最大路径和,last[i][j] 记录在计算最大路径和时选择的路径
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}
babyre
需要掌握:
1.ELF(Executable and Linkable Format)是一种常见的可执行文件和目标文件格式。在 ELF 文件中,有一个叫做 .init_array 的节(section),用于存放程序的初始化函数(init function)的地址。这些初始化函数会在程序执行之前被自动调用,用于执行一些必要的初始化工作。
.init_array 节中存放着一系列函数指针,这些函数指针指向程序中需要在加载时执行的初始化函数。操作系统在加载 ELF 文件时会依次调用这些初始化函数,确保程序的各个部分都得到正确的初始化。
需要注意的是,.init_array 节中的初始化函数的执行顺序是按照它们在节中的出现顺序来确定的。因此,开发者在编写这些初始化函数时需要考虑它们之间的依赖关系,以确保程序能够正确地初始化。
2.特定库<pthread.h>
部分函数:
sem_wait() 函数:
- 当一个线程调用 sem_wait() 函数时,它会试图获取一个信号量。如果信号量的值大于 0,表示有可用的资源,那么该线程将继续执行,并且信号量的值会减 1。
- 如果信号量的值为 0,表示当前没有可用的资源,那么调用 sem_wait() 的线程会被挂起(阻塞),直到有其他线程调用 sem_post() 来增加信号量的值。
sem_post() 函数:
- 当一个线程调用 sem_post() 函数时,它会释放一个资源,并将信号量的值加 1。
- 如果此时有其他线程正在等待该信号量(即信号量的值为 0),则会有一个或多个线程从 sem_wait() 中返回,并继续执行其后续操作。
总的来说,信号量提供了一种线程间同步和互斥的机制,允许线程之间协调共享资源的访问。sem_wait() 用于等待资源的获取,并在获取到资源时继续执行,而在资源不可用时被挂起;sem_post() 用于释放资源,以便其他线程可以继续执行。这样,通过信号量的使用,可以有效地控制并发线程对共享资源的访问,避免竞态条件和数据不一致的问题。
signal(8, handler) :这行代码用于设置信号处理函数。signal
函数用于注册信号处理函数,其中第一个参数表示信号编号,这里是 8(通常用于处理程序错误)。第二个参数是信号处理函数的地址,即 handler
使用:
sem_init(adress,0,初值) 初始化信号值
pthread_create(adress,0,函数,0) 创建线程
sem_wait(adress) 等待信号值,一旦信号值不为0,则信号值立马减一并返回,若信号值为0则阻塞线程的进行
sem_post(adress) 信号值加一
pthread_join(*(&ptr ), 0LL) 暂停现行线程,直到指定线程运行完毕
3.进程
进程与线程的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)
4.浮点异常
- 试图将一个数除以0
- 试图对负数取平方根
- 试图对负数取对数
- 浮点数溢出或下溢(例如,结果太大或太小无法表示)
在这种情况下,程序会收到一个 SIGFPE 信号并终止执行
做题:修改变量名后
进入输入函数,可知 dword_41C0为输入input,点开后发现地址与4240连续,即输入32个字节,第33个字节被设置为249
进入func1,如下
用8.3 IDA远调 注意用同一个版本的server,不然就会报错
调试
调试到第四次,F8步过的时候,发生浮点异常
对于
这段代码的作用是设置一个信号处理函数 handler
,然后在执行关键代码之前,检查是否是第一次执行,如果是则注册信号处理函数,并进行数组元素异或操作。这样,在执行关键代码期间,如果产生了信号(例如,程序错误),会跳转到注册的信号处理函数进行相应的处理,所以最后得到字符串wtxfei
然后就是注意看清楚循环结构,一次循环会执行完1,2,3,4四个线程
exp:
hgame{you_are_3o_c1ever2_3Olve!}
babyAndroid
主要逻辑
注意此时双击key获取的是key的资源id而不是key的值
最初在下图中可得知经过加密后的数据如最下方check函数,发现是RC4加密
接下来就是获取key(正确打开方式)
1 |
|
使用工具解密,注意要将数据进行处理变为16进制,我们会发现得到的数据里面包含了负数
对于负数的转换,常用的方法是使用补码表示法。以下是将负数转换为十六进制的步骤:
- 将负数的绝对值转换为二进制表示。例如,-75 的绝对值是 75,其二进制表示为 1001011。
- 如果二进制表示不足 8 位,需要在左侧填充零,使其达到 8 位长度。在这种情况下,补充前导零使二进制表示为 01001011。
- 取该二进制数的按位取反。对于 01001011,按位取反后为 10110100。
- 将按位取反后的二进制数加 1。对于 10110100,加 1 后为 10110101。
- 将得到的补码转换为十六进制表示。10110101 转换为十六进制表示为 0xB5。
因此,-75 的十六进制表示为 0xB5。同样的方法可以应用于其他负数的转换。
按照以上方法,所以处理后得到的数据为 0xB5 0x50 0x50 0x30 0xA8 0x4B 0x67 0xD3 0xA5 0x59 0xC4 0x5B 0xCA 0x05 0x06 0xB8,去空格,替换小写后得 G>IkH<aHu5FE3GSV
(不知道为什么拿cyberchef解出来不对)
apk改后缀名为zip解压后在lib里面找到so文件,然后在native层
点开函数发现是AES(可使用findcrypt判断)
得到加密数据
(在汇编语言中,DCB
是 “Define Constant Byte” 的缩写,用于声明一个或多个字节常量。每个 DCB
指令后面跟随着一系列以逗号分隔的字节值。)
ezcpp
总的加密逻辑就这样,逆回去就行了(这个题因为数据是一样的但是我以为不一样,看wp看了半天最后知道是一样的……深深的伤透了我的心……)
循环四次,从后面往前面,八个字节为一组
exp:
#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;
}
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);
}
hgame