攻防世界逆向刷题笔记(新手模式9-21)

bad_python

看样子是pyc文件损坏了。利用工具打开,发现是MAGIC坏了。搜下也没有头绪。

攻防世界-难度1- bad_python - _rainyday - 博客园

python Magic Number对照表以及pyc修复方法 - iPlayForSG - 博客园

看WP才知道36已经提示了pyc版本了。参考第二个文章,除了魔法数字还有8字节的额外信息。

但是我填充之后还是bad magic,再看看WP咋说的。原来代表文件大小的地方还不能为0.也就是第12个字节。

我们只需要修复一下就可以了。 妈的,用FF也不行,看来必须中规中矩才行。用E5成功了。、

 识别出是XTEA加密,写出解密即可。后期我这又出问题了,这一步就不搞了吧

ereere

左边没有main了,依据start必定会出现main函数,一个个点找到了疑似main的函数。

base64换表+RC4。思路同攻防世界RE_ereere_攻防世界ereere-CSDN博客

用cyberchef解决的

easyEZbaby_app

定位到关键代码。主要看checkpass和checkUsername函数

首先是username,密码是zhishixuebao的MD5,以2为步长取。结果是:7afc4fcefc616ebd

pass则是如下。长度显然是15位,注意算法要求是char数组=0,说明赋值等式右边值是0(也就是48)。写出Python代码即可。

a=''
for i in range(15):
   a+=chr(((255 - i) + 2) - 98-48)
print(a)

toddler_regs 

导入pdb文件,放进IDA分析。

持续跟踪,首先看到stage1,要求参数必须是要求的值,只能动调修改这个参数。

但是看起来没啥用啊,看看stage2.

发现stage1的用处了,他会把黄框部分设置为23.我们直接找到这个对应的值即可。注意team是个字符串数组,我们导出来看看,直接在C里面运行吧。

第一个team输出了:0Int

第二个代码和上面一样,结果是:nTJnU

组合起来就是flag{0Int_1s_n1c3_but_nTJnU_is_we1rd}

上面显然是错误的,可能会遇到这个地方刚好是\0直接截断。保险的做法是先定义好数组大小(如char[512][10],再直接去掉&运行即可)

exp:

也可以动调做,我电脑不知为何运行不起来这个软件,只能静态分析了。

easyre-xctf

看题目简介估计有壳。是UPX原版壳,官方工具就可以脱壳了。

但并没有明显的算法,只在字符部分看到了疑似flag的后半段。双击这里有一个符号名part2。

动调看看有没有提示,拿拿数据。

在内存发现了疑似的部分

根据其地址定位到函数,这就是第一个flag。函数名字有个part1,对应着part2.两个16进制转换为字符后就是part1.

七分靠猜,这里有运气成分,正常来说估计得搜part1了。

CatFly

CatFly【汇编代码还原】 - yuanyy720 - 博客园

看主函数很复杂。慢慢来看吧。

运行出来是个这,输入的东西会在左下角显示。那么main函数有的部分就可以不看了。

看到一个提示:

要记住逃逸密码??

难道是迷宫,分析这部分,\x1Bxxx是终端转义啊,看着是打印图形用的。

百思不得姐,还是看看WP吧。

re学习(29)攻防世界-CatFly(复原反汇编)_攻防世界catfly-CSDN博客

---------------------------------------------------------------------------------------------

观看这二个WP,感觉逆向也是先不必要考虑每行代码,大致知道意思或猜测即可,着重关注目标相关的代码(找特征之类)最后也知道了关键代码,这样每个代码的意思都清晰了。

而且要擅长用交叉引用。

这个题关注到了另类的printf,其他的感觉都是输出彩虹猫或字符串。这也是结合运行的程序得出的结果。

看看这个off_FA88.在上面不远处就交叉引用了一次,作为返回值a2、a3都是计数器。

其功能为:当a2!=18,a3≤4||a3>54时off_FA88不变(v12的值就是off_FA88),所以此处相当于是k=18(一次)且m∈(4,53)时会执行sub_6314函数,即循环执行50次。--参考博客

再看看这俩也被使用的函数。

这个估计是来给E120赋值的。

E1E8在这里也会被增加

这个就是进一步处理。

 

具体EXP参考博客吧,懒得看了。 

好像用这个算法了:伪随机数生成算法(1)线性同余法_伪随机数 同余-CSDN博客

IgniteMe

跟进到主函数。

首先str长度需要大于4.

先来一个循环把str循环复制给v7。后两个if判断意思是大写转小写,小写转大写。

最关键的在后院吗,str2我们已经知道了,这就是目标。byte我们也知道,目标就是求v7。接下来的sub_4013c0跟进一下。

 是一个表达式,相当于v7^0x55+72.那就很明显了,写出对应exp:

str2='GONDPHyGjPEKruv{{pj]X@rF'
byte=[ 0x0D, 0x13, 0x17, 0x11, 0x02, 0x01, 0x20, 0x1D, 0x0C, 0x02, 
  0x19, 0x2F, 0x17, 0x2B, 0x24, 0x1F, 0x1E, 0x16, 0x09, 0x0F, 
  0x15, 0x27, 0x13, 0x26, 0x0A, 0x2F, 0x1E, 0x1A, 0x2D, 0x0C, 
  0x22, 0x04]
flag=''
#根据str2猜测长度是24
for i in range(24):
    flag+=chr(((byte[i]^ord(str2[i]))-72)^0x55)
print(flag)
print(flag.lower())

出来的flag都是大写,不要忘记两个if判断是大写转小写小写转大写,因此再次转为小写即可

BABYRE

main函数看着很简单

需要注意:在 C 语言中,(*judge)(s) 这种语法表示通过函数指针调用函数。在 C 语言中,函数名本质上是指向函数代码起始地址的指针。可以将这个地址存储在一个函数指针变量中,然后通过该变量调用函数。

也就是说这是一段自修改代码。judge是函数,需要解密这段judge。

先用AI生成(因为我不太会IDA python)一段解密的脚本在IDA运行,然后C--P加载为函数即可

import idautils
import idaapi

base = 0x600b00

# 处理182个字节(与原始代码一致,0到181共182次)
for i in range(182):
    # 读取原始字节
    original_byte = idaapi.get_byte(base + i)
    
    # 异或解密
    decrypted_byte = original_byte ^ 0xC
    
    # 写入解密后的字节
    idaapi.patch_byte(base + i, decrypted_byte)
    
    # 标记为数据(这一行删了就行)无用
    idaapi.create_data(base + i, FF_BYTE, 1, idaapi.BADADDR)

print(f"已解密 {base:X} 开始的182个字节")

再次从main函数进入judge即可了。 直接点进去judge好像部分代码还是错误,重新从main进去就好了。

这个加密逻辑就很明显了。

a='fmcd\x7Fk7d;V`;np'
flag=''
for i in range(len(a)):
    flag+=chr(ord(a[i])^i)
print(flag)

parallel-comparator-200

这一题直接给了C代码。有点意思。

注意到代码的flag_len是20.

首先认识一个新函数

逐步分析,main函数调用了下图的highly_.....函数。

跟进此函数。根据is_ok的判断条件,着重关注此部分,说明result值是0.因为is_ok的条件为generated==just_a_string。

查找用到result的地方。注意到只有first_letter未知,user_string是我们的目标。

最后一行代码实际上经过上文知识点铺垫,调用了checking函数。

由于result==0,说明argument[0]+argument[1]严格相等于argument[2]。会循环20次这个checking函数(因为pthread_creat函数在for循环内部循环了20次)

0我们不知道,(起码知道是某个小写字母)2是目标,1知道了。而且2就等于1+0.但是这还不太够啊,看看还有没有其余条件,似乎没了。

先尝试写一个爆破脚本试试。这里要注意,一定要多生成几个flag,因为不同的frist_letter对应不同的flag,我们试着a~z都搞一遍。第一次没想到这样遍历。而且代码美观不如豆包写的,还得多写多练。

、注意{}是占位符,可以写任意的表达式。

a = [0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7]

# 尝试所有可能的基准值b (97-122)
for base in range(97, 123):
    flag = []
    valid = True
    
    for i in a:
        char_code = base + i
        if 32 <= char_code <= 126:
            flag.append(chr(char_code))
        else:
            valid = False
            break  # 这个基准值无效
    
    if valid:
        print(f"基准值 {chr(base)} ({base}): {''.join(flag)}")

发现flag 

很让我开心的是我找到了大致思路!哈哈哈哈哈。只是脚本功底还比较薄弱。

secret-galaxy-300

注意到程序是打印了类似于星球的东西。

但是观察函数发现其实并没有完全打印出来,a1这个数组的部分内容并没有完全用到,所以我打算动调拿一波数据看看。

动调看a1并没发现明显的字符串

攻防世界逆向高手题之secret-galaxy-300-CSDN博客

看了别人的wp,反思一下,还是思路错了,这时候可以结合题目的提示,隐藏的星系,确实发现一个没有被打印出来的星系,点击ctrl+x查看交叉引用:

(而且我说为啥有三个代码差不多的文件,原来对应不同平台)

其实这里就可以看出来这估计就是flag了,这里偷了个懒,使用了AI。

# 定义各个星系名称字符串
ngs_2366 = "NGS 2366"
andromeda = "Andromeda"
messier = "Messier"
sombrero = "Sombrero"
triangulum = "Triangulum"
dark_secret_gala = "DARK SECRET GALAXY"

# 将各个星系名称转换为字符列表,方便按索引取值
galaxy_strings = {
    "ngs_2366": list(ngs_2366),
    "andromeda": list(andromeda),
    "messier": list(messier),
    "sombrero": list(sombrero),
    "triangulum": list(triangulum),
    "dark_secret_gala": list(dark_secret_gala)
}

def build_result_string():
    """构建结果字符串"""
    result_chars = [
        galaxy_strings["andromeda"][8],
        galaxy_strings["triangulum"][7],
        galaxy_strings["messier"][4],
        galaxy_strings["andromeda"][6],
        galaxy_strings["andromeda"][1],
        galaxy_strings["messier"][2],
        '_',
        galaxy_strings["andromeda"][8],
        galaxy_strings["andromeda"][3],
        galaxy_strings["sombrero"][5],
        '_',
        galaxy_strings["andromeda"][8],
        galaxy_strings["andromeda"][3],
        galaxy_strings["andromeda"][4],
        galaxy_strings["triangulum"][6],
        galaxy_strings["triangulum"][4],
        galaxy_strings["andromeda"][2],
        '_',
        galaxy_strings["triangulum"][6],
        galaxy_strings["messier"][3]
    ]
    return ''.join(result_chars)

# 直接构建并打印结果字符串
print("构建的结果字符串:", build_result_string())

其实直接动调很快

simple-check-100

打开也是三个文件,有了前面的经验,这里面的代码一定是一样的

不如直接打开exe,动调起也方便

怀疑这部分的result就是最终的flag。

为了便于调试分析,我想使用动调的方法来做这道题。目前有两个思路,一个是直接改汇编代码,让这个条件强制为真,另一个是利用check_key来找v8的值,但是很遗憾看代码这个是v7地址对应的数组,估计不方便寻找和爆破。方便起见,先直接改汇编代码看看。

好吧,这种思路看着不太行的样子。OD出来的是乱码。估计是GBK编码不可以处理,这个估计不是GBK编码,

需要注意用X32得用管理员模式,刚才我patch了估计就是没管理员模式,他不认,IDA动调的patch估计没有静态的好使,patch了还是说wrong.

参考了攻防世界-simple-check-100_攻防世界 simple-check-100-CSDN博客看来这种动调还是找专业的调试器吧

我看人家都是用GDB动调的。linux估计不乱码。

新学的调试指令:set $eax=1

这里需要注意在这里改:,估计箭头跑到这里意思就是这个指令执行完毕了已经。

果不其然出来了flag。

接下来试试静态分析

注意参数本来是字符,搞成了int,因此本来一字节,导致合并为4字节。

还要注意这是一个嵌套for循环。 

这借鉴了攻防世界 simple-check-100(非常好的一篇wp,日后还得多多观看,受益匪浅)-CSDN博客

#include <stdio.h>
int main(int argc, char *argv[])
{
    unsigned char flag_data[] = {
        220, 23, 191, 91, 212, 10, 210, 27, 125, 218,
        167, 149, 181, 50, 16, 246, 28, 101, 83, 83,
        103, 186, 234, 110, 120, 34, 114, 211};
    char v7[] = {
        84, -56, 126, -29, 100, -57, 22, -102, -51, 17,
        101, 50, 45, -29, -45, 67, -110, -87, -99, -46,
        -26, 109, 44, -45, -74, -67, -2, 106};
    unsigned int v2;
    unsigned char *v3;
    // v11 = -478230444;
    // v12 = -1709783196;
    // v13 = 845484493;
    // v14 = 1137959725;
    // v15 = -761419374;
    // v16 = -752063002;
    // v17 = -74;
    // v18 = -67;
    // v19 = -2;
    // v20 = 106;
    // 方法一:类比写
    for (int i = 0; i <= 6; ++i)
    {
        v2 = ((v7[4 * i] & 0x000000FF) + ((v7[4 * i + 1] & 0x000000FF) << 8) + ((v7[4 * i + 2] & 0x000000FF) << 16) + ((v7[4 * i + 3] & 0x000000FF) << 24)) ^ 0xDEADBEEF;
        v3 = (unsigned char *)&v2;
        for (int j = 3; j >= 0; --j)
        {
            printf("%c", *(v3 + j) ^ flag_data[4 * i + j]);
        }
    }
    // 方法二:按位异或,小端字节序,低位在低地址
    // for (int i = 0; i <= 6; ++i) {
    // printf("%c", v7[4 * i + 3] ^ 0xDE ^ flag_data[4 * i + 3]);
    // printf("%c", v7[4 * i + 2] ^ 0xAD ^ flag_data[4 * i + 2]);
    // printf("%c", v7[4 * i + 1] ^ 0xBE ^ flag_data[4 * i + 1]);
    // printf("%c", v7[4 * i] ^ 0xEF ^ flag_data[4 * i]);
    // }
    return 0;
}

代码分析:v2 的计算逻辑

这行代码的核心是 将 4 个有符号字节组合成一个 32 位无符号整数,然后与 0xDEADBEEF 进行异或运算。让我逐步拆解这个过程:

1. 为什么需要 & 0x000000FF

  • v7 是一个 有符号字符数组char v7[]),其中的负数(如 -56)在内存中以补码形式存储(例如 -56 的补码是 0xC8,即十进制的 200)。

  • 问题:当将有符号数扩展为更大的类型(如 int)时,符号位会被保留。例如:

    • v7[1] 的值是 -56(二进制 0xC8),如果直接转换为 int,会变成 0xFFFFFFC8(因为符号位扩展)。

    • 这会导致后续的位运算(如 << 8)结果错误。

  • 解决方法:通过 & 0xFF 将其截断为无符号字节:

    • -56 & 0xFF → 0xC8(十进制 200),确保高 24 位为 0

2. 为什么要进行位移操作(<< 8<< 16<< 24)?

这是 小端序(Little Endian)字节组合 的过程:

  • 假设有 4 个字节 b0, b1, b2, b3,对应的索引为 4*i 到 4*i+3

  • 在小端序中,低字节存放在低地址,高字节存放在高地址。因此,组合成 32 位整数的公式是:

    plaintext

    value = b0 + (b1 << 8) + (b2 << 16) + (b3 << 24)
    
  • 示例
    假设 v7[4*i] 到 v7[4*i+3] 的值为 [84, -56, 126, -29](即 [0x54, 0xC8, 0x7E, 0xE3]):

    • v7[4*i] & 0xFF → 0x54(低字节)

    • v7[4*i+1] & 0xFF → 0xC8

    • v7[4*i+2] & 0xFF → 0x7E

    • v7[4*i+3] & 0xFF → 0xE3(高字节)

    • 组合后的值:0xE37EC854(注意字节顺序颠倒,符合小端序)。

主要是因为将字符指针弄成整数的,再加上端序转换,搞得很麻烦。 

re1-100

略微参考攻防世界逆向高手题之re1-100-CSDN博客

开局这些都是反调试

函数功能与原理

  • 管道概念:管道是一种进程间通信(IPC,Inter - Process Communication)机制,用于在具有亲缘关系(如父子进程)的进程间传递数据。它本质上是一个特殊的文件,遵循先进先出(FIFO)原则。

  • 函数作用:在许多编程语言和系统(如 C、C++ 等基于 POSIX 标准的系统编程中),pipe 函数用于创建一个匿名管道pipe 函数通常接受一个包含两个文件描述符的数组作为参数(这里 pParentWrite 应该是数组首地址),其中一个文件描述符用于读取管道数据(通常是数组的第一个元素,记为 fd[0]),另一个用于向管道写入数据(通常是数组的第二个元素,记为 fd[1]

  • 成功时:返回 0
  • 失败时:返回 -1,并设置 errno 来指示具体错误类型。

由此我们已经知道pparentread/pparentwrite都是什么意思。

这个代码大概意思就是判断输入的是不是完整。根据我的判断。 

关键应该是这个:

顺序怎么不一样,还得进入confusekey看看。大致意思是每十个一组,按照3412组的顺序进行排列。排列之后就满足了所有的条件哦。

exp:

a = 'daf29f59034938ae4efd53fc275d81053ed5be8c'
part1 = ''
part2 = ''
part3 = ''
part4 = ''

for i in range(len(a)):
    if i <= 9:
        part1 += a[i]
    elif 10 <= i <= 19:
        part2 += a[i]
    elif 20 <= i <= 29:
        part3 += a[i]
    elif 30 <= i <= 39:
        part4 += a[i]

print(part3 + part4 + part1 + part2)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值