来是搞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}


"Imagination will take you everywhere."