BUUCTF逆向刷题笔记(13-?)持续更新
[BJDCTF2020]JustRE
shitf+f12看到可疑字符串,查看他的交叉引用。
查看后发现一个格式化输出的东西,如实填写即可。
应该是点击1999次按钮就会出flag吧也许。
刮开有奖
这是个赌博程序?有点意思呀。点开说是刮开有奖,但是没什么反应。
先他妈不写了,没有音乐写不进去。
OK,我们拖进IDA看看,根据经验跟进此函数进行分析。
由于string长度=8才会进行此if判断,因此大胆推测string就是flag。其中前四位在末尾的if判断已经给出,所以我们只需要看后四位就行(实际上前四位一会也需要看)。看后面的几位,很明显看到v18被string所赋值,点进函数看看究竟发生了什么事。
函数内有一数组内容如下所示,怀疑是base64加密(上面的41h代表A),在chef里面解密以下v4和v5的数据就拿到了原本的了应该。 最后得到v4=jMp,v5是WP1。
根据此,string的678位是jMp,第345是WP1,但是真的很容易看到12位吗?请看开头部分的代码。
这部分其实点进去函数,推测是一个排序的(我也没看出来,参考网络WP所写的)根据高手思路,直接复制伪C代码是最快的写题方法。汇编中寻址是i*4,因为一个int占4空间,我们修改一下就可以了,直接用i寻址!以下是参考的WP所写的代码:
#include <stdio.h>
#include <string.h>
int sub_4010F0(char* a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3;
for (i = a2; i <= a3; a2 = i)
{
v5 = i;
v6 = a1[i];
if (a2 < result && i < result)
{
do
{
if (v6 > a1[result])
{
if (i >= result)
break;
++i;
a1[v5] = a1[result];
if (i >= result)
break;
while (a1[i] <= v6)
{
if (++i >= result)
goto LABEL_13;
}
if (i >= result)
break;
v5 = i;
a1[result] = a1[i];
}
--result;
} while (i < result);
}
LABEL_13:
a1[result] = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
int main(void)
{
char str[] = "ZJSECaNH3ng";
sub_4010F0(str,0,10);
printf("%s", str);
return 0;
}
然后运行,就是新的v7,这样才可以在if判断句里面进行计算。
刚开始想到了base64,没想到v7也进行了排序操作。现在粗略瞄一眼这个函数,实现了递归调用和对数组值的修改,确实可能是排序。具体算法:
斗胆写了个快速排序的python实现,方便以后复习:
a = [1, 4, 5, 2, 3]
def quick(arr):
if(len(arr)<=1):
return arr
b=arr[0]
left=[x for x in arr[1:] if x<b]
right=[x for x in arr[1:] if x>b]
return quick(left)+[b]+quick(right)
print(quick(a))
参考WP:
BUUCTF 刮开有奖(特别详细了,尽自己全力理解所写)-CSDN博客
[BUUCTF]Reverse-刮开有奖 - 玉石听 - 博客园
破解BUUCTF_刮开有奖-CSDN博客
[BUUCTF]REVERSE——刮开有奖_buuctf reverse 刮开有奖-CSDN博客
[ACTF新生赛2020]easyre1
先用UPX脱壳,看到了主要的判断条件,但是目前看不出来v5的值,陷入僵局。看了WP才知道咋写了。
观察发现关键的(char *)操作,即强制转换为char指针,v5本来占3*4=12字节,转换后相当于储存字符串的一个数组,这点格外注意,那么_data_start后面中括号就是v5的索引的意思?非也,最前面的*表示解引用,那就是说其实就是v5的字符。find()函数可以快速查询特定字符的索引。
我们只要满足if的判断条件即可。那么v4我们已经知道是什么了,为了方便代码的开展,我们把它转换为ASCII数组。具体实现看脚本。。
#转换a的ASCII码到b
a='''*F'\"N,\"(I?+@'''
b=[]
c=[]
flag=''
for i in a:
b.append(ord(i))
print(b)
#开始写脚本破解
index='''~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$# !"'''
for i in b:
c.append(index.find(chr(i))+1)#+1不可以忘记,原代码中的索引v5进行-1才是=v4
for i in c:
flag+=chr(i)
print(flag)
还有一个坑点,这里的27h没有识别出来,我们需要将他转换为10进制自己再查ASCII表添加进脚本。
参考题解:
BUUCTF之[ACTF新生赛2020]easyre 1(RE) - Eip的浪漫 - 博客园
BUUCTF逆向wp [ACTF新生赛2020]easyre_buuctf [actf新生赛2020]easyre-CSDN博客
简单注册器
说实话这也是我学逆向的一大乐趣,破解软件的感觉还是很爽的。
居然是APK格式。先进模拟器看看里面内容,然后直接用jadx分析就可以了。原来看大佬们破解玄奥周易这样的小软件应该也是这种思路了,不过他们的算法估计更厉害。
进去是这样,不惯着他,直接进jadx分析。锁定关键代码。通过第一个if判断句,我们应该可以得到其实flag长度估计是32位。而且第二个和最后一个值都已经知道。
然后对x进行操作,咱们把这个代码copy到java代码运行一下看看。
出来了59acc538825054c7de4b26440c0999dd这一串
public class Main {
public static void main(String[] args) {
char[] x = "dd2940c04462b4dd7c450528835cca15".toCharArray();
x[2] = (char) ((x[2] + x[3]) - 50);
x[4] = (char) ((x[2] + x[5]) - 48);
x[30] = (char) ((x[31] + x[9]) - 48);
x[14] = (char) ((x[27] + x[28]) - 97);
for (int i = 0; i < 16; i++) {
char a = x[31 - i];
x[31 - i] = x[i];
x[i] = a;
}
for (int j = 0; j < x.length; j++) {
System.out.println(x[j]);
}
}
}
其实这个就是flag。但是我们亲自输入进去软件让他自己出flag成就感满满呀,接着看吧。一共是32位。按照第一个判断句的要求给他修改了。其中最后一个相加=104,我们知道肯定是两个数字,因为字母都是ASCII码比较大,无法满足条件。得到下面的字符串:
2b6cc538825054c7de4b26440c0999da
直接输进入软件就行,出来flag。
[GWCTF 2019]pyre
看名字像是python逆向!不出意料是pyc文件,掏出工具反编译PYC。
关键代码如上图所示。
首先第一个for循环看样子就是通过input1计算出了一个数字。在下面的循环中,对code进行了一次反复异或操作。code首先要被第一个for循环操作一次,再自己反复异或。又遇到我最反感的有%的题目。首先我揣测这是不是某个没见过的算法,于是我问问豆包吧。
看到这里恍然大悟,code其实是储存了所有的num后的并且经过第二个for循环的列表了,所以我们应该先将code转换为异或之前,才有可能找到原来的num!然后我们用爆破的思路做。有思路了,咱们开始行动吧。
但是这样的思路按照我的实力暂时还做不出来,而且是复杂的思路。看看别人的wp:
[GWCTF 2019]pyre - 原来是甘文川同学 - 博客园
学到了新知识;
先看对code的异或,是0和1,1和2.由于两次异或就是不异或,所以我们考虑倒着异或。务必理解这个脚本,举个例子,本来是0=0^1,3=3^4(数字代表索引)。现在我们倒着计算3,就是3=3^4,所以必须是i+1才可以。因为最终的长度的最后一位自己没有被后面的异或,从len-2开始计算即可。range中间的-1代表最后i会走到0这个值,刚好就是第一个。
随后, 看这个代码:
我们可以改写为:
code=(flag+i) %128。ASCII不超过128的,对着以后还要敏感点。但是%128并不是无用,因为里面还有负数,如-1%5还余4,(在python里面)这个需要注意。
code =['\x1f','\x12','\x1d','(','0','4','\x01','\x06','\x14','4',',','\x1b','U','?','o','6','*',':','\x01', 'D', ';','%', '\x13']
code1=[]
for i in code:
code1.append(ord(i))
#还原code
for i in range(len(code1)-2,-1,-1):
code1[i]=code1[i]^code1[i+1]
print(code1)
flag=''
for i in range(len(code1)):
flag+=chr((code1[i]-i)%128)
print(flag)
为什么不可以爆破,因为很容易重复,可能1和129结果就一样。原来一个simpleRev可以爆破就是因为不会重复。(我猜)
findit
还是一个apk,看样子flag就是wifi'密码。
粗略扫一眼,先定义了a和b,然后经过特定运算得到X和Y,必须X和输入的东西一样,才会输出Y。所以Y是flag没跑了,X是获得Y的密钥。老规矩,复制到编译器运行一下便知。
public class Main {
public static void main(String[] args) {
char[] a = { 'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e' };
char[] b = { 'p', 'v', 'k', 'q', '{', 'm', '1', '6', '4', '6', '7', '5', '2', '6', '2', '0', '3', '3', 'l', '4',
'm', '4', '9', 'l', 'n', 'p', '7', 'p', '9', 'm', 'n', 'k', '2', '8', 'k', '7', '5', '}' };
char[] x = new char[17];
char[] y = new char[38];
// 处理数组 a
for (int i = 0; i < 17; i++) {
if ((a[i] < 'I' && a[i] >= 'A') || (a[i] < 'i' && a[i] >= 'a')) {
x[i] = (char) (a[i] + 18);
} else if ((a[i] >= 'A' && a[i] <= 'Z') || (a[i] >= 'a' && a[i] <= 'z')) {
x[i] = (char) (a[i] - '\b');
} else {
x[i] = a[i];
}
}
// 处理数组 b
for (int i2 = 0; i2 < 38; i2++) {
if ((b[i2] >= 'A' && b[i2] <= 'Z') || (b[i2] >= 'a' && b[i2] <= 'z')) {
y[i2] = (char) (b[i2] + 16);
if ((y[i2] > 'Z' && y[i2] < 'a') || y[i2] >= 'z') {
y[i2] = (char) (y[i2] - 26);
}
} else {
y[i2] = b[i2];
}
}
// 输出处理后的数组 x
for (int i = 0; i < 17; i++) {
System.out.print(x[i]);
}
System.out.println();
// 输出处理后的数组 y
for (int i = 0; i < 38; i++) {
System.out.print(y[i]);
}
}
}
x就是我们应该要输入的,y直接就是flag。
[ACTF新生赛2020]rome
看样子也是实现了一个简单的字符变换加密。接下来细细分析代码。