吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3167|回复: 14
上一主题 下一主题
收起左侧

[CTF] 2024帕鲁杯reverse复现

[复制链接]
跳转到指定楼层
楼主
xiaowaaa 发表于 2024-4-25 22:10 回帖奖励
本帖最后由 爱飞的猫 于 2024-4-30 04:58 编辑

1. 茶:

1.1. 经典查壳

64位无壳软件:

1.2. ida 启动

main 函数还是比较复杂的,他的流程大概就是输入 flag,然后经过加密,在经过 if 语句判断与密文进行对比,我们使用 ida 的查询加密算法的插件来进行查询一下他的加密算法:

他给出的是salsa20加密,但是结果并不是这个加密算法,所以还是要靠自己的经验来观察

这个函数就是我们所要进行解密的函数,这个加密其实是 chacha20 加密,经过搜索可以得到 chacha20 加密他需要一个密钥 key,一个偏移量 ponce,其中偏移量是一个 12 位的,但是他给的是 16 位,经过动调发现,他只取了前 12 位,下面给出解密代码(使用 python 的库进行解密,这个 Crypto 是真的难安装)

from Cryptodome.Cipher import ChaCha20

enc_data = bytes.fromhex("f568c48912eed6dc520c7164f44b6378e1d0d3e248914fa8847b405a131f")

key = b"SGludDogSW1wcm92ZSBvZiBTYWxzYTIw"
nonce = b"Is_This_"

cc = ChaCha20.new(key=key, nonce=nonce)

print(cc.decrypt(enc_data))

flag 就是 flag{But_I_Like_ChaCha20_More}

chacha20加密参考文章:链接已丢失,需要补上

2. Pypl

记录一下这个 python 逆向的解决

2.1. 解包

首先可以观察到这个是一个 python 进行打包的程序所以使用 pyinstxtractor 解包。

我刚开始使用的是低于 2.0 的版本,这个解包出来的 pyc 不带后缀,而且也没有自动填补程序头首先将所要解包的程序放在与其 pyinstxtractor 放在一起,然后打开终端运行python pyinstxtractor.py 解包.exe  (格式),执行完毕会看到 successful,而且会生成一个文件夹:

上面的文件夹就存放着我们解包的 pyc 文件,我们往他里去找根文件夹名字相同的文件。
然后如果没有 pyc 的后缀,我们手动添加,最后拉入到 010editor 中进行查看文件头。

如果缺少 magic 头,我们就应该重新修改,所以我们还是使用 2.0 以上的版本吧,方便快捷此时我们可以通过一个 dll 文件进行查询他的 python 版本

所以这是一个python311的版本贴一下各个版本的magic头

enum PycMagic {
    MAGIC_1_0 = 0x00999902,
    MAGIC_1_1 = 0x00999903, /* Also covers 1.2 */
    MAGIC_1_3 = 0x0A0D2E89,
    MAGIC_1_4 = 0x0A0D1704,
    MAGIC_1_5 = 0x0A0D4E99,
    MAGIC_1_6 = 0x0A0DC4FC,

    MAGIC_2_0 = 0x0A0DC687,
    MAGIC_2_1 = 0x0A0DEB2A,
    MAGIC_2_2 = 0x0A0DED2D,
    MAGIC_2_3 = 0x0A0DF23B,
    MAGIC_2_4 = 0x0A0DF26D,
    MAGIC_2_5 = 0x0A0DF2B3,
    MAGIC_2_6 = 0x0A0DF2D1,
    MAGIC_2_7 = 0x0A0DF303,

    MAGIC_3_0 = 0x0A0D0C3A,
    MAGIC_3_1 = 0x0A0D0C4E,
    MAGIC_3_2 = 0x0A0D0C6C,
    MAGIC_3_3 = 0x0A0D0C9E,
    MAGIC_3_4 = 0x0A0D0CEE,
    MAGIC_3_5 = 0x0A0D0D16,
    MAGIC_3_5_3 = 0x0A0D0D17,
    MAGIC_3_6 = 0x0A0D0D33,
    MAGIC_3_7 = 0x0A0D0D42,
    MAGIC_3_8 = 0x0A0D0D55,
    MAGIC_3_9 = 0x0A0D0D61,
    MAGIC_3_10 = 0x0A0D0D6F,
    MAGIC_3_11 = 0x0A0D0DA7,
    MAGIC_3_12 = 0x0A0D0DCB,

    INVALID = 0,
};

参考资料: Python逆向全版本MagicNumber表

接下来就是准备将 pyc 反编译为 python 代码。

2.2 反编译

这里我尝试使用uncompyle6进行反编译,但是一直报错,我要不知道什么原因导致的,贴个图记录一下错误看看以后可以解决吗:

所以我还是使用在线网站将其反编译了

使用这个可以解决得到代码:

#!/usr/bin/env python
# Version: Python 3.11

from Crypto.Util.number import bytes_to_long

def enc(key):
    R = bytes_to_long(b'Welcome To PaluCTF!')
    MOD = 2 ** 418
    R = R ^ R - 60 >> 24
    R = R ^ R - 60 << 88
    R ^= key
    R = -R ** 2 * 2024 % MOD
    R = R * key % MOD
    return R

flag = input('Welcome To PaluCTF!\nInput FLAG:')
m = bytes_to_long(flag.encode())
cor = 0x2E441F765514CCA89173554726494D37E9FBE774B6F807BC5F6E71117530CE3D7DB5F70554C03CD9055F4E42969600904DF1F4DB8L
if enc(m) == cor:
    print('Congratulation!')
    return None
print('Wrong FLAG!')

下面解决这个加密,设计异或和位运算所以使用这Z3对其进行解密给一下代码:

from z3 import *
from Cryptodome.Util.number import *

def enc(key):
    R = bytes_to_long(b"Welcome To PaluCTF!")
    MOD = 2**418
    R = R ^ ((R - 60) >> 24)
    R = R ^ ((R - 60) << 88)
    R ^= key
    R = (-R * R * 2024) % MOD
    R = (R * key) % MOD
    return R
answer = 0x2E441F765514CCA89173554726494D37E9FBE774B6F807BC5F6E71117530CE3D7DB5F70554C03CD9055F4E42969600904DF1F4DB8

s=Solver()
key=BitVec('key',418)
s.add(enc(key)==answer)
s.check()
res=s.model()
#print(res)
flag=long_to_bytes(res[key].as_long())
print(flag)

3. O2

3.1. 经典查壳:

32位elf程序

3.2. IDA启动

可以发现 ELF 的头文件报错了:

所以我们找一个 32 位 ELF 文件参考一下:

下面是正常的:

所以我们修改一下,将偏移 0x0005 处的 01 修改为 02,意思是这是一个64位的程序:

1 32位程序
2 64位程序

成功进入主函数但是我的 main 函数不能反编译,显示编译错误,这个我没查出来是什么原因。

网上教程说可以通过动调解决一下,就是找到出错的地址,然后F5反编译一下,然后返回就可以F5了试了试果然可以。

得到了主函数:

__int64 __fastcall main(int a1, char ** a2, char ** a3) {
    __int64 v3; // rax
    std::ostream * v4; // rbx

    __int64 v5; // rax
    _BYTE * v6; // rbp
    char v7; // si
    std::ostream * v8; // rax
    __int64 v9; // rdx
    size_t v10; // rbx

    __int64 v11; // r13
    __int64 v12; // rdx

    __int64 v13; // rbx
    __int64 v14; // rdx

    __int64(__fastcall * v16)(); // rax
    _BYTE v17[32]; // [rsp+0h] [rbp-68h] BYREF
    void * s1; // [rsp+20h] [rbp-48h] BYREF
    size_t n; // [rsp+28h] [rbp-40h]

    v3 = std::operator << < std::char_traits < char >> ( & std::cout, "Hello! ", a3);
    v4 = (std::ostream * ) std::__ostream_insert < char, std::char_traits < char >> (v3, qword_607AF3F30340, qword_607AF3F30348);
    v5 = * (_QWORD * )( * (_QWORD * ) v4 - 24 LL);
    v6 = * (_BYTE ** )((char * ) v4 + v5 + 240);
    if (!v6)
        std::__throw_bad_cast();
    if (v6[56]) {
        v7 = v6[67];
    } else {
        std::ctype < char > ::_M_widen_init( * (_QWORD * )((char * ) v4 + v5 + 240));
        v7 = 10;
        v16 = * (__int64(__fastcall ** )())( * (_QWORD * ) v6 + 48 LL);
        if (v16 != std::ctype < char > ::do_widen)
            v7 = ((__int64(__fastcall * )(_BYTE * , __int64)) v16)(v6, 10 LL);
    }
    v8 = (std::ostream * ) std::ostream::put(v4, v7);
    std::ostream::flush(v8);
    std::operator << < std::char_traits < char >> ( & std::cout, "Check Flag:", v9);
    std::operator >> < char > ( & std::cin, & unk_607AF3F30360);
    sub_607AF3F2D5C0(v17, & unk_607AF3F30360, & qword_607AF3F30340);
    sub_607AF3F2D6C0((__int64) & s1, (__int64) v17);
    v10 = n;
    v11 = qword_607AF3F30388;
    v12 = qword_607AF3F30388;
    if (n <= qword_607AF3F30388)
        v12 = n;
    if (v12 && memcmp(s1, obj, v12) || (v13 = v10 - v11, v13 > 0x7FFFFFFF) || v13 < (__int64) 0xFFFFFFFF80000000 LL) {
        std::string::_M_dispose( & s1);
        goto LABEL_14;
    }
    std::string::_M_dispose( & s1);
    if ((_DWORD) v13) {
        LABEL_14: std::operator << < std::char_traits < char >> ( & std::cout, "Wrong!", v14);
        goto LABEL_12;
    }
    std::operator << < std::char_traits < char >> ( & std::cout, "Right!", v14);
    LABEL_12:
        std::string::_M_dispose(v17);
    return 0 LL;
}

观察一下,我们可以发现 sub_607AF3F2D5C0(v17, &unk_607AF3F30360, &qword_607AF3F30340); 就是加密函数

__int64 * __fastcall sub_607AF3F2D5C0(__int64 * a1, char ** a2, _QWORD * a3) {

    __int64 * v3; // rcx
    char * v4; // r12
    __int64 v6; // r15
    int v7; // ebp

    __int64 v8; // rax
    unsigned __int64 v9; // r13
    unsigned __int64 v10; // rax
    int v12; // [rsp+Ch] [rbp-4Ch]
    char * v13; // [rsp+10h] [rbp-48h]

    v3 = a1 + 2;
    *((_BYTE * ) a1 + 16) = 0;
    * a1 = (__int64)(a1 + 2);
    v4 = * a2;
    a1[1] = 0 LL;
    v13 = & a2[1][(_QWORD) v4];
    if (v4 != v13) {
        v6 = 0 LL;
        v7 = 0;
        while (1) {
            v9 = v6 + 1;
            v12 = ( * v4 + * (char * )( * a3 + v7)) % 128;
            v10 = a1 + 2 == v3 ? 15 LL : a1[2];
            if (v10 < v9) {
                std::string::_M_mutate(a1, v6, 0 LL, 0 LL, 1 LL);
                v3 = (__int64 * ) * a1;
            }
            ++v4;
            *((_BYTE * ) v3 + v6) = v12;
            v8 = * a1;
            a1[1] = v9;
            *(_BYTE * )(v8 + v9) = 0;
            v7 = (unsigned __int64)(v7 + 1) % a3[1];
            if (v13 == v4)
                break;
            v6 = a1[1];
            v3 = (__int64 * ) * a1;
        }
    }
    return a1;
}

v12 = (*v4 + *(char [i])(a3 + v7)) % 128; 这一句就是主要的加密函数可以简单分析一下各个变量的意思

  • v4 就是我们输入的值,也就是 flag(enc)
  • a3 就是传入的 key,我们可以找到他也就是 v12=(enc[i]+key[i])% 128

所以我们进行解密:

enc=bytes.fromhex("364d4d5c3e387e00421c597a0a7302144d5b70087e064619567336297d151f56770a7935424f2a780643")
key=b'PaluCTF'
flag=''
for i in range(len(enc)):
    flag+=chr((enc[i]-key[i%7])%128)

print(flag)

解释一下代码,方便下次自己使用。

bytes.fromhex 是将连续的两个数转换为 hex:

hex_string = '616263'
byte_array = bytearray.fromhex(hex_string)
print(byte_array)

bytearray(b'abc')

还有一种解法就是爆破,学习一下大佬的答案:

from string import printable

enc = bytearray.fromhex("364d4d5c3e387e00421c597a0a7302144d5b70087e064619567336297d151f56770a7935424f2a780643")

key = "PaluCTF"

for i in range(len(enc)):
    for c in printable:
        if (ord(c) + ord(key[i % len(key)])) % 128 == enc[i]:
            print(c, end="")
            break

这里的 from string import printable

# import string library function  
import string  

# Storing the sets of punctuation, 
# digits, ascii_letters and whitespace 
# in variable result  
result = string.printable 

# Printing the set of values  
print(result) 

输出结果:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+, -./:;<=>?@[\]^_`{|}~ 

返回一系列可打印的值,所以我们使用双重循环进行爆破就ok了

答案是 flag{d80a0d76-23af-486e-a0bc-43a463eac552} 剩下两道不是很懂,再分析分析再写吧,帕鲁杯收获还是挺大的,对于加密算法也是又了解到一种。


修改记录:

  • 20240429: 修正排版

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
笙若 + 1 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
爱飞的猫 发表于 2024-4-26 02:37

图一个都没有成功贴上哦,建议使用 Markdown 语法并参考帮助修改:


3#
liyezhi7742 发表于 2024-4-26 10:49
贴图没了,链接也没有打好(下次建议编译成文档上传
4#
yiting8 发表于 2024-4-26 11:57
5#
nnelqw 发表于 2024-4-26 15:44
重新编辑下吧,图烂完了
6#
sundeheng 发表于 2024-4-26 17:05
排版好乱啊,也都没图
7#
hellostarrysky 发表于 2024-4-27 06:49
厉害,学习到了!
8#
没头脑和温柚 发表于 2024-4-28 01:21
友情提示 贴图错误
9#
 楼主| xiaowaaa 发表于 2024-4-29 22:42 |楼主
爱飞的猫 发表于 2024-4-26 02:37
[md]图一个都没有成功贴上哦,建议使用 Markdown 语法并参考帮助修改:

- [【公告】发帖代码插入以及添 ...

抱歉抱歉,我不知道没传上去,马上改
10#
 楼主| xiaowaaa 发表于 2024-4-29 22:43 |楼主

马上改佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 10:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表