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

posted @ 2022-11-22 10:34  Clovershrub  阅读(290)  评论(0编辑  收藏  举报