BUUCTF逆向工程-2
BUUCTF逆向工程-2
相册
打开后可以找到发送邮件的函数原型:
根据交叉引用找到调用它的地方:
可以知道C2.MAILSERVER应当为邮箱,根据交叉引用可以找到:
这个函数加载了一个core文件,然后调用里面的NativeMethod.m()函数,查看它的Library:
有一个libcore.so文件,将它用IDA打开,在java_com_net_cn_NativeMethod_m()函数中可以看见:
查看base64方法中的shared可以拿到加密表:
解密得到flag:18218465125@163.com
[MRCTF2020]hello_world_go
代码写得根本看不懂在干什么,但是瞎点一通发现unk_4D3C58里就是flag:hello_world_gogogo
从结果来看应该是一个go语言,无语。。。
[WUSTCTF2020]level3
打开看见它想让我破译一个base64:
点进加密函数可以跟踪到密码表:
用这个密码表解码发现解出来是乱码......想到密码表可能被修改过,查看它的交叉引用:
写个简单的脚本得到flag:Base64_is_the_start_of_reverse
[GWCTF 2019]xxor
打开看见:
前面一段对输入进行TEA,之后判断结果,解码脚本在此:
#include <stdio.h>
#include <stdint.h>
void decrypt (int *v, int *k)
{
unsigned int v0 = v[0], v1 = v[1], i;
int delta = 1166789954;
int sum = 0;
for(i = 0; i < 64; i++)
{
sum += delta;
}
for (i = 0; i < 64; i++)
{
v1 -= (v0 + sum + 20) ^ ((v0 << 6) + k[2]) ^ ((v0 >> 9) + k[3]) ^ 0x10;
v0 -= (v1 + sum + 11) ^ ((v1 << 6) + k[0]) ^ ((v1 >> 9) + k[1]) ^ 0x20;
sum -= delta;
}
v[0] = v0;
v[1] = v1;
return;
}
int main()
{
int v[2] = {}, k[4] = {2,2,3,4};
unsigned int a1[6] = {-548868226, 550153460, 3774025685, 1548802262, 2652626477, -2064448480};
int i, j;
for(i = 0; i <= 2; i++)
{
v[0] = a1[i*2];
v[1] = a1[i*2+1];
decrypt(v, k);
a1[i*2] = v[0];
a1[i*2+1] = v[1];
}
for(i = 0; i <= 5; i++)
{
for(j = 2; j >= 0; j--)
{
printf("%c", *((char *)&a1[i] + j));
}
}
return 0;
}
得到flag:re_is_great!
说实话,写完这题我仍然不是很理解HIDWORD、LODWORD和ELF的逆序存储,全靠IDA动调测算出数据的存储方式,然后进行逆推,再根据输出调整顺序。。。
虽说篇幅不长,但是这个b干了我将近4个小时
[FlareOn4]IgniteMe
打开IDA,果然,C++写出来的程序最恶心了(虽然我喜欢用C++):
sub_4010F0是输入函数,sub_401050是加密函数:
其中sub401000函数里面是一个__ROL4__函数,百度了半天我也没看明白,但是IDA动调之后可以看见最后v4=4,所以也可以写出脚本:
#include <iostream>
using namespace std;
int main()
{
int ans[99] = {13, 38, 73, 69, 42, 23, 120, 68, 43, 108,
93, 94, 69, 18, 47, 23, 43, 68, 111, 110,
86, 9, 95, 69, 71, 115, 38, 10, 13, 19,
23, 72, 66, 1, 64, 77, 12, 2, 105};
int flag[99];
int i;
int v4 = 4;
for(i = 38; i >= 0; i--)
{
flag[i] = v4 ^ ans[i];
v4 = flag[i];
}
for(i = 0; i <= 38; i++)
{
cout << char(flag[i]);
}
return 0;
}
得出flag:R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com
[WUSTCTF2020]Cr0ssfun
没啥技术含量
flag:cpp_@nd_r3verse_@re_fun
[FlareOn6]Overlong
IDA中代码很短,理论上运行一遍就有答案:
其中unk_402008是给定的175空间的数组,但是sub_401160中对于次数组的调用最多只有28*4=112空间,因此推测所传参数28太小,所以拖入x32dbg修改对应二进制值1C为AE:
之后运行起来,即可得到结果:
得到flag:I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com
[UTCTF2020]basic-re
shift+F12:
得到flag:str1ngs_1s_y0ur_fr13nd
[FlareOn3]Challenge1
简单的变表base64,得到flag:sh00ting_phish_in_a_barrel@flare-on.com
[ACTF新生赛2020]Oruga
这是一道比较抽象的迷宫题:
由这个可以推出地图是一个16*16的空间,长这个样子:
看一看它的校验部分:
它的v2在校验中就进行了更改,而下一次循环又不会对v2进行回溯,因此它走迷宫的路线会变成直来直去的样子。一直向某个方向走到底直到遇见非空的格子,同时还不能越界,因此路线为:
得到flag:MEWEMEWJMEWJM
[BJDCTF2020]BJD hamburger competition
下载之后非常懵逼,是一个Unity的游戏,打开Visual Studio发现Unity的游戏使用C#写的,所以我们要在它给的一大坨东西里面找一个C#写的dll文件。发现在BJD hamburger competition_Data\Managed下有一个Assembly-CSharp.dll,使用dnSpy打开它,找到加密部分:
跟踪Md5(),发现它截取了答案的前20位:
写出脚本:
import hashlib
str = "DD01903921EA24941C26A48F2CEC24E0BB0E8CC7"
for i in range (10000000000000):
if hashlib.sha1(i.__str__().encode()).hexdigest().upper() == str:
break
print('flag{' + hashlib.md5(i.__str__().encode()).hexdigest().upper()[0:20] + '}')
得到flag:B8C37E33DEFDE51CF91E
特殊的 BASE64
简单的变表base64,不得不说,C++的逆向代码看起来确实恶心
得到flag:Special_Base64_By_Lich
[Zer0pts2020]easy strcmp
主函数内只有这些,提交发现这不是正确答案:
返回去看看init函数有没有进行一些什么操作:
发现它其实调用了一些函数,只不过是通过直接访问地址的形式调用的,双击funcs_889可以看到:
它调用了两个函数sub_6E0和函数sub_795,其中第一个没什么卵用,跟进第二个:
发现这个函数很神奇,将strcmp函数的地址传给了qword_201090,将sub_6EA函数的地址传给了off_201028,双击sub_6EA查看:
发现sub_6EA对参数做了一通操作,然后返回地址在qword_201090的函数的值,而这个函数恰好就是刚赋完值的strcmp函数。在sub_795函数里将sub_6EA函数的地址传给了off_201028,双击off_201028查看:
发现off_201028地址上的函数恰好又是strcmp函数。所以主函数里执行strcmp时其实先执行了sub_6EA函数,再执行strcmp函数,可以写出脚本:
#include <iostream>
using namespace std;
int main()
{
char p[99] = "zer0pts{********CENSORED********}";
long long k[4] = {0, 0x410A4335494A0942, 0x0B0EF2F50BE619F0, 0x4F0A3A064A35282B};
for (int i = 0; i < 4; i++)
{
*(long long *)&(p[i * 8]) += k[i];
}
cout << p;
}
得到flag:l3ts_m4k3_4_DETOUR_t0d4y
[ACTF新生赛2020]Universe_final_answer
是个简单的z3,此题大多数时间用在了z3的安装和调查使用教程,算出key的值之后用IDA动调一下即可:
得到flag:F0uRTy_7w@_42
[WUSTCTF2020]level4
这是个算法题,给出二叉树的中序遍历和后序遍历,求先序遍历。有一点神奇的是在IDA里静态或者动调的时候都没有找到它存放这些数据的地方......
得到flag:This_IS_A_7reE
crackMe
代码逻辑非常清晰,但是代码很长,很恶心:
先后输入用户名和密码,根据用户名在sub_401090()中创建byte_416050[],在loc_4011A0()中创建Format[]和v4[],跟进loc_4011A0()会有一个花指令,nop掉之后可以得知Format是正确提示且v3恒等于1,因此需要sub_401830()返回true,跟进sub_401830():
第一部分将password当成16进制数两两一组分割,存入v14[]。第二部分用v14[]和byte_416050[]创建v16[],最后再用v16创建v13,进行判断。根据v5<8可以得知v14[]长度为8。在sub_410470()中可以得知v16的值是“dbappsec”。跟进sub_401710():
虽然它判断了一堆情况,但是v4是用户名“welcomebeijing”的长度,v3最大为8,因此它只会执行else if的内容。将sub_401830()中第二部分内的两次反动调在汇编码中将jz改为jmp绕过,动调得到创建v16时的byte_416050[]的值。要注意在汇编码中,程序将byte_416050[v12+v7]的值传给了v12,再用v12进行异或,所以如果只看伪代码手动生成是不对的。写出脚本:、
#include <iostream>
using namespace std;
int main()
{
char ans[99] = "dbappsec";
char name[99] = "welcomebeijing";
int XOR[99] = {0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD};
int i, j;
for(i = 0; i < 8; i++)
{
ans[i] ^= name[i];
}
for(i = 0; i < 8; i++)
{
ans[i] ^= XOR[i];
}
for(i = 0; i < 8; i++)
{
printf("%x", ans[i] & 255);
}
return 0;
}
得到密码:39d09ffa4cfcc4cc,将程序中所有反动调全部patch掉然后动调检验,或者直接运行程序会看见程序退出证明成功(因为失败会让你继续输入):
将其md5之后提交显示不正确。百度搜索得知“正确”的解答中并没有分析sub_401710(),得到密码4eb5f3992391a1ae,很明显它不正确:
得到
可以通过提交的flag:d2be2981b84f2a905669995873d6a36c
可以通过程序的flag:84ab2835640e510eb81f86e0ced4d91c
[网鼎杯 2020 青龙组]signal
这个东西滴加密比较复杂,有一张加密流程表,根据表单内容对输入进行各种赋值、异或、加减乘除。逆过来的话有一定的难度,所以,我决定请出angr:
import angr
p = angr.Project('signal.exe', auto_load_libs = False)
init_state = p.factory.entry_state()
s = p.factory.simgr(init_state)
s.explore(find = 0x0040179E, avoid = 0x00401539)
print(s.found[0].posix.dumps(0))
得到flag:757515121f3d478
超!这angr真nm好用!
[GUET-CTF2019]number_game
很奇妙的一道题:
不是很能理解为什么sub_400758()返回值明明是个指针,但在其内部却可以把指针变量赋值给整型变量。在sub_400917()内查看矩阵的样子:
得到flag:1134240024
findKey
又一个非常恶心的C++,主函数东西很少,shift+F12找到关键词“flag{}”,双击跟进发现花指令,nop掉:
之后进入真正恶心的地方,关注函数sub_40101E:
和sub_401005
sub_40101E进去发现:
0x8003u是MD5加密的特征值,因此判定整个函数用于MD5。
对于第一个sub_401005,就是将那一长串绿色的东西每一位异或‘S’,得到的东西应该为MD5之后的输入,在线解密得到123321。而第二个sub_401005并没有找到v5-492对应的值,猜测就是输入,结果证明我猜对了。附上解密脚本
#include <iostream>
using namespace std;
int main()
{
int key[99] = {87, 94, 82, 84, 73, 95, 1, 109, 105, 70, 2, 110, 95, 2, 108, 87, 91, 84, 76};
char ans[99] = "0kk`d1a`55k222k2a776jbfgd`06cjjb";
int i;
for(i = 0; i <= 31; i++)
{
ans[i] ^= 'S';
cout << char(ans[i]);
}
cout << endl;
char k[9] = "123321";
for(i = 0; i <= 18; i++)
{
key[i] ^= k[i % 6];
cout << char(key[i]);
}
return 0;
}
得到flag:n0_Zu0_n0_die
确实,no zuo no die,没事别碰逆向,这个b又干了我4个小时。
[羊城杯 2020]easyre
这个没啥,就是繁,encodeone进行base64,encodetwo乾坤大挪移,encodethree凯撒,逆过来就行
得到flag:672cc4778a38e80cb362987341133ea2
[网鼎杯 2020 青龙组]jocker
打开看见SMC,所以它前面的东西一定是假的,看都不用看。修改完之后得到两个被隐藏的函数encrypt()和Finally()。
encrypt()函数逻辑很清晰,用输入去异或Buffer得到unk_403040。
但是这个Finally()函数中以time(0)作为种子取随机数,也就是说,你是极大概率得不到出题人当时的情况的。经百度,这里需要你去猜,同样也是异或加密,而且是跟v2异或。
根据猜到的结果写出脚本:
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
int ans[999] = {14, 0, 0, 0, 13, 0, 0, 0, 9, 0,
0, 0, 6, 0, 0, 0, 19, 0, 0, 0,
5, 0, 0, 0, 88, 0, 0, 0, 86, 0,
0, 0, 62, 0, 0, 0, 6, 0, 0, 0,
12, 0, 0, 0, 60, 0, 0, 0, 31, 0,
0, 0, 87, 0, 0, 0, 20, 0, 0, 0,
107, 0, 0, 0, 87, 0, 0, 0, 89, 0,
0, 0, 13};
char XOR1[99] = "hahahaha_do_you_find_me?";
char XOR2[99] = "%tp&:";
int flag[99], temp;
int i;
for(i = 0; i < 19; i++)
{
flag[i] = ans[i*4] ^ XOR1[i];
}
temp = '}' ^ ':';
for(i = 0; i < 5; i++)
{
flag[i+19] = temp ^ XOR2[i];
}
for(i = 0; i < 24; i++)
{
printf("%c", flag[i]);
}
return 0;
}
得到flag:d07abccf8a410cb37a
不是很认可这种本地打不通的题。。。
[FlareOn5]Minesweeper Championship Registration
是个java的程序,没逆过,但是拖进IDA就能得到flag:GoldenTicket2018@flare-on.com
firmware
固件逆向,不想学,看这里吧。
得到flag:33a422c45d551ac6e4756f59812a954b
[ACTF新生赛2020]SoulLike
一起来感受3000行屎山代码的美,没有含金量,flag:b0Nf|Re_LiT!
[GWCTF 2019]re3
第一次见,代码自修改问题:
在file/Script command中编写idc脚本:
转成代码并构造函数,得到被隐藏的内容:
使用findcrypt插件可以得知sub_400A71()和sub_40196E()和AES加密有关。unk_603170为秘钥。找到生成它的地方:
插件提示sub_401CF9()为md5加密。从伪代码来看秘钥只与第一和第五个md5有关,然而手动生成与动调结果并不一致。写出解密脚本:
from Crypto.Cipher import AES
from Crypto.Util.number import *
a = [0xBC, 0x0A, 0xAD, 0xC0, 0x14, 0x7C, 0x5E, 0xCC, 0xE0, 0xB1, 0x40, 0xBC, 0x9C, 0x51, 0xD5, 0x2B, 0x46, 0xB2, 0xB9, 0x43, 0x4D, 0xE5, 0x32, 0x4B, 0xAD, 0x7F, 0xB4, 0xB3, 0x9C, 0xDB, 0x4B, 0x5B]
k = [0xCB, 0x8D, 0x49, 0x35, 0x21, 0xB4, 0x7A, 0x4C, 0xC1, 0xAE, 0x7E, 0x62, 0x22, 0x92, 0x66, 0xCE]
ans = ""
key = ""
for i in range(len(a)):
ans += "{:02x}".format(a[i])
for i in range(len(k)):
key += "{:02x}".format(k[i])
ans = int(ans, 16)
key = int(key, 16)
aes = AES.new(long_to_bytes(key), mode = AES.MODE_ECB)
flag = aes.decrypt(long_to_bytes(ans))
print(flag)
得到flag:924a9ab2163d390410d0a1f670
[GXYCTF2019]simple CPP
今天也是讨厌C++逆向的一天呢~
异或的东西可以通过动调得到
然后是
接着是
最后那一大坨考虑使用z3:
from z3 import *
s = Solver()
x = BitVec('x', 64)#用Int进行不了~运算
y = BitVec('y', 64)
z = BitVec('z', 64)
w = BitVec('w', 64)
s.add(z & (~x) == 0x11204161012)
s.add(0x3E3A4717373E7F1F ^ w == 0x3E3A4717050F791F)
s.add(0x11204161012 | (x & y) | (z & (~y)) | (x & (~y)) == 0x3E3A4717373E7F1F)
s.add((z & (~y)) & x | z & ((x & y) | y & ~x | ~(y | x)) == 0x8020717153E3013)
s.add(0x11204161012 | (x & y) | y & z == ~x & z | 0xC00020130082C0C)
if s.check():
r = s.model()
for i in r:
print(i, hex(r[i].as_long()))
将得到的答案在异或回去:
#include <stdio.h>
int main()
{
long long ans[99];
char XOR[] = "i_will_check_is_debug_or_not";
long long i, j, flag[99];
ans[1] = 865872043546520588;
ans[0] = 4483973367147818765;
ans[3] = 842073600;
ans[2] = 577031497978884115;
for(i = 0; i < 4; i++)
{
if(i == 3)
{
for(j = 0; j < 4; j++)
{
flag[i*8+j] = (ans[i] & 0xFF000000) >> 24;
ans[i] = ans[i] << 8;
}
}
else
{
for(j = 0; j < 8; j++)
{
flag[i*8+j] = (ans[i] & 0xFF00000000000000) >> 56;
ans[i] = ans[i] << 8;
}
}
}
for(i = 0; i < 27; i++)
{
flag[i] ^= XOR[i];
printf("%c", flag[i]);
}
return 0;
}
然后就可以得到乱码We1l_D0ndaQbg�_Slgebra_am_i
经百度,是题错了,z3解出来不止一组解。挺神奇的,5个条件约束不住4个数。
得到flag:We1l_D0ne!P0or_algebra_am_i
[FlareOn5]Ultimate Minesweeper
get了一个dnSpy的新玩法。拖进去容易找到加密函数:
但是仔细看一看发现它用revealedCells作为种子生成了一个随机数数组array,之后在加密输出。因此想法是动调,查看一下会让我们失败的函数:
根据英文提示,第一个if就是失败判定,把它右键、编辑方法,给它注释掉:
右下角编译之后Ctrl+shift+s保存为新程序,运行新程序,找到三个非雷地块:
之后再在正常的程序里点击这三个地块,得到flag:Ch3aters_Alw4ys_W1n@flare-on.com
[MRCTF2020]PixelShooter
附件是一个apk,但是里面有很多unity的关键字,所以去找Assembly-CSharp.dll:
导出后拖进dnSpy,在UIControler方法中找到:
得到flag:Unity_1S_Fun_233
[FlareOn1]Bob Doge
又是一个C#的程序,拖入dnSpy,容易找到加密函数:
发现需要寻找dat_secret,可惜找不到(话说,这玩意儿是怎么藏起来的?),所以准备动调:
在数据栏中得到flag:3rmahg3rd.b0b.d0ge@flare-on.com
[2019红帽杯]xx
红帽杯的题总能让我眼前一黑
首先判断输入位数为19位,然后判断输入的值是不是在特定字符串里。
然后构造key,发现key保留了输入的前4个,其余的赋0,再将其转为4个32位数用于XXTEA。然后你需要坚定不移地猜输入的前4个就是"flag"。XXTEA的代码是真心没看懂,但好在他是标准的。通过动调可以知道如果输入为1234,在XXTEA中input会变成34333231,解密时要注意大小端序。
对加密结果进行打乱,并且进行了一个离谱的异或。解异或需要从后往前解。
最后进行对比,这种赋值方式也会产生大小端序的问题,算不过来的话......动调吧!骚年!
写出脚本:
#include <stdio.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void XXTEA(unsigned int *v, int n, unsigned int key[4])
{
unsigned int y, z, sum;
unsigned p, rounds, e;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
{
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
int main()
{
int ans[99] = {206, 188, 64, 107, 124, 58, 149, 192, 239, 155, 32, 32, 145, 247, 2, 53, 35, 24, 2, 200, 231, 86, 86, 250}, enc[99];
int v21 = 23, v22 = 23, i, j;
for(; v21 >= 1; v22--)
{
int v23 = 0;
if(v21 / 3 > 0)
{
do
{
ans[v22] ^= ans[v23];
v23++;
} while (v23 < v21 / 3);
}
v21--;
}
enc[2] = ans[0];
enc[0] = ans[1];
enc[3] = ans[2];
enc[1] = ans[3];
enc[6] = ans[4];
enc[4] = ans[5];
enc[7] = ans[6];
enc[5] = ans[7];
enc[10] = ans[8];
enc[8] = ans[9];
enc[11] = ans[10];
enc[9] = ans[11];
enc[14] = ans[12];
enc[12] = ans[13];
enc[15] = ans[14];
enc[13] = ans[15];
enc[18] = ans[16];
enc[16] = ans[17];
enc[19] = ans[18];
enc[17] = ans[19];
enc[22] = ans[20];
enc[20] = ans[21];
enc[23] = ans[22];
enc[21] = ans[23];
unsigned int flag[99];
for(i = 0; i < 6; i++)
{
for(j = 3; j >= 0; j--)
{
flag[i] = (flag[i] << 8) + enc[i*4+j];
}
}
unsigned int key[4] = {0x67616c66, 0, 0, 0};
XXTEA(flag, 6, key);
for(i = 0; i < 6; i++)
{
for(j = 0; j < 4; j++)
{
printf("%c", flag[i] & 0xFF);
flag[i] >>= 8;
}
}
return 0;
}
得到flag:CXX_and_++tea
[CFI-CTF 2018]IntroToPE
这道题就比较水了,打开找到判断函数:
可以看出,应当使validatePassword.verifyPassword()返回真值,双击跟踪:
看见是一个base64,理论上应该接着去找加密使用的对应表,可惜没找到,就用默认的吧。
得到flag:.NetC#_1s_@w3s0m3
equation
非常sb的一道题,先jsfuck,再z3。
得到flag:A_l0ng_10NG_eqU4Ti0n_1s_E4Sy_W1Th_z3
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix