攻防世界逆向高手进阶(持续更新)

[toc]

dmd-50

做题实录

附件dMd是个64位ELF文件,运行一下,让输入key,随便输入一个,报错。

1565588951505

IDA打开看一下

2019-08-12_135045

直接看if判断,发现字符串780438d5b6e29db0898bc4f0225935c0,分析代码,看到第50行是个MD5加密,所以想到尝试MD5解密上面这个字符串,在这个网站解密的结果如下:

1565589413006

然后我运行程序输入grape,程序报错,好吧,看来没这么简单

进到MD5函数里看看,也没发现什么

1565590696742

做题一度陷入了僵局。后来又进入一个解密网站解密,发现居然解密的结果不一样{{jingxia}}

这次的结果是

1565590901116

运行程序输入b781cbb29054db12f88f08c6e161c199,成功。所以flag就是b781cbb29054db12f88f08c6e161c199。

后来我发现grape在MD5加密之后就是b781cbb29054db12f88f08c6e161c199,可能第一个网站比较牛逼,直接给你解析到最开始的字符串{{xiaoku}}

分析总结

这个就是简单的看源码分析找到MD5解密就行了,没什么难度。

Shuffle

题目描述:找到字符串在随机化之前.

做题实录

这是个32位elf文件,运行跑出来是乱码

放到IDA里看一下

这……不说了{{mudengkoudai}}

直接出答案SECCON{Welcome to the SECCON 2014 ctf!}

re2-cpp-is-awesome

题目描述:他们说c++是复杂的,证明他们说的是错的!

做题实录

上来先一顿基操

1565595113757

IDA,开启!进入main函数

2019-08-12_190525

分析函数,可以发现第35行是主要的判断函数,v9就是我们输入的flag。那么flag的线索就藏在off_6020A0[asc_6020C0[v15]]里,那先看看off_6020A0里是什么吧。

1565608274123

看到一个像flag的东西,突然兴奋了起来,不会这么简单吧。继续看

1565608360037

好吧,本来以为是flag,但是看到这发现事情果然没有那么简单{{koubi}}

接着看asc_6020C0,发现是一些数据

1565609099770

后面其实本来一开始也是db,只不过我改成dd了(按D键),至于为什么要改,是我马后炮做完题才发现的,在写脚本的时候我会说到。

从分析可以看出flag应该就是从off_6020A0处的str(下文都叫str)中生成的。

先通过v15找出对应的asc_6020C0中的值,再以asc_6020C0的值为偏移量在str中找对应的字母,最终拼成flag。

有了这个思路后我就开始试着看一下flag是什么,发现asc_6020C0第一个是“$”,按D键转成ASCII码是24h,数出来是A,接着动态调试一下看看对不对。在判断的地方下断点,然后在Debugger->Process Option->Parameters中填入要输入的参数Adawda(除了开头的A后面是随便填的),开始调试

1565609766817

调试中我又下了两个断点,调试之后发现A是对的,那就印证了我的猜想,那下一步就是把所有的字符找出来。

asc_6020C0的数据提取出来,我直接按照反汇编的结果写的脚本如下:

offset=[36,00,00,00,00,00,00,00,5,00,00,00,54,00,00,00,
        101,00,00,00,7,00,00,00,39,00,00,00,38,00,00,00,
        45,00,00,00,1,00,00,00,3,00,00,00,00,00,00,00,
        13,00,00,00,86,00,00,00,1,00,00,00,3,00,00,00,
        101,00,00,00,3,00,00,00,45,00,00,00,22,00,00,00,
        2,00,00,00,21,00,00,00,3,00,00,00,101,00,00,00,
        00,00,00,00,41,00,00,00,68,00,00,00,68,00,00,00,
        1,00,00,00,68,00,00,00,43] #这是最后字符串的偏移量 注意刚提取出来的时候数都是16进制的,我改成了10进制

str = 'L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t'

for eax in range(0,31):
    i = offset[eax*4]
    print(str[i], end = "")  #后面的end是让打印不自动换行

#得出结果:   ALEXCTF{W3_L0v3_C_W1th_CL45535}

在这一步我卡了好久,因为我在手动把16进制修改成10进制的时候不小心删了个00,然后卡了半天。后来终于发现少了个00补上了但是补错了位置……导致得出了一个这样的flag:ALEXCTF{W3_L0v3LC_W1th_CL45535}

这完全就是一个flag该有的亚子好吗?然后我就怀着激动的心情提交了,然后,系统告诉我,错了

所以说:

细心真的很重要很重要。

不过,我后来发现,特喵的这不是dd双字字形吗,难怪eax要乘4,难怪那么多的0。就因为漏了个0卡了我好久我去{{tuxue}}{{tuxue}}{{tuxue}}

所以我在写这篇文章的时候才把asc_6020C0的数据用dd表示出来,这样就非常直观简洁了{{xiaoku}}

分析总结

实际上做完再回过头来再看这道题,可以看出这道题其实并不算难,但是这道题花了我很多的时间,我很早就注意到了off_6020A0[asc_6020C0[v15]]这个关键点,但是当时看到asc_6020C0的不知道是干啥的数据时有点懵逼,最后硬是汇编调试了半天才反应过来,然后就没有顾其他的直接按照汇编语言写脚本了。实际上做完题之后一看到++v15,每次只自增1,就明白了数据是双字存放的,如果当时冷静下来再思考一下,应该就能很快把这道题做出来。

这个题让我认识到:1.汇编还得好好学。2.IDA真NB,而我连D键咋用都不知道。不行,IDA PRO权威指南,打开!3.我确实是个菜狗,还得多做题{{liulei}}

crackme

做题实录

IDA看看源码:

1567172093090

根本不知道这是什么玩意儿,看来是加了壳,PEiD看一下有什么。

1567172218601

可以看到壳是nSPack 3.7,那么就先用UnPackNsPack3.7脱壳,脱完壳之后再分析:

1567172355880

可以看到就是简单的算法逆向,关键语句是第15句,我们看看Notflag和arg(名字我改过了):

1567172454937

可以看到Notflag是字符串,arg是一个数组,那么就写个脚本跑出来就行了:

notflag = 'this_is_not_flag'
arg = ['12', '4', '8', '14', '24', '5C', '4A', '3D', '56', '0A', '10', '67', '0', '41', '0', '1', '46', '5A', '44', '42', '6E',
    ¦  '0C', '44', '72', '0C', '0D', '40', '3E', '4B', '5F', '2', '1', '4C', '5E', '5B', '17', '6E', '0C', '16', '68', '5B', '12']

arg2 = []
for i in range(len(arg)):
    arg2.append(int(str('0x') + arg[i],16))
print(arg2)

flag = ''
for i in range(len(arg2)):
   flag += chr(arg2[i] ^  ord(notflag[i % 16]))  
print(flag)
#得出结果:flag{59b8ed8f-af22-11e7-bb4a-3cf862d1ee75}

分析总结

实际上我认为这道题的难点在于脱壳,我直接用了大佬的工具,具体脱壳流程我还不会,还需要继续学习。

大佬的工具参见这篇文章《[原创]NsPack 3.7 浅析 (7.3更新脱壳机和源码)》

攻防世界逆向新手训练

[toc]

[begin]本[/begin]来是搞Web的(虽然菜的抠脚),但是战队缺reverse和pwn的人,正好自己对这方面也很有兴趣,于是就转了方向。刚学几天,先做上几道攻防世界的新手训练题。把自己做题的过程和学到的知识记录下来。由于我在做的过程中很多时候都是瞎试试出来的,所以记录的时候分成两部分,第一部分是【做题实录】,就是我做题的真实思考过程,flag可能是瞎猜猜出来的,有很多的运气成分在。第二部分是【分析总结】,是我在网上查阅大佬的“正确做法”,即通过分析和思考,用正统做法得出flag。分析总结中我会把我复现的写出来。由于我实在是逆向小白,可能大部分都是靠运气做出来的,大佬勿喷,估计以后再回头看自己的解法自己都要笑死了。

re1

题目描述:菜鸡开始学习逆向工程,首先是最简单的题目

做题实录

双击点开,让我输入flag

1565360175669

随便输一个:

1565360208916

看样子是要比较字符串的,放到OD里面看看,找到了比较字符串的地方,下断点

1565360371101

随便输入什么字符之后继续往下走,发现flag已经进入到了ESP中

1565360501795

分析总结

这个题非常简单,没什么好说的,其实放到OD里还可以直接搜索字符串,一步到位……

1565360834492

当然了,还可以用IDA直接看源码

1565361318963

看到strcmp()函数,输入的是v9,和v5比较,所以数据就应该在v5里面,再看第10行,把xmmword_413E34的值赋给v5,所以双击xmmword_413E34看它的数据。把qword_413E44xmmword_413E34的数据拼起来

1565361586179

你可以把qword_413E44xmmword_413E34的数据拼起来用python把16进制的数转化成文本然后反向写出flag就可以了。

print ('7D4654435455443074656D30633165577B465443545544'.decode('hex'))
# 解码结果:   }FTCTUD0tem0c1eW{FTCTUD

或者光标点击xmmword的数据,按下a键,

1565362998384

确认就可以直接把它转化成字符串。(我也是后来才知道的,看来ida还是要好好学)

1565363024355

game

题目描述:菜鸡最近迷上了玩游戏,但它总是赢不了,你可以帮他获胜吗

做题实录

一个exe文件,打开如下:

1565417874449

让你玩游戏,赢了就有flag,于是玩三个小时玩通关拿到flag。放到OD里动态调试

一路走到这里让你输入n,估计接下来就是字符串比较了,随便输一个继续调试

1565418367023

这部分一整段都在判断灯的情况,调试的过程中发现只要判断出灯没有全亮,就会跳转回003EF4FB,没有进入game.003E7AB4,所以猜测是成功了就进入这里,于是进去看看

1565418838983

进来之后,果不其然,是成功的函数

1565419406338

接下来就运行这个函数,就可以得到flag

1565419538116

分析总结

这个题还是比较简单的,没啥好说的。再用IDA来试一下

1565419925277

进入main_0()函数,看到判断语句,逻辑很简单

1565419996445

进入sub_457AB4()

1565420082978

这个程序就疯狂跳转,不知道要干啥

进去之后就是这样

1565420136904

可以直接写个脚本跑,在这里我选择改程序的逻辑,让它跳过判断直接执行这个函数

1565420760854

这一部分就是判断语句,只要把cmp edx, 1改成cmp edx edx那么条件就恒为真。

特别注意的是:cmp edx, 1占3个字节而cmp edx edx占两个字节,所以最后一个字节用nop填充。否则会出错

修改后如下:

1565423173098

保存之后双击运行,随便输入一个n,就可以得出flag

1565423246933

Hello,CTF

题目描述:菜鸡发现Flag似乎并不一定是明文比较的

做题实录

一个exe,运行结果如下:

1567821831010

要找序列号,IDA看一下:

1567822212906

看来flag就是这个绿色的字符串了,看到有字母有数字且字母不超过f,所以16进制解码试一下,得到flag:

CrackMeJustForFun

分析总结

这个题比较简单,我开始是用动态调试做的,都差不多,只要看到这个字符串基本上就可以解决了。

open-source

题目描述:菜鸡学逆向学得头皮发麻,终于它拿到了一段源代码

做题实录

源代码如下:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("what?\n");
        exit(1);
    }

    unsigned int first = atoi(argv[1]);
    if (first != 0xcafe) {
        printf("you are wrong, sorry.\n");
        exit(2);
    }

    unsigned int second = atoi(argv[2]);
    if (second % 5 == 3 || second % 17 != 8) {
        printf("ha, you won't get it!\n");
        exit(3);
    }

    if (strcmp("h4cky0u", argv[3])) {
        printf("so close, dude!\n");
        exit(4);
    }

    printf("Brr wrrr grr\n");

    unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

    printf("Get your key: ");
    printf("%x\n", hash);
    return 0;
}

看到就是要输入规定的参数运行就可以了,注意这个参数要在命令行中输入。第一个参数直接跑个脚本:

print(str(0xcafe))
#51966

第二个参数最小的是25,第三个参数h4cky0u

编译之后cmd运行,得到flag:

1567823954737

分析总结

分析源码,没啥可说的。

simple-unpack

题目描述:菜鸡拿到了一个被加壳的二进制文件

做题实录

UltraEdit打开看一下,搜索flag:

1567824208589

去掉中间的乱码,得到flag:

flag{Upx_1s_n0t_a_d3liv3r_c0mp4ny}

分析总结

直接查找确实是歪门邪道,毕竟这题是关于加壳的,所以UE看一下:

1567824854437

看到这是个upx的壳,那直接用upx脱壳就行了:

1567825480287

脱完壳查找flag即可:

1567825781968

logmein

题目描述:菜鸡开始接触一些基本的算法逆向了

做题实录

IDA里看一下:

1567827148274

第26行就是加密算法,写个解密算法就行了:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void main()
{
    int v6;
    int v9;
    long long v7;
    int i;

    v9 = 0;
    char * str = ":\"AL_RT^L*.?+6/46";
    v7 = 28537194573619560LL;
    v6 = 7;

    for ( i = 0;i < 17; ++i )
    {
        printf("%c",*((char*)&v7 + i % v6)^str[i]);
    }

}
//  RC3-2016-XORISGUD

分析总结

这个题就是简单的算法逆向,没啥可说的。

insanity

题目描述:菜鸡觉得前面的题目太难了,来个简单的缓一下

做题实录

IDA打开,查找flag,得到flag:

1567827487075

分析总结

实际上这个题就直接运行,等上几秒它会随机给你输出一些字符串,我第一次运行就直接得到了flag……

1567827754608

no-strings-attached

题目描述:菜鸡听说有的程序运行就能拿Flag?

做题实录

这是个32位的elf文件,ubuntu里跑一下,出来一堆不知道是啥的东西。

1565410895869

IDA打开

1565411183353

这个authenticate()看着很可疑,进去看看:

1565411728014

看到一个decrypt(...),估计就是它了,进去看看

1565411819257

看样子是一个解密函数,在decrypt(...)下断点进行动态 调试,进入while循环Hex View跟踪EAX(因为函数返回值就存在EAX里),可以看到flag正在被解密出来,继续往下走

1565412182025

解密完成,得出flag,去掉0x00就是最终的flag,由于题目来源9447CTF,所以没有前面的1。

1565412265513

分析总结

我看了一下,基本上都差不多,不过很多都是用gdb调试的,没用过gdb,正好趁这个机会学习一下,写一下gdb调试的方法。

进入gdb后输入 file no-strings-attached 开始调试程序

然后在decrypt()下断点,用命令 break decrypt

按n是单步步过,s是单步步进,啥也不输直接按回车默认执行上一条指令

单步执行到这里发现EAX里面存了9,是flag的开头

1565415496515

记下此时EAX的位置0x804cff0,输入 finish 让程序执行完当前的函数并返回

1565416027427

查看EAX的内容,输入命令 x/100u $eax (该命令具体使用方式参见GDB下查看内存命令(x命令)

1565416413194

写个脚本跑出来就行了

s=[57,52,52,55,123,121,111,117,95,97,114,101,95,97,110,95,
    105,110,116,101,114,110,97,116,105,111,110,97,108,95,109,121,
    115,116,101,114,121,125]
flag=''
for i in range(len(s)):
    flag += chr(s[i])

print (flag)

#得出结果:9447{you_are_an_international_mystery}

getit

题目描述:菜鸡发现这个程序偷偷摸摸在自己的机器上搞事情,它决定一探究竟

做题实录

这是个elf文件,在ubuntu里跑了一下发现没反应,放到IDA里看一下

1565407669159

看样子估计是个解密flag的,看了看数据段发现了t是flag(IDA里面按a把ASCII码变成字符串),但是内容不知道

1565408592965

1565408717450

我猜while循环应该就是解密的,于是动态调试一下。

1565408257709

可以看到在循环的过程中下面flag就已经出现了,继续调试得出flag

1565408338406

分析总结

这个题其实也可以不用动态调试,因为while循环里转换算法都写好了,自己按照源码写个脚本跑就行了。

s = 'c61b68366edeb7bdce3c6820314b7498'
flag = ''
for i in range(len(s)):
    if i & 1:
        t = 1
    else:
        t = -1
    flag  += chr(ord(s[i]) + t)
print (flag)

#得出结果:  b70c59275fcfa8aebf2d5911223c6589

python-trade

题目描述:菜鸡和菜猫进行了一场Py交易

做题实录

下下来是pyc文件,放到ubuntu里面,用uncompyle反编译(下载过程也踩了很多坑……)并保存到tes.py

uncompyle6 Py.pyc > tes.py

得到源码:

# uncompyle6 version 3.3.5
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.15+ (default, Nov 27 2018, 23:36:35) 
# [GCC 7.3.0]
# Embedded file name: 1.py
# Compiled at: 2017-06-02 19:20:43
import base64

def encode(message):
    s = ''
    for i in message:
        x = ord(i) ^ 32
        x = x + 16
        s += chr(x)

    return base64.b64encode(s)


correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
flag = ''
print 'Input flag:'
flag = raw_input()
if encode(flag) == correct:
    print 'correct'
else:
    print 'wrong'
# okay decompiling Py.pyc

是一个加密的程序,那按照它的步骤解密就行了,是一个算法逆向的题。

解密代码如下:

import base64

def decode(message):
    message = base64.b64decode(message)
    s = ''
    for i in message:
        x = ord(i)-16
        x = x ^ 32
        s += chr(x)

    return s 

message = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
print (decode(message))

#得出结果:   nctf{d3c0mpil1n9_PyC}

分析总结

这题没啥好说的,就是算法逆向。但是安装使用uncompyle遇到点问题,也记下来吧。

pip install uncompyle

然后使用的时候需要输compyle6,我在输了之后一直提示我“command not found”,后来发现需要执行这两条命令安装环境变量

export PATH=HOME/bin:/usr/local/bin:PATH
PATH=HOME/bin:/usr/local/python27/bin:/usr/local/bin:PATH

csaw2013reversing2

题目描述:听说运行就能拿到Flag,不过菜鸡运行的结果不知道为什么是乱码

做题实录

拿到的是个exe文件,双击运行结果如图:

1565351871574

正如描述所说,是一堆乱码,放到OD里看一下。

发现这个位置就是弹出flag的位置。

1565352046845

在这儿设个断点,然后进去仔细看。

进来之后看这个函数,发现有两个MessageBox函数,觉得有蹊跷,于是在00B01094处取消跳转,让它往下走看看有什么反应。

1565352326390

在走的时候把int3用nop填充,00B010A3处的jmp跳转也用nop填充

走到第一个MessageBox,弹出了一个flag,但是什么也没有

1565352520653

于是点击忽略继续往下走,又遇到了jmp跳转,同样nop填充

走到这flag已经加载进内存了。

1565352654965

分析总结

方法一:直接修改程序逻辑

参考【XCTF 攻防世界】 Reverse —— csaw2013reversing2

由于是个32位的PE文件(linux下用file命令查看),所以用ida pro打开,F5反编译,生成类C语言的伪代码如下:

1565354260660

lpMem就是flag的内容。看第10行if语句,如果sub_40102A()IsDebuggerPresent()返回值为真则执行__debugbreak()调试断点函数、子函数sub_401000(...)和退出进程函数ExitProcess(...)函数,否则直接执行MessageBoxA(...)函数弹出flag框。

打开sub_40102A()函数看一下:

1565355150691

发现返回值恒等于零,那就是说只有在调试状态下才会执行if里面的语句。由于直接双击运行没有在调试环境下,弹出乱码的flag,所以可以肯定if里面的语句是关键点,应该包含解密flag的函数。

打开sub_401000(...)看一下:

1565355328341

看样子是个解密函数,那应该就是它了。那么思路就很清晰了,我们要让程序跳过if判断,直接执行sub_401000(...)函数,然后再跳过ExitProcess(...)函数执行MessageBoxA(...)函数弹出解密后的flag框。

既然思路已经清晰那么就开始修改程序吧。

这是原本的逻辑:

1565355918832

使用IDA修改指令的方法是将光标放在要修改的指令上,依次点击Edit -> Patch program -> Assemble,弹出指令修改框进行修改。

修改之后逻辑如下:

1565356638399

红色标注的就是修改过的地方。修改完成后要保存到文件,依次点击 Edit -> Patch program -> Apply patches to input file…,弹出设置框,选择待打补丁程序进行修改。

最后双击运行修改后的程序,直接弹出已解密的flag框。

1565358175915

方法二:DLL注入

参考攻防世界 RE csaw2013reversing2

maze

题目描述:菜鸡想要走出菜狗设计的迷宫

做题实录

打开IDA看了半天源码,不知道和迷宫有啥关系,以前没做过这种题,还以为是代码不停跳转什么的,还是最后看了wp才明白,真的是个迷宫(吐血)。还是要做题,提高自己的姿势水平,不然就太naive了。

分析总结

这是个64位的elf文件,放IDA里看一下main函数

2019-08-11_203146

(请无视我刚开始看的时候做的注释)首先看第13行,判断输入的字符串,从这里可以看出要求输入的flag长度为24,且以 ‘nctf{‘ 开头,以 ‘}’ 结尾(最后的 ‘}’ 本来是ASCII码,在IDA里把光标选择ASCII码按R键就可以把ASCII码变成字符了)。

由于是迷宫,一般是二维的,所以应该有一个记录坐标的变量,看到v9,在判断中有一个&v9(&v9)+1(也就是v9跟着的的下一个字节的地址),那么可以猜想v9就是一个记录位置坐标信息的二维数组。那么while循环就是在判断输入的字符来改变坐标信息,也就是用户输入字符来走迷宫。

正常来说,v9和v9+1对应着迷宫的行和列(别问我为啥不是列和行,我觉得正常讲话就是行在前,所以自然而然想到行和列。就算不对那到时候再改吗,反正就两种情况。而且下面的分析可以确定就是对应的行和列)。

既然是走迷宫,当然就得判断走没走到终点,看到了第72行的“Congratulations!”,那看一下它前面第70行的判断,判断坐标所在,如果是“#”,就到终点了,说明“#”号就代表终点。而且看一下asc_601060

1565528977356

有个“#”,看来这应该就是迷宫的地图了。我们的目标就是要走到“#”处。再回过头来看8*v9+SHIDWORD(V9),既然是坐标,说明v9就代表行,而v9+1代表列,而且可以知道迷宫的地图应该是8个字符一行的(因为行增加一,数就增加8)。

至于这个LABLE_15,每走一步都要进入这里,应该就是判断有没有越界什么的函数。

现在基本上就已经理清楚了这个迷宫,只要知道怎么走就行了。while循环里的函数如下:

//因为是&v9+1,所以是判断列,也就是左右走
bool __fastcall sub_400650(_DWORD *a1)//(_DWORD *)&v9 + 1
{
//为'O' 
  int v1; // eax

  v1 = (*a1)--;     //往左走
  return v1 > 0;
}

bool __fastcall sub_400660(int *a1)//(int *)&v9 + 1
{
//为'o'
  int v1; // eax

  v1 = *a1 + 1;     //往右走
  *a1 = v1;
  return v1 < 8;
}
//上下走
bool __fastcall sub_400670(_DWORD *a1)//&v9
{
//为'.'
  int v1; // eax

  v1 = (*a1)--;     //往上走
  return v1 > 0;
}

bool __fastcall sub_400680(int *a1)//(int *)&v9
{
//为'0'
  int v1; // eax

  v1 = *a1 + 1;     //往下走
  *a1 = v1;
  return v1 < 8;
}

总结一下:“.”往上走、“0”往下走、“O”往左走、“o”往右走

拿个脚本把地图写出来(python是真的好用)

map = '  *******   *  **** * ****  * ***  *#  *** *** ***     *********'
for i in range(0, len(map), 8):
    print (map[i: i + 8])

#得出结果:
'''
  ******
*   *  *
*** * **
**  * **
*  *#  *
** *** *
**     *
********
'''

要求步长为18,从左上角开始走,走到“#”的位置,走就行了,最后结果为

nctf{o0oo00O000oooo..OO}