moectf 题解记录
题解
有一个人前来参加CTF
听说是入门级别, 试下水, 结果发现挺有意思.
网站链接
Re
歪比巴卜
welcome_to_the_world_of_re
IDA载入, 容易看出这几行是检验过程:
1 | for ( i = 0; i <= 3; ++i ) |
unk_407040
是你输入的数据, for
里有4个函数, 逐个打开查看
函数 | 作用 |
---|---|
sub_401550 |
检查长度 |
sub_401585 |
检查是否是”moectf{“开头 |
sub_4015C5 |
检查结尾是否为”} |
sub_401606 |
检查flag内容, 发现flag直接放在off_403028 里 |
如果事先发现没有加密的话, 也可以直接记事本打开搜”moectf{“.
moectf{W31C0Me_t0_m03CTF_2021_w0o0o0oooo0ooooo0o0oooo0!!!}
EinfachRe
1 | int main() |
打开发现事狗屁不通文章生成器
打开发现是简单的异或加密, 又知道前7个字母是”moectf{“, 然后就可以写脚本啦
1 | c = [0x28, 0x15, 0x3A, 0x1B, 0x44, 0x14, 0x06] |
moectf{Ez_x0r}
Realezpy
是python脚本的字节码, 用uncompyle6反编译一下, 得到下面的
1 | # uncompyle6 version 3.7.4 |
观察一下, 可以写出解密脚本
1 | c = [119, 121, 111, 109, 100, 112, 123, 74, 105, 100, 114, 48, 120, 95, 49, 99, 95, 99, 121, 48, 121, 48, 121, 48, 121, 48, 95, 111, 107, 99, 105, 125] |
好了, 以上就是小编整理的关于Realezpy的内容了, 看看下面的flag吧.
moectf{Pyth0n_1s_so0o0o0o0_easy}
A_game
打開康康是什麼遊戲
1 | _main(argc, argv, envp); |
下面给了提示, 是数独. 程序把你填的数放到残缺的数独题中, 如果经过检验通过就可以自动算出flag.
数独题如下:
1 | 0 0 5 0 0 4 3 6 0 |
0是要填的, 然后直接去网上找解题器.
诶真香~~
moectf{S0_As_I_prAy_Un1imited_B1ade_WOrks—-E1m1ya_Shiro}
clothes
打开发现不对劲, 哦, 原来是加壳了. 查壳发现是aspack. 然后去吾爱上找了个脱壳器, 脱壳后程序无法启动, 不过问题不大, 直接IDA打开即可.
是异或加密, 直接写解密代码
1 |
|
为什么不用python? 问就是当时不会.
moectf{45pack_1s_3z_t0_unpack}
大佬请喝coffee
用jd-gui打开, 发现是矩阵, 脑袋开始痛了.
但是, matlab yyds!!!
问题不大, 把代码整理下, 得到下面的:
《易得》 $ x = \begin{bmatrix}69 & 88 & 99 & 97 & 108 & 105 & 98 & 117 & 114 \end{bmatrix}^\mathrm{T} $
chr()
一下, 结果是EXcalibur.
moectf{EXcalibur}
time2go
打开发现程序很复杂, 后来在知道这是go语言编译后的结果. 真正的程序入口函数是main_main
.
然后发现程序一直在输出-延迟-输出-延迟. 砸瓦鲁多? 不存在的, 直接把延时函数time_Sleep
给屏蔽掉, 方法是打开函数并切到汇编模式,对着函数第一句mov rcx, gs:28h
, Edit->Patch program->Assemble, 改成ret
, 让它直接返回, 别忘了Edit->Patch program->Apply patches. 运行程序, 游戏结束!
1 | welcome to moectf2021! |
然而发现flag只有一半. 无奈地再次分析, 最后花了七八分钟在main_fun2
函数的main_CanuFindme
变量里找到了后面一半:5_amaz1ng}.
moectf{G0_1an8uag3_15_amaz1ng}
midpython
又是一个python程序, 不同的是这个被编译成了exe.
在网上找了这篇文章, 按照他的方法, 我得到了MidPython文件, 补上文件的前十六个字节, 修改后缀为pyc, 它终于可以运行了!!!
可能是我的打开方式有问题, uncompyle6不能反编译它, 于是我从网上下载了pycdas程序, 得到下面的:
1 | [Code] |
“说人话?”
好吧原来的人工反编译结果找不到了, 直接把官方WP搬过来:
1 | key = [69, 70, 79, 72, 88, 75, 85, 127, 89, 85, 74, 19, 74, 122, 107, 103, 75, 77, 9, 73, 29, 28, 67] |
解密:
1 | key = [69, 70, 79, 72, 88, 75, 85, 127, 89, 85, 74, 19, 74, 122, 107, 103, 75, 77, 9, 73, 29, 28, 67] |
moectf{Pyth0n_M@st3r!!}
ez_Algorithm
逆向出来发现一个使用递归(效率很低)的算法:
1 | __int64 __fastcall fuck(int a1) |
写成数学式就是:
让我们帮它优化亿下:
就不求通项了, 反正速度也挺快-_-:
1 |
|
总之这个题很有意思, 它让我想起了被数列支配的恐惧.
moectf{4f73r_a11_7h1s_71m3~D0_y0u_r3a11z3_7h3_m3an1ng_0f_T1m3_c0mp13x17y???}
PEPEPE
打开IDA, 诶, 怎么变16位程序了?
科普一下, PE是32/64位Windows下的可执行文件, 其中一个特点为能在对应系统下运行以外, 还能在16位DOS下运行(虽然运行结果只是提示一条消息). 这是Windows的一贯作风: 在版本更迭时, 仍然会保持变态的兼容性, 甚至因此现在Windows下的路径分隔符仍然是反斜杠而不是更有现代风格的斜杠. 所以, 你可以永远相信Windows的向下兼容能力, 虽然说向下兼容有好有坏.
IDA没有识别到PE头, 只识别到MZ头. 用16进制打开发现PE偏移被置零了(0x3C处). 把PE偏移填上0x00000080就OK了. 深入了解PE结构, 可以看看《Windows PE权威指南》
现在再打开IDA, 发现这次正常了. 在main中可以直接看到加密的全过程:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
虽然不知道之前输入了什么, 我们还是尝试解密一下:
1 |
|
用记事本打开输出文件, 解密了, 但是, 没有完全解密.
记事本中有这么一段:
1 | ??鎒qiersill摉ke羙u!reve2ierwilllikeyou!reverierwilllikeyou!騟ve|v遼w輊 |
原来PE文件有很多相连的0字符, 这些地方异或以后会出现循环的密码字串, 盲猜一手密码是reverierwilllikeyou! 稍微修改下代码:
1 |
|
输入那个密码, 再用记事本打开, 看到了久违的MZ头.
修改后缀为exe, 得到flag.
moectf{P3_Structur3_1s_r3ally_fUnnY!}
RedC4Bomb
打开分析, 发现main函数直接JUMPOUT, 切汇编看看发生甚么事了.
找到JUMPOUT的地方, 发现加了花指令, 填nop还原一下. 诶, 又有一个, 再还原…
搞了老半天, 可能有什么脚本能快一点吧, IDA更多的不懂了.
总之有两个函数加了花指令, 都还原以后, 查看程序逻辑(有些函数名已修改):
1 | int __cdecl main_0(int argc, const char **argv, const char **envp) |
比较像RC4, 但不完全是, 我用THISISAFAKEFLAG作为密码试了一下, 发现不行.
v8应该是一个C++类, 前256字节放sbox, 紧跟着是密码字符串, 先初始化sbox, 再加密用户输入, 然后和byte_43AB30比对.
碰一下运气吧, 猜它是对称加密, 写了如下代码:
1 |
|
运行一下, 得到”D1S4ss3mbl3_th3_b0mb”, 这应该就是我们要的答案了, 试一下, 果然可以.
moectf{D1S4ss3mbl3_th3_b0mb}
baby_bc
这 都 是 些 啥 啊
我不到啊! 像某种汇编指令, 注意到文件中出现了这些东西:
1 | llvm.module.flags |
在网上查询得知, 这是llvm的代码, 由clang编译得来. 这个原来的后缀应该是.ll, 从SegmentFault网上找到了编译它的方法:
1 | llvm-as chall.ll |
结果找不到__isoc99_scanf
. 额, 强行换成scanf
, 这下可以了.
IDA载入, 那两个显眼的下北沢函数是RC4. 下面那个像base64(不完全是). 先把比对结果转成base64:
1 |
|
使用工具进行base64解码, 再用程序中的”11 45 14 61 76 61 6C 6F 6E 2C 79 79 64 73”作为密钥进行RC4, 得到答案.
moectf{Y0u_Kn0w_1lVm_ir_c0d3_A_l0t_!1!1}
Algorithm_revenge
又是个算法题. 载入后发现程序用一个固定的种子向一个二维数组填入随机数. 程序意图为:
小人从(1, 1)出发, 可以选择(2, 1)或(2, 2)
小人在(n, m)时, 可以在下一步跳到(n + 1, m + i), (i = -1, 0, 1 且 m + i > 0).
直到 n = 50 时停止. 计算小人经过的数的总和. 求和的最大值.
我的算法是倒推法, 简单说就是从第 n 层去选一个 n + 1 层中最好的结果.如图所示:
1 |
|
真就acm了呗.
moectf{DDDRDDLRLRDRDRLLRRRDLLLRRLDRRDRRLDDDLDRRDLRDLLRDD}
Pwn
之前打比赛的时候完全不知道题目什么意思, 网站打开是一个个人博客, 然后啥也没有啊?
比赛结束才想起去网上搜一搜nc是啥, 然后发现是一个命令, 安装了个amd64的linux试了一下, 诶可以了.
test_your_nc
linux下输入nc pwn.blackbird.wang 9500
, 然后cat flag
moectf{enjoy_the_netcat_and_the_shell}
Int_overflow
IDA打开分析:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
按看到的那样, 随便给一个无符号整数2147483648(0x80000000)即可. 用过修改器的同学应该对这个数字很熟悉hhh
moectf{y0ul0v3m3m3l0v3y0u_1nt0v3rfl0w}
baby_fmt
1 | int __cdecl main(int a1) |
程序先产生一个随机数, 保存在dword_804C044
全局变量里. 所以我们尝试使用printf
的漏洞向dword_804C044
写数据. (%n
可以向一个地址写已打印的字符数).
1 | from pwn import * |
moectf{fmt_1s_soooo_e@sy}
ret2text
先看main
函数
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
s
大小仅为10, 但是最多可输入256个字符, 可以利用这个修改返回地址, 在函数return 0;
的时候跳到另一个地方.
发现有一个函数backdoor
里面有"/bin/sh"
, 切汇编看看
1 | .text:0000000000400687 ; int backdoor() |
0x40068B
这里把字符串放入rdi(第一个参数), 然后调用system
函数, 那就跳到这吧.
1 | from pwn import * |
moectf{ret2txt_tr4v3l2she11}
babyrop
通过main
找到vuln
函数有栈溢出
1 | int vuln() |
然后backdoor
里有调用system
函数
1 | .text:080484F6 ; int backdoor() |
但是参数并不是/bin/sh
, 所以我们要想办法让栈顶存放的指针指向这个字符串, 然后再跳到这执行system
.
按ctrl+S打开节表, 发现0x804A028处有可读写空间, 我们可以先想办法把"/bin/sh"
写到这里.
可以通过溢出构造这样一个栈帧:
高地址
缓冲区地址 |
system函数的调用处 |
gets函数的开头(或者jmp _gets) |
低地址
程序执行到ret
指令时, 会让函数返回到0x08048380处(jmp _gets
), 然后程序以0x804A028(放置"/bin/sh"
的缓冲区)作为第一个参数调用gets
, 这时我们输入"/bin/sh"
, gets
返回到0x8048513, 也就是直接调用system
, 此时的栈顶正好是这个缓冲区.
1 | from pwn import * |
moectf{do_you_l1k3_vtuber_too?}
Int_overflow_revenge
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
先看17行, 如果输入的值大于1, 游戏结束. 所以第一个输入应该是不大于1的整数.
然后就可以sell_watermelon
1 | int sell_watermelon() |
可以利用整数溢出写数组之外的值, 我们可以修改返回地址到0x08049246, 那里会调用system("/bin/sh")
返回地址应该是[ebp+0x4]
, 数组起始地址是ebp-0x208
, (0x208+4)/4=131, 那就输入131.
1 | from pwn import * |
moectf{the_watermelon_is_permitted_to_be_grown_up}
Human’s Nature
查看main
调用的vuln
函数:
1 | void __noreturn vuln() |
这是个死循环, 所以修改返回地址没用, 但是我们可以通过printf
漏洞修改got中指向printf
的值, 让它变成指向system
的值, 如果我们在前面read
函数被调用时输入了"/bin/sh"
, 那么我们的目的就达到了.
程序开启了地址随机化, 所以我们要先得到任意一处代码在内存中的位置, 再减去不开启随机化时的位置, 就可以求出偏移, 这里我们选择vuln
的返回地址0x555A28555303.
下一步我们获取printf
函数的地址, 然后我们可以通过题目中给出的libc.so求得system
的地址.
然后就可以修改got中的printf
啦, 那天在网上找到pwn中有个函数fmtstr_payload
可以直接构造格式串, 新技能get.
1 | from pwn import * |
moectf{hIj4ck_Is_@_gr34t_w4y_t0_g3t_sh311}
更多
星期八更新