BaseCTF新生赛Reverse week1解题wp
寒影和寒风萧 发表于 广东 CTF 847浏览 · 2024-09-12 08:51

BaseCTF新生赛misc week1解题wp

Base

Base编码是一种常用的将字节流(通常包含不可输入/显示字符)转换为可输入/显示的字符串的编码方案,使信息更易于传输。具体可以参考计算机相关的编码 - CTF Wiki

KFWUM6S2KVHFKUTOOQZVUVCGNJGUOMLMLAZVE5SYGJETAYZSKZVGIR22HE======

直接base32解码:

QmFzZUNURnt3ZTFjMG1lX3RvX2I0c2VjdGZ9

然后再base64解码

BaseCTF{we1c0me_to_b4sectf}

https://gchq.github.io/CyberChef 是常用的编码分析与数据处理工具

根本进不去

我们可以看到题目给了 flag.basectf.fun

可以看到并没有解析, 我们可以尝试看一下这个域名解析到哪里了
可以查询 TXT 记录

如果本地环境有问题, 也可以尝试在线 dig 工具 (https://tool.lu/dns/index.html)

海上遇到了鲨鱼

Wireshark 是强大的网络数据捕获与分析工具

打开附件,看到协议为 TCP 与 HTTP 的分组,可以一个个点击看看是什么
我们先关注 HTTP 协议的数据,右键点击 → 追踪流 → HTTP Stream
可以看到客户端对服务端发送的请求数据,以及服务端的响应
右下角可以切换到前一个/后一个流(TCP 连接),在流 4 中可直接看到逆序的flag

有明显字样BaseCTF{} 逆序可得

手敲亦可(
BaseCTF{15ef386b-a3a7-7344-3b05-ac367316fb76}
可以回看前面的几个流:
流1的 robots.txt 是爬虫协议,通常用来表示网站不希望哪些页面被爬取
流2得到一个 301 Moved Permanently 状态码,让浏览器重新访问 Location: 指向的页面
流3得到一个 404 Not Found 状态码,表示页面不存在

正着看还是反着看

010 Editor 打开,除了 txt.galf 也可以看见末尾明显 JFIF 特征
文件的本质就是一堆字节。像 010 Editor 这样的十六进制编辑器可以查看/编辑文件的原始字节流。
大部分文件有其固定的文件结构,常见的图片格式如 PNG、JPG 等都是由一系列特定的数据块组成的。在许多非文本文件的开头,都有一片区域来显示这个文件的格式,这就是文件头标志。例如 JPG 开头通常是 ÿØÿà..JFIF这样的模式,看到这个模式就知道是 JPG 文件。

写一个脚本将文件逐字节逆序:

def reverse_bytes_in_file(input_file_path, output_file_path):

    try:
        with open(input_file_path, 'rb') as infile:
            content = infile.read()
            reversed_content = content[::-1]

            with open(output_file_path, 'wb') as outfile:  
                outfile.write(reversed_content)

        print(f"文件内容已成功逆序,并写入到 {output_file_path}")
    except FileNotFoundError:
        print(f"未找到文件: {input_file_path}")
    except Exception as e:
        print(f"发生错误: {e}")


input_file = './flag'
output_file = './out'
reverse_bytes_in_file(input_file, output_file)

当然也有更 Pythonic 的写法,一行搞定:

open('./out','wb').write(open('./flag','rb').read()[::-1])

也可以将文件上传到 CyberChef,逆序(注意按字节而不是字符),然后下载:

得到一个文件,使用 010 Editor 的模板功能可以识别出最后有一个未知区域:

PK.. (50 4B 03 04) 则是 ZIP 压缩文件的标志。
图片查看软件在显示完 JPG 内容后,会忽略这个部分;而压缩软件会在文件中寻找这个 50 4B 03 04。所以用看图软件打开这个文件会看到图片,用压缩软件打开这个文件会看到压缩包中的内容。
binwalk 分离(也可以 010 Editor 手动选中保存):

binwalk 也常用于从一整个固件文件中分离已知格式文件。

人生苦短,我用Python

这道题的目的是给选手看一下 Python 程序长什么样子。如你所见,Python 的语法非常精简(例如可以不写 main,定义一个变量只需要写变量名就好了),很多复杂的操作一行就能写完。
不过好像起反作用了,出题人好像写的判断条件太多了,让有些选手做到一半放弃了,对不起。其实去掉头尾也就 29 个字符而已(小声)
在 CTF 中经常会见到/用到 Python。学什么编程语言都是,多读、多写就会了。哪里看不懂就问 AI(以下是 GitHub Copilot 的回答,选作 Writeup 时有修改):
这个程序是一个简单的CTF(Capture The Flag)挑战,要求用户输入一个特定格式的字符串(称为flag)。程序通过一系列检查来验证输入的flag是否符合预期的格式和内容。如果任何一个检查失败,程序会调用abort函数,输出错误信息并退出程序。以下是程序的详细解释:

  1. 导入模块
  2. 定义abort函数
  3. 打印欢迎信息并获取用户输入
  4. 一系列检查
    • 检查flag长度是否为38
    • 检查flag是否以BaseCTF{开头
    • 检查flag的下标为10和11的字符是否为Mp(下标是从0开始的)
    • 检查flag的最后三个字符是否为3x}并重复8次
      至此可得BaseCTF{Mp*3x}
    • 检查flag的最后一个字符的ASCII值是否为125(即})
    • 检查flag中_字符的数量是否为4
    • 检查flag按_分割后的每部分长度是否为[14, 2, 6, 4, 8]
    • 检查flag的第13到32个字符,每隔4个字符是否为lsTn(Python用左闭右开区间)
      至此可得BaseCTF{*Mpl
      s_T***n*3x}
    • 检查flag前9个字符的大写形式用

BaseCTF新生赛crypto week1解题wp

十七倍

「初等数论」和「代数」是密码学的数学基础。这里推荐《密码编码学与网络安全——原理与实践》这本书,分别用一章(几十页)就从头介绍了密码学中会用到的数学知识。(可以找 LilRan 要电子书。)

解法一:乘法逆元

对于题目
$$y = (x 17) \% 256$$
两边同乘241得
$$(y
241) \% 256 = (x 17 241) \% 256$$
在模算术运算中,我们有$$(ab)\%c=((a\%c)(b\%c))\%c$$,于是上式改写成
$$(y 241) \% 256 = ((x \% 256) (17 241 \% 256)) \% 256$$
也就是
$$(y
241) \% 256 = ((x) (1)) \% 256$$
这样我们就得到了
$$x = (y
241) \% 256$$
由于$$(17*241)\%256=1$$,我们把241称为17的模256逆元。
241是怎么算出来的呢?可以通过扩展欧几里得原理求得。在 Python 中直接pow(17, -1, 256)就可以得出结果。
所以,对题目每个结果再乘241就变回去了:

#include <stdio.h>

int main() {
    unsigned char flag[] = {
         98, 113, 163, 181, 115, 148, 166,  43,   9,  95,
        165, 146,  79, 115, 146, 233, 112, 180,  48,  79,
         65, 181, 113, 146,  46, 249,  78, 183,  79, 133,
        180, 113, 146, 148, 163,  79,  78,  48, 231,  77,   0
    };
    int i;
    for (i = 0; i < 40; i++) {
        flag[i] = flag[i] * 241;
    }
    printf("%s\n", flag);
    return 0;
}

// BaseCTF{yoUr_CrYpt0_1earNinG_5tarTs_n0w}

更多关于乘法逆元的知识可以参考:乘法逆元 - OI Wiki

解法二:位运算

17表示为二进制是10001
十进制各位分别表示 …… 1000 100 10 1
二进制各位分别表示 …… 16 8 4 2 1
17 = 16 + 1
如果对66(1000010)乘17,在二进制中会是

1000010
  x     10001
 -------------
      1000010
  1000010
 -------------
  10001100010

所以a*17相当于(a<<4)+a
结果的低四位和a的低四位是一样的,高四位就是a的高四位+低四位的结果
由此可以写逆运算:

#include <stdio.h>

int main() {
    unsigned char flag[] = {
         98, 113, 163, 181, 115, 148, 166,  43,   9,  95,
        165, 146,  79, 115, 146, 233, 112, 180,  48,  79,
         65, 181, 113, 146,  46, 249,  78, 183,  79, 133,
        180, 113, 146, 148, 163,  79,  78,  48, 231,  77,   0
    };
    int i;
    for (i = 0; i < 40; i++) {
        unsigned char low = flag[i] & 15;
        unsigned char high = (flag[i] >> 4) - low;
        flag[i] = (high << 4) | low;
    }
    printf("%s\n", flag);
    return 0;
}

// BaseCTF{yoUr_CrYpt0_1earNinG_5tarTs_n0w}

解法三:暴力破解

每个字符只有95种可能,且40个字符是独立的,最坏情况下只需要计算95*40次,可以接受。(一般认为电脑一秒钟可以进行数亿次基本运算)。

#include <stdio.h>

int main() {
    unsigned char flag[] = {
         98, 113, 163, 181, 115, 148, 166,  43,   9,  95,
        165, 146,  79, 115, 146, 233, 112, 180,  48,  79,
         65, 181, 113, 146,  46, 249,  78, 183,  79, 133,
        180, 113, 146, 148, 163,  79,  78,  48, 231,  77,   0
    };
    int i;
    for (i = 0; i < 40; i++) {
        unsigned char j;
        for (j = 32; j < 127; j++) {
            if ((j * 17) % 256 == flag[i]) {
                /* C 语言常见坑!这里写 % 256 是考虑整数提升 */
                flag[i] = j;
                break;
            }
        }
    }
    printf("%s\n", flag);
    return 0;
}

// BaseCTF{yoUr_CrYpt0_1earNinG_5tarTs_n0w}

解法四:约束求解

z3-solver 擅长解方程(线性运算和位运算)。可以把题目的运算用 z3-solver 重新写一遍,让 z3-solver 找出一组满足条件的解。

import z3

s = z3.Solver()
flag = [z3.BitVec(f'flag_{i}', 8) for i in range(40)]

cipher = [
     98, 113, 163, 181, 115, 148, 166,  43,   9,  95,
    165, 146,  79, 115, 146, 233, 112, 180,  48,  79,
     65, 181, 113, 146,  46, 249,  78, 183,  79, 133,
    180, 113, 146, 148, 163,  79,  78,  48, 231,  77
]

for i in range(40):
    s.add(32 <= flag[i], flag[i] <= 126)
    s.add(flag[i] * 17 == cipher[i])

if s.check() == z3.sat:
    m = s.model()
    print(''.join(chr(m[f].as_long()) for f in flag))

# BaseCTF{yoUr_CrYpt0_1earNinG_5tarTs_n0w}

ez_math/mid_math

该题主要考察对代数中行列式的掌握(mid_math修复了可以直接gcd出来的bug).
分析

EXP

point1 = 85763755029292607594055805804755756282473763031524911851356658672180185707477
point2 = 70470862191594893036733540494554536608294230603070251013536189798304544579643
MAT = [[73595299897883318809385485549070133693240974831930302408429664709375267345973630251242462442287906226820558620868020093702204534513147710406187365838820773200509683489479230005270823245,
  46106113894293637419638880781044700751458754728940339402825975283562443072980134956975133603010158365617690455079648357103963721564427583836974868790823082218575195867647267322046726830,
  161159443444728507357705839523372181165265338895748546250868368998015829266587881868060439602487400399254839839711192069105943123376622497847079185],
 [13874395612510317401724273626815493897470313869776776437748145979913315379889260408106588331541371806148807844847909,
  17025249852164087827929313934411832021160463738288565876371918871371314930048841650464137478757581505369909723030523,
  59510107422473463833740668736202898422777415868238817665123293560097821015330],
 [11314088133820151155755028207579196628679021106024798818326096960197933616112389017957501267749946871903275867785729,
  13883500421020573457778249958402264688539607625195400103961001780695107955462968883861677871644577542226749179056659,
  48528427402189936709203219516777784993195743269405968907408051071264464132448]]

from sage.all import *
from Crypto.Util.number import *
print(long_to_bytes(det(matrix(MAT)) // (point1 - point2)))

# b"BaseCTF{7E9328AF-784C-8AF5-AC10-D6A8FC0977A8}"

babypack

from Crypto.Util.number import *
import random
flag=b'BaseCTF{}'
m=bytes_to_long(flag)
bin_m=bin(m)[2:]
length=len(bin_m)

a=[1]
sum=1
for i in range(length-1):
    temp=random.randint(2*sum+1,4*sum)
    sum=sum+temp
    a.append(temp)

a=a[::-1]
c=0
for i in range(length):
    if bin_m[i]=='1':
        c=c+a[i]
print("a=",a)
print("c=",c)

简单的超递增序列(不懂的可以简单了解一下背包密码)
从尾开始遍历列表a,大于c就为0,小于等于c就为1,并且c要减去这个值
(数据太多我就不贴了)

#BaseCTF{2c4b0c15-3bee-4e4a-be6e-0f21e44bd4c9}
from Crypto.Util.number import *
# a=
c=2488656295807929935404316556194747314175977860755594014838879551525915558042003735363919054632036359039039831854134957725034750353847782168033537523854288427613513938991943920607437000388885418821419115067060003426834
bin_m=""
for i in a:
    if c>=i:
        bin_m+="1"
        c=c-i
    else:
        bin_m+="0"
m=int(bin_m,2)
print(long_to_bytes(m))

babyrsa

from Crypto.Util.number import *

flag=b'BaseCTF{}'
m=bytes_to_long(flag)

n=getPrime(1024)
e=65537
c=pow(m,e,n)

print("n =",n)
print("e =",e)
print("c =",c)
"""
n = 104183228088542215832586853960545770129432455017084922666863784677429101830081296092160577385504119992684465370064078111180392569428724567004127219404823572026223436862745730173139986492602477713885542326870467400963852118869315846751389455454901156056052615838896369328997848311481063843872424140860836988323
e = 65537
c = 82196463059676486575535008370915456813185183463924294571176174789532397479953946434034716719910791511862636560490018194366403813871056990901867869218620209108897605739690399997114809024111921392073218916312505618204406951839504667533298180440796183056408632017397568390899568498216649685642586091862054119832
"""

单素数RSA
$$\phi(n)$$表示从1到n之间,有多少个数与n互素。
计算方法:排除掉不与n互素的数。
$$\phi(pq)=pq-p-q+1 = (p-1)(q-1)$$
这题n已经是素数了,1到n-1都与n互素,$$\phi(n)=n-1$$
求到$$\phi$$之后就正常做就行
(理解RSA最重要的一点是搞懂e*d=1 mod(φ(n)),要理解为什么这里为什么是取φ(n))
(很难绷,为什么那么多人问我n怎么分解,n=getPrime(1024)已经是素数了,不需要分解了。)

from Crypto.Util.number import *
import gmpy2
n = 104183228088542215832586853960545770129432455017084922666863784677429101830081296092160577385504119992684465370064078111180392569428724567004127219404823572026223436862745730173139986492602477713885542326870467400963852118869315846751389455454901156056052615838896369328997848311481063843872424140860836988323
e = 65537
c = 82196463059676486575535008370915456813185183463924294571176174789532397479953946434034716719910791511862636560490018194366403813871056990901867869218620209108897605739690399997114809024111921392073218916312505618204406951839504667533298180440796183056408632017397568390899568498216649685642586091862054119832

phin = n-1
d = gmpy2.invert(e, phin)
m = pow(c, d, n)
print(long_to_bytes(m))
#b'BaseCTF{7d7c90ae-1127-4170-9e0d-d796efcd305b}'

helloCrypto

from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random

flag=b'BaseCTF{}'

key=random.randbytes(16)
print(bytes_to_long(key))

my_aes=AES.new(key=key,mode=AES.MODE_ECB)
print(my_aes.encrypt(pad(flag,AES.block_size)))

# key1 = 208797759953288399620324890930572736628
# c = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'

直接AES解密即可

from Crypto.Util.number import *
from Crypto.Cipher import AES


key1 = 208797759953288399620324890930572736628
c = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'
my_aes1=AES.new(key=long_to_bytes(key1),mode=AES.MODE_ECB)
print(my_aes1.decrypt(c))

#b'BaseCTF{b80bf679-1869-4fde-b3f9-d51b872d31fb}\x03\x03\x03'

BaseCTF新生赛Reverse week1解题wp

You are good at ida

re签到题,找就完事了

64位无壳
拖入64位ida分析
按键盘 F5 看伪代码

flag被分成了多份,第一份直接就在主函数里面,然后有个提示,让我们shift f12
这个视图列举出了 IDA 识别到的所有字符串

很明显第二部分在这
双击这一行可以跳转到文件中这个字符串的位置,DATA XREF:显示了哪个函数使用了这个字符串,双击可以跳转到那个函数
然后将里面的数值按 R 键以字符形式显示,就能得到第二部分
并且函数里面给了最后一部分的提示

最后一部分在一个名字叫Interesting的函数里面
直接在函数列表那搜索

就得到了完整的flag,自行拼接即可

UPX mini

如果直接用IDA打开,会发现IDA只能识别出很少的代码及大量数据,可以看到名字为 UPX0,UPX1,UPX2 的段。
下载好附件,先查看可执行文件信息是一种好习惯
例如使用 Detect It Easy

又例如 Exeinfo

发现是有UPX壳的。这是一种“压缩壳”,用了UPX的可执行文件由“UPX解压代码”和“压缩的原文件”组成。在程序启动时先执行UPX的代码,把压缩后的原文件解压后,再把控制流转到原文件。
在网上搜索UPX可以找到官方工具:https://github.com/upx/upx/releases
下载后打开 upx.exe 所在文件夹,执行

发现脱壳成功,那就可以用ida64正常分析了。ida分析结束,此时的窗口就是main函数的流程图,直接F5反汇编,得到伪代码,

代码逻辑很清晰,就是将输入的flag进行加密后与Str2进行比较。那么我们现在来看看这个加密函数是不是真的是base64编码,

_BYTE *__fastcall base64_encode(char *a1)
{
  int v1; // eax
  _BYTE *v2; // rax
  char v4[72]; // [rsp+20h] [rbp-70h] BYREF
  _BYTE *v5; // [rsp+68h] [rbp-28h]
  int v6; // [rsp+74h] [rbp-1Ch]
  char *Str; // [rsp+78h] [rbp-18h]
  int v8; // [rsp+80h] [rbp-10h]
  int v9; // [rsp+84h] [rbp-Ch]
  int v10; // [rsp+88h] [rbp-8h]
  int v11; // [rsp+8Ch] [rbp-4h]

  Str = a1;
  strcpy(v4, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  v6 = strlen(a1);
  v1 = v6 % 3;
  if ( v6 % 3 == 1 )
  {
    v10 = 1;
    v11 = 4 * (v6 / 3 + 1);
  }
  else if ( v1 == 2 )
  {
    v10 = 2;
    v11 = 4 * (v6 / 3 + 1);
  }
  else if ( !v1 )
  {
    v10 = 0;
    v11 = 4 * (v6 / 3);
  }
  v5 = malloc(v11 + 1);
  v9 = 0;
  v8 = 0;
  while ( v8 < v6 - v10 )
  {
    v5[v9] = v4[(int)(unsigned __int8)Str[v8] >> 2];
    v5[v9 + 1] = v4[((int)(unsigned __int8)Str[v8 + 1] >> 4) | (16 * Str[v8]) & 0x30];
    v5[v9 + 2] = v4[((int)(unsigned __int8)Str[v8 + 2] >> 6) | (4 * Str[v8 + 1]) & 0x3C];
    v5[v9 + 3] = v4[Str[v8 + 2] & 0x3F];
    v8 += 3;
    v9 += 4;
  }
  if ( v10 == 1 )
  {
    v5[v11 - 4] = v4[(int)(unsigned __int8)Str[v6 - 1] >> 2];
    v5[v11 - 3] = v4[(16 * Str[v6 - 1]) & 0x30];
    v2 = &v5[v11 - 1];
    *v2 = 61;
    v5[v11 - 2] = *v2;
  }
  else if ( v10 == 2 )
  {
    v5[v11 - 4] = v4[(int)(unsigned __int8)Str[v6 - 2] >> 2];
    v5[v11 - 3] = v4[((int)(unsigned __int8)Str[v6 - 1] >> 4) | (16 * Str[v6 - 2]) & 0x30];
    v5[v11 - 2] = v4[(4 * Str[v6 - 1]) & 0x3C];
    v5[v11 - 1] = 61;
  }
  v5[v11] = 0;
  return v5;
}

很明显,就是没有经过任何改动的base64编码,那么我们就可以拿着密文用CyberChef一把梭了。

ez_maze

拿到附件,查壳,没有壳,64bit的

直接开始分析,F5大法

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char v5[32]; // [rsp+20h] [rbp-60h] BYREF
  __int16 v6; // [rsp+40h] [rbp-40h]
  char v7; // [rsp+42h] [rbp-3Eh]
  int i; // [rsp+48h] [rbp-38h]
  int v9; // [rsp+4Ch] [rbp-34h]

  sub_401840(argc, argv, envp);
  j_puts(Buffer);
  j_puts(aTakeTheShortes);
  j_puts(aShowYourTime);
  memset(v5, 0, sizeof(v5));
  v6 = 0;
  v7 = 0;
  j_scanf("%34s", v5);
  v9 = 0;
  for ( i = 0; v5[i]; ++i )
  {
    v3 = (unsigned __int8)v5[i];
    if ( v3 == 100 )
    {
      if ( v9 % 15 == 14 )
        goto LABEL_20;
      ++v9;
    }
    else if ( (unsigned __int8)v5[i] > 100u )
    {
      if ( v3 == 's' )
      {
        if ( v9 > 209 )
          goto LABEL_20;
        v9 += 15;
      }
      else
      {
        if ( v3 != 'w' )
        {
LABEL_21:
          j_puts(aInvalidInput);
          return -1;
        }
        if ( v9 <= 14 )
          goto LABEL_20;
        v9 -= 15;
      }
    }
    else
    {
      if ( v3 != 'a' )
        goto LABEL_21;
      if ( !(v9 % 15) )
      {
LABEL_20:
        j_puts(aInvalidMoveOut);
        return -1;
      }
      --v9;
    }
    if ( asc_403020[v9] == '$' )
    {
      j_puts(aInvalidMoveHit);
      return -1;
    }
    if ( asc_403020[v9] == 'y' )
    {
      j_puts(aYouWin);
      j_puts(aPlzBasectfLowe);
      return 0;
    }
  }
  j_puts(aYouDidnTReachT);
  return 0;
}

逻辑还是很清晰的,虽然难看了点(本题除了考察迷宫,还考察一点点代码阅读能力)。很简单的迷宫题,wsad,分别对应上下左右,然后if的判断可知,这应该是一个15*15的迷宫,shift+F12可以查找字符串,可以看到迷宫,

对着字符串按 Shift+E 可以提取数据

手动整理一下,

这就是迷宫,要求以最短路径从x走到y,

手动走一下也行,就是最短路径了,sssssssddddwwwddsssssssdddsssddddd,然后再找个计算哈希值的工具(例如 CyberChef),得到32位的小写md5,就是flag了,这里也附上脚本

maze = [
    8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1,
    0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1,
    0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
    0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1,
    0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1,
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1,
    1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 9
]

visited = [0] * (15 * 15)  # 记录访问过的点


def BFS(maze, x, y):
    queue = [(x, y, '')]  # 设置队列,bfs用队列,dfs用栈
    while queue:
        x, y, path = queue.pop(0)
        if x < 15 and y < 15 and x >= 0 and y >= 0 and visited[x * 15 + y] != 1 and maze[x * 15 + y] != 1:
            visited[x * 15 + y] = 1  # 证明已经访问过了
            queue.append((x + 1, y, path + 's'))  # 只能字符串相加
            queue.append((x, y - 1, path + 'a'))
            queue.append((x, y + 1, path + 'd'))
            queue.append((x - 1, y, path + 'w'))
        else:
            continue
        if maze[x * 15 + y] == 9:
            return path


flag = BFS(maze, 0, 0)
print(flag)

flag:BaseCTF{131b7d6e60e8a34cb01801ae8de07efe}

BasePlus

题目将输入的字符串经过Encode函数后与预定的密文 lvfzBiZiOw7lhF8dDOfEbmI]i@bdcZfEc^zaD! 进行比较,重点在观察Encode函数的伪代码

函数实现了一个Base64编码(如果有不懂的可以去看看Base64编码算法在IDA中的识别)

通过观察及调试可以发现,该函数与正常的Base64编码有一处小差异

do
{
    *(_BYTE *)(a2 + v8) = v4[v8] ^ 0xE;
    ++v8;
}
while ( v8 != v5 );

数组存储的值是Base64编码后异或完(0xE)的结果
根据main函数以及Encode函数中的伪代码,我们可以推出程序的flag处理方式:
输入Str --> 根据Secret数组进行Base64编码 --> 返回Base64编码后的字符串 ^ 0xE 的结果
所以我们要做的是:
取出预定的flag密文 --> 将每个字符都异或0xE --> 用预定的码表去解码这串密文
为了方便,这里使用CyberChef作为解题工具

Ez Xor

从名字就能猜出是一个关于异或的逆向题
下载附件用die查壳

无壳64位,直接拖入64位ida分析

大部分情况下你能见到的计算机使用的都是“小端序”,对于一个字节放不下的整数,会按顺序存放最低字节、次低字节、……
但编程时写的(IDA伪代码看到的)是我们实际使用的数据。
例如,0xDEADBEEF 在内存中是(小地址)EF BE AD DE(大地址)。
这一段讨论的是整数,字符串和其他有结构的数据类型不在讨论范围。

v4 = 7499608 = 0x726F58 在内存中是这样的:

毕竟C语言是很“自由”的,所有数据以字节形式放在内存里,你可以认为内存中的 58 6F 72 00 是一个整数 7499608,也可以认为是一个字符串 "Xor"。关键看代码中对它进行了什么操作。

双击 Str 和 v11 和 v12 和 v13,可以看到它们在内存(栈)上的起始地址偏移。赋值后,它们在内存中是这样的:

下面 strlen(Str) 时,从 Str 开头开始直到找到一个 0x00 为止,所以上图 28 个字节都可以算进 Str 的范围。
注意 v13 的\"表示一个"字符。毕竟字符串以"起止,不用转义字符的话会解析错误的。

双击KeyStream进入这个函数

如果感觉不直观的话,可以右键单击函数名,在 Set item type 中改为正确的数据类型int64 fastcall KeyStream(char a1, char a2, int a3)

这个函数会修改 a2 也就对应于 main 中的实参 v14。

再看看encrypt函数(这里除了设置数据类型,还修改了变量名):

异或解密就是再和异或的数异或一次就能还原
例如(这里的ABC并不是指字符ABC,可以理解为公式)
A ^ B = C
C ^ B = A

接下来上python解密脚本

def key_stream(key):
    key_box = []
    for i in range(28):
        key_box.append(key[i%3] ^ i)
    return key_box

def decrypt(enc, key):
    flag = ""
    key = key[::-1]
    for i in range(len(enc)):
        flag += chr(enc[i] ^ key[i])
    return flag

enc1 = bytes.fromhex("1D0B2D2625050901")[::-1]
enc2 = bytes.fromhex("673D491E20317A24")[::-1]
enc3 = bytes.fromhex("34056E2E2508504D")[::-1]
enc4 = b"\"@;%"
enc = enc1 + enc2 + enc3 + enc4
print(enc)

key = (7499608).to_bytes(4, 'little')
key_box = key_stream(key)
print(key_box)

flag = decrypt(enc,key_box)
print(flag)
0 条评论
某人
表情
可输入 255