【BUUCTF】Reverse篇部分总结
孩子可太想升个级了,放一些之前写的一些buuctf逆向题目的总结## easyre
首先查一下壳以及其他相关信息
!(https://www.buryia.top/upload/2021/12/BurYiA1589757804-782ff4bef7664759bb377c65925b9ebe.png)
可以看到是 64 位程序,没壳,那就可以直接用IDA打开了
IDA一键F5
!(https://www.buryia.top/upload/2021/12/BurYiA1589757813-867f17302e8d46fa97b11cbb6f0abeb4.png)
真·easyre
## reverse1
拿到题目,继续查壳,看信息
!(https://www.buryia.top/upload/2021/12/BurYiA1589759515-d585e506f43444b49bcf6d2538c4a1ea.png)
64位程序,没壳
IDA打开后找主函数逻辑,然后.....点了半天没找到
直接**shift+F12**查看字符串
!(https://www.buryia.top/upload/2021/12/BurYiA1589759557-43d09246125c49db8b9b9f730364e460.png)
可以发现这里有一个关键字符串
> this is the right flag!
点进去,x查看交叉引用便可以找到主函数
!(https://www.buryia.top/upload/2021/12/BurYiA1589759589-36dbd5403ad44ba1b90ea471d03eb4f1.png)
可以看到的是在这个字符串上面有个 **strcpy** 对比,其中一个是我们输入的字符串,而另一个不出意外就是flag了,点进去看看
!(https://www.buryia.top/upload/2021/12/BurYiA1589759608-df246978d5bc4f5e87f25c38f599a2ce.png)
加上 flag 前缀后我们直接提交,但很难受的就是可以发现flag是错误的。
我们继续去分析他的主函数
在上面我们可以看到他对 flag 做了与一个操作
```cpp
for ( j = 0; ; ++j )
{
v8 = j;
v2 = j_strlen(Str2);
if ( v8 > v2 )
break;
if ( Str2 == 111 )
Str2 = 48;
}
```
注意他的第二个 if 判断,按 r 我们把 ascll 码变成字符,可以发现他是把字符串中的所有的 “o” 变成了 “0”
然后就不用我多说了吧,提交flag就行
## reverse2
查壳
!(https://www.buryia.top/upload/2021/12/Lily_1611716862-7c3c4d9056e142489886c5f5d1daacc3.png)
64位程序,无壳
用IDA打开程序,分析主函数
!(https://www.buryia.top/upload/2021/12/Lily_1611676056-8dfded446f0f43628fe5f0b1c9561249.png)
可以看到程序是将我们输入的数据与flag做对比,并且在对比之前将flag中的r和i字符换成了1
剩下的就不用多说了吧,记得flag的格式“flag{}”
当然,比对类的题目可以直接用动态调试来查看存储在程序中的字符串是什么
ELF文件,用gdb打开,在第一个输入点处ctrl+c断下来,单步随便输入一串字符串后就可以在strcmp处下断了(在输入后最好单步再走几步,将输入的函数先执行完成)
再用continue运行,就能直接看strcmp函数中的参数了,如下图
!(https://www.buryia.top/upload/2021/12/Lily_1611716671-1d937f68e78d413e84dfa9131b60e149.png)
## 内涵的软件
查看程序基本信息
!(https://www.buryia.top/upload/2021/12/Lily_1612490409-3e73ceb3cb364419a7a549621637b71d.png)
32位程序,没有加壳
选择IDA32打开查看主要函数
!(https://www.buryia.top/upload/2021/12/Lily_1612490642-915775d9c10f419ca21243516a0896f9.png)
很明显答案已经摆出来了,结合题目要求“注意:得到的 flag 请包上 flag{} 提交”换一下flag格式就可以了
## 新年快乐
查看程序基本信息
!(https://www.buryia.top/upload/2021/12/Lily_1612491085-813550efaca542099b6cdc8a79a7304a.png)
32位程序,可以很惊奇的发现终于带壳了
掏出x32dbg开始手脱
UPX的壳一般会有pushad和popad
打开后一路F9找pushad,找到后F8运行一步,再在栈顶下硬件访问断点(也可以单步,找大跳转就行,单步的过程中遇到向上的跳转记得在下面F4,向上的跳转是循环,没必要再去单步跟循环了)
!(https://www.buryia.top/upload/2021/12/Lily_1612493785-c6a762623e644c2aabe705ea657e8f04.png)
直接F9,就可以发现已经过了popad,在下面不远处我们可以发现一个很大的跳转,一般就是程序入口了
!(https://www.buryia.top/upload/2021/12/Lily_1612493935-784f99d458c84339980421551b667ae3.png)
dump后用IDA打开进行分析
!(https://www.buryia.top/upload/2021/12/Lily_1612494663-79cfa872c5dd4dfbaa62666f3530c0a8.png)
可以看到程序还是很简单的,重点就是在考察脱壳能力
## guessgame
查看程序相关信息
!(https://www.buryia.top/upload/2021/12/Lily_1612495883-377c56eac6034944985ebdcd37f991a2.png)
64位程序,没有加壳
IDA打开分析
!(https://www.buryia.top/upload/2021/12/Lily_1612496053-d1e83e77d2e74bd1b1e52737090524be.png)
可以看到没有什么有用的东西
Shift+F12看一下字符串窗口
!(https://www.buryia.top/upload/2021/12/Lily_1612496072-f77e3d83c76949e18d4baed50af2f07a.png)
...行吧
## helloword
一道安卓题目
先查壳
!(https://www.buryia.top/upload/2021/12/Lily_1612499427-6573c6a77dd244a1ab8be4740dba596d.png)
无壳,直接拖进jadx中进行分析
!(https://www.buryia.top/upload/2021/12/Lily_1612498926-513e8bafc6844c11bba1880fd2c6a30a.png)
## xor
Linux下的程序,直接拖入IDA分析
!(https://www.buryia.top/upload/2021/12/Lily_1612503006-ced60384fa024910a569e576d08d80bd.png)
可以看到程序逻辑是将我们输入的字符串从第二位开始异或,再与存储在程序内部的字符串做比较(Shift+E可以将数据提取出来,记得先全选,在提取)
!(https://www.buryia.top/upload/2021/12/Lily_1612503179-7e5a1d318a7d42e0bee707e33defa061.png)
因为xor再异或一次便可以得到原先的内容,写脚本跑一下便可
```cpp
#include<bits/stdc++.h>
using namespace std;
unsigned char ida_chars[] =
{
102,10, 107,12, 119,38,79,46,64,17,
120,13,90,59,85,17, 112,25,70,31,
118,34,77,35,68,14, 103, 6, 104,15,
71,50,79, 0
};
unsigned char flag={102,0};
int main(int argc, char const *argv[])
{
for (int i = 1; i < 33; i++)
flag = ida_chars ^ ida_chars;
cout << flag;
return 0;
}
// 10110
// 11011
// 10110
```
## reverse3
查看程序相关信息
!(https://www.buryia.top/upload/2021/12/Lily_1612513351-b4904c93ad0443a59f99d97ce97557af.png)
32位程序,没有加壳
用IDA打开分析主要函数
!(https://www.buryia.top/upload/2021/12/Lily_1612515691-7935519575564bd0985c7642b0d91ed8.png)
可以看到首先是将输入的字符串进行一个函数变换
变换后的字符串中每个元素再加上自己的下标
完成后再与程序内部所存储的字符串进行对比
跟进对输入的字符串进行操作的函数后可以发现是一个Base64编码的操作
则我们只要将程序内存储的字符串先进行每个元素减去下标的操作,在进行Base64解码即可反推出flag
```cpp
#include<bits/stdc++.h>
using namespace std;
unsigned char ida_chars[] =
{
101, 51, 110, 105, 102, 73, 72, 57, 98, 95,
67, 64, 110, 64, 100, 72, 0
};
unsigned char flag={0};
int main(int argc, char const *argv[])
{
for (int i = 0; i < 16; i++)
flag = ida_chars - i ;
cout << flag;
return 0;
}
```
## 不一样的flag
查壳,看信息
!(https://www.buryia.top/upload/2021/12/BurYiA1589758255-7cbd1965737842929bf7542c77508888.png)
32位,没壳
IDA打开我们来分析逻辑
!(https://www.buryia.top/upload/2021/12/BurYiA1589758359-d163619ae8ac4b08941039d3108e4af3.png)
首先我们可以看到在下面他有提示让输入上下左右,其次在上面我们注意到有这么一行
>mov ebx, offset __data_start__ ; "*11110100001010000101111#"
注意最后的那个字符串基本是由 0 和 1 组成,并且首位是 * ,末尾是 #,那这个是绝对是个迷宫题没跑了
既然是迷宫题,我们就需要知道他是几行几列的迷宫题,这样我们才能去解
F5 分析逻辑,我们注意主函数最后的这一块
!(https://www.buryia.top/upload/2021/12/BurYiA1589758404-e500db731208429495b49aba687db02f.png)
可以看到迷宫的一层有5个字母,并且flag就是我们输入的内容
那显然迷宫就是这样的了
>*1111
01000
01010
00010
1111#
flag 就不写了,扫一眼就出来了
## SimpleRev
- [题目链接](https://files.buuoj.cn/files/7458c5c0ce999ac491df13cf7a7ed9f1/SimpleRev?token=eyJ1c2VyX2lkIjo4OTU1LCJ0ZWFtX2lkIjpudWxsLCJmaWxlX2lkIjoyNDN9.YB4Aeg.W12m0xmeRYRJmvcMAZpdsS-5NHE)
一道Linux下的题目,但我死活跑不起来,就很气...
程序基本信息
!(https://www.buryia.top/upload/2021/12/Lily_1612579762-44092080225e42b6b7aebd00b085a8ef.png)
64位程序
IDA打开进行分析,主要的flag验证部分在Decry函数,因太长不好截图,直接粘贴在下面
```cpp
unsigned __int64 Decry()
{
char v1; //
int v2; //
int v3; //
int i; //
int v5; //
char src; // BYREF
__int64 v7; //
int v8; //
__int64 v9; // BYREF
int v10; //
unsigned __int64 v11; //
v11 = __readfsqword(0x28u);
*(_QWORD *)src = 'SLCDN'; // 原先的数据在内存中为小端存储
// 故应为:NDCLS
v7 = 0LL;
v8 = 0;
v9 = 'wodah'; // 同理应为:shadow
v9 = 0LL;
v10 = 0;
text = join(key3, (const char *)v9); // text = killshadow
strcpy(key, key1);
strcat(key, src); // key = ADSFKNDCLS
v2 = 0;
v3 = 0;
getchar();
v5 = strlen(key);
for ( i = 0; i < v5; ++i ) // 将key中大写全部换成小写
{
if ( key > 64 && key <= 90 )
key = key + 32;
++v3; // v3 = v5
} // key = adsfkndcls
printf("Please input your flag:");
while ( 1 )
{
v1 = getchar();
if ( v1 == '\n' )
break;
if ( v1 == ' ' )
{
++v2;
}
else // 输入字符不为空格或者回车时
{
if ( v1 <= '`' || v1 > 'z' )
{
if ( v1 > '@' && v1 <= 'Z' ) // 大写字符
{
str2 = (v1 - 39 - key + 97) % 26 + 97;
++v3;
}
}
else // 小写字符
{
str2 = (v1 - 39 - key + 97) % 26 + 97;
++v3;
}
if ( !(v3 % v5) )
putchar(' ');
++v2;
}
}
if ( !strcmp(text, str2) )
puts("Congratulation!\n");
else
puts("Try again!\n");
return __readfsqword(0x28u) ^ v11;
}
```
可以看到是一个加密变换,key和最终的text都能直接得到(注意小端的问题),那么写个脚本跑一下即可
```cpp
#include<bits/stdc++.h>
using namespace std;
char key[] = "adsfkndcls";
char text[] = "killshadow";
char flag = {0};
int main(int argc, char const *argv[])
{
for (int i = 0; i < 10; i++)
{
for (char j = 65; j < 126; j++)
{
if(j>=91 && j<=96)
continue;
if((j - 39 - key + 97) % 26 + 97 == text)
{
flag = j;
break;
}
}
}
cout << flag;
return 0;
}
```
## Java逆向解密
.class 文件,用jadx直接打开看java代码
```java
import java.util.ArrayList;
import java.util.Scanner;
/* renamed from: reversereason: default package */
public class reverse {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
System.out.println("Please input the flag :");
String str = s.next();
System.out.println("Your input is :");
System.out.println(str);
Encrypt(str.toCharArray()); // toCharArray() 方法将字符串转换为字符数组。
}
public static void Encrypt(char[] arr) {
int[] KEY;
ArrayList<Integer> Resultlist = new ArrayList<>();
for (char c : arr) {
Resultlist.add(Integer.valueOf((c + '@') ^ 32)); // valueOf() 方法用于返回给定参数的原生 Number 对象值,参数可以是原生数据类型, String等。
//此处返回一个int值
}
System.out.println(Resultlist);
ArrayList<Integer> KEYList = new ArrayList<>();
for (int i : new int[]{180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65}) {
KEYList.add(Integer.valueOf(i));
}
System.out.println("Result:");
if (Resultlist.equals(KEYList)) {
System.out.println("Congratulations!");
} else {
System.err.println("Error!");
}
}
}
```
计算什么的好烦的,直接爆破
```java
import java.util.ArrayList;
/* renamed from: reversereason: default package */
public class reverse {
public static void main(String[] args) {
int text[] = {180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65};
ArrayList<Integer> flag = new ArrayList<>();
for(int i=0; i<18; i++)
for(char c=33; c<128; c++)
{
if(Integer.valueOf((c + '@') ^ 32) == text)
flag.add(Integer.valueOf(c));
}
// flag.add(33);
System.out.println(flag);
}
}
```
爆破得到的数组通过ascll转一下即可
需要注意点的是需要将全部的可见字符全部用于跑字典,不然就会出现问题
## luck_guy
DIE 分析后可以看到为 elf 64 位程序,先用ida64打开看是否可以分析出什么
发现有一个 get_flag 函数,那不出意外获取 flag 的逻辑就在这里了
```cpp
unsigned __int64 get_flag()
{
unsigned int v0; // eax
int i; //
int j; //
__int64 s; // BYREF
char v5; //
unsigned __int64 v6; //
v6 = __readfsqword(0x28u);
v0 = time(0LL);
srand(v0);
for ( i = 0; i <= 4; ++i )
{
switch ( rand() % 200 )
{
case 1:
puts("OK, it's flag:");
memset(&s, 0, 0x28uLL);
strcat((char *)&s, f1);// GXY{do_not_
strcat((char *)&s, &f2);
printf("%s", (const char *)&s);
break;
case 2:
printf("Solar not like you");
break;
case 3:
printf("Solar want a girlfriend");
break;
case 4:
s = 0x7F666F6067756369LL;
v5 = 0;
strcat(&f2, (const char *)&s);
break;
case 5:
for ( j = 0; j <= 7; ++j )
{
if ( j % 2 == 1 )
*(&f2 + j) -= 2;
else
--*(&f2 + j);
}
break;
default:
puts("emmm,you can't find flag 23333");
break;
}
}
return __readfsqword(0x28u) ^ v6;
}
```
大致观察后可以发现是在 case 4 中给 f2 赋值,之后再 case 5 中对 f2 进行变换,最后在 case 1 中进行合并输出
写个脚本跑一些就可以得到结果
需要注意到是,f2 是存储在栈中的,所以写 f2 数组时应该倒着写,
即
char arr[] = {0x69, 0x63, 0x75, 0x67, 0x60, 0x6f, 0x66, 0x7f, 0};
得到的 flag 改成 flag{} 格式再提交...
## findit
又一道安卓 apk 题目
Android-packinfo 查壳后没有发现什么信息,直接用 jadx 打开
找到主函数
```java
package com.example.findit;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
/* access modifiers changed from: protected */
@Override // android.support.v7.app.ActionBarActivity, android.support.v4.app.FragmentActivity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText edit = (EditText) findViewById(R.id.widget2);
final TextView text = (TextView) findViewById(R.id.widget1);
final char[] a = {'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e'};
final 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', '}'};
((Button) findViewById(R.id.widget3)).setOnClickListener(new View.OnClickListener() {
/* class com.example.findit.MainActivity.AnonymousClass1 */
public void onClick(View v) {
char[] x = new char;
char[] y = new char;
for (int i = 0; i < 17; i++) {
if ((a < 'I' && a >= 'A') || (a < 'i' && a >= 'a')) {
x = (char) (a + 18);
} else if ((a < 'A' || a > 'Z') && (a < 'a' || a > 'z')) {
x = a;
} else {
x = (char) (a - '\b');
}
}
if (String.valueOf(x).equals(edit.getText().toString())) {
for (int i2 = 0; i2 < 38; i2++) {
if ((b < 'A' || b > 'Z') && (b < 'a' || b > 'z')) {
y = b;
} else {
y = (char) (b + 16);
if ((y > 'Z' && y < 'a') || y >= 'z') {
y = (char) (y - 26);
}
}
}
text.setText(String.valueOf(y));
return;
}
text.setText("答案错了肿么办。。。不给你又不好意思。。。哎呀好纠结啊~~~");
}
});
}
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
```
很明显的可以看到有个地方会输出答案错误,那他上面的函数不出意外就是 flag 的输出函数了
继续写个脚本跑就行,要注意的是如果使用c/c++写,数组需要定义为 unsigned char,不然会溢出造成显示出问题
```cpp
#include<iostream>
using namespace std;
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', '}'};
unsigned char y={0};
int main(int argc, char const *argv[])
{
for (int i2 = 0; i2 < 38; i2++)
{
if ( (b < 'A' || b > 'Z') && (b < 'a' || b > 'z') )
y = b;
else
{
y = (char) (b + 16);
if ((y > 'Z' && y < 'a') || y >= 'z')
y = (char) (y - 26);
}
}
cout << y << endl;
for (int i = 0; y!=0; i++)
{
cout << (int)y << " ";
}
return 0;
}
```
## 简单注册器
还是安卓
主要逻辑在这里
```java
public void onClick(View v) {
int flag = 1;
String xx = editview.getText().toString();
if (!(xx.length() == 32 && xx.charAt(31) == 'a' && xx.charAt(1) == 'b' && (xx.charAt(0) + xx.charAt(2)) - 48 == 56)) {
flag = 0;
}
if (flag == 1) {
char[] x = "dd2940c04462b4dd7c450528835cca15".toCharArray();
x = (char) ((x + x) - 50);
x = (char) ((x + x) - 48);
x = (char) ((x + x) - 48);
x = (char) ((x + x) - 97);
for (int i = 0; i < 16; i++) {
char a = x;
x = x;
x = a;
}
textview.setText("flag{" + String.valueOf(x) + "}");
return;
}
textview.setText("输入注册码错误");
}
```
直接跑就行,够清晰了,跑出来记得加一个 flag 框
## pyre
pyc 文件,用在线工具逆向源码
```python
#!/usr/bin/env python
# visit http://tool.lu/pyc/ for more information
print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
num = ((input1 + i) % 128 + 128) % 128
code += num
for i in range(l - 1):
code = code ^ code
print code
code = [
'\x1f',
'\x12',
'\x1d',
'(',
'0',
'4',
'\x01',
'\x06',
'\x14',
'4',
',',
'\x1b',
'U',
'?',
'o',
'6',
'*',
':',
'\x01',
'D',
';',
'%',
'\x13']
```
根据题目意思写了一个 c 语言版本的,用来测试
```cpp
#include<bits/stdc++.h>
using namespace std;
string arr = "flag{abcabc}";
int brr;
int main(int argc, char const *argv[])
{
int length = arr.length();
for (int i = 0; i < length; i++)
{
int num = ((arr + i) % 128 + 128) % 128;
brr = num;
}
for (int i = 0; i<length; i++)
cout << (int)brr << ", ";
cout << endl;
for(int i=0; i<length-1; i++)
brr = brr ^ brr;
for (int i = 0; i<length; i++)
cout << (int)brr << ", ";
cout << endl;
for(int i=length-2; i>=0; i--)
brr = brr ^ brr;
for (int i = 0; i<length; i++)
cout << (int)brr << ", ";
cout << endl;
// l = len(input1)
// for i in range(l):
// num = ((input1 + i) % 128 + 128) % 128
// code += num
return 0;
}
```
异或部分倒着写就行,前面的求余那一块直接爆破即可
```cpp
#include<iostream>
#include<cstring>
using namespace std;
char code[] = { 0x1f, 0x12, 0x1d, '(', '0', '4', 0x01, 0x06, 0x14, '4', ',', 0x1b, 'U', '?', 'o', '6', '*', ':', 0x01, 'D', ';', '%', 0x13, 0};
// char code[] = {102, 109, 99, 106, 127, 102, 104, 106, 105, 107, 109, 8, 0};
// char code[] = {11, 14, 9, 21, 25, 14, 2, 3, 2, 6, 101, 8, 0};
char arr = {0};
int main(int argc, char const *argv[])
{
int l = strlen(code);
for (int i = 0; i<l; i++)
cout << (int)code << ", ";
cout << endl;
for(int i=l-1; i>=0; i--)
code = code ^ code;
for (int i = 0; i<l; i++)
cout << (int)code << ", ";
cout << endl;
for (int i = 0; i < l; i++)
for (int j = 32; j < 128; j++)
if( ((j + i) % 128 + 128) % 128 == code)// && j>=32 && j<=126)
{
arr = j;
// break;
}
cout << arr << endl;
for (int i = 0; i<l; i++)
cout << (int)arr << " ";
cout << endl;
return 0;
}
```
得到的 flag 记得换格式提交
## JustRE
没啥说的,函数不多,翻一下就出来了
```cpp
INT_PTR __stdcall DialogFunc(HWND hWnd, UINT a2, WPARAM a3, LPARAM a4)
{
CHAR String; // BYREF
if ( a2 != 272 )
{
if ( a2 != 273 )
return 0;
if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 )
{
sprintf(String, Format, ++dword_4099F0);
if ( dword_4099F0 == 19999 )
{
sprintf(String, " BJD{%d%d2069a45792d233ac}", 19999, 0);
SetWindowTextA(hWnd, String);
return 0;
}
SetWindowTextA(hWnd, String);
return 0;
}
EndDialog(hWnd, (unsigned __int16)a3);
}
return 1;
}
``` 不错,LZ能打包下原文件吗 感谢分享!! akarus 发表于 2022-8-4 22:00
不错,LZ能打包下原文件吗
啊这个,基本都是buu上的原题,当时也没有刻意保存文件,这个……不好意思 谢谢分享 感谢大佬分享!{:1_919:} 写的真好!请问可以发一下资料嘛 感谢分享有crypto的吗
页:
[1]