参赛ID:1K0CT
Crisscross | pyre
给了一个python程序和它的输出:
1 | import random |
求出flag每一位对应的(w, n)
(需要注意的是w
和n
的排列顺序类似...n[2], w[1], n[0], w[0], n[1], w[1]...
) 再稍微改一下原程序爆破出flag:
1 | key1 = [127, 81, 241, 40, 222, 128, 45, 87, 27, 154, 66, 162, 73, 176, 172, 164, 106, 234, 77, 5] |
Shifty Sands | 进阶的迷宫题
用DIE打开附件 64位ELF无壳 IDA64打开 主函数结构非常简单 胜利的判断是走到L
失败的判定是走到S
唯一需要费点时间看的是加了点混淆(永真判断)的change()
函数:
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
既然是会动的迷宫 那盯着看原来的迷宫看是几乎不可能解出来的 这里提供一个python程序模仿迷宫的运作并记录步数:
1 | def print_maze(maze, player_pos): |
但是因为我的移动逻辑是交换 所以程序还是有点瑕疵(你不会赢 也不会似) 但是作为一个做出答案的辅助工具还是足够的
另外需要注意的是 附件中的迷宫运作逻辑是:
S
移动- 玩家所在行, 列变化
- 判断玩家所在行, 列是否是
S
所以其实是可以实现类似居合和见切的效果的(玩太刀玩的) 不然也无法通关 e.g.
1 | → → |
最后得到路线后nc到官方的端口并输入路线 得到flag:nbctf{5lowly_5huffl3d 5wa110wing_54nd5}
Itchy Scratchy | Scratch编程逆向
没有混淆逻辑很清晰的Scratch工程文件:
alpha
和enc
也都给出了 唯一的问题是最后需要解方程 这里用z3求解
1 | from z3 import * |
Twostep | 构式加密
题如其名 用了类似两步舞的加密方式 有一种独特的美感(但是逆向的时候感觉不出来😡)
用DIE打开附件 64位ELF无壳
主函数的逻辑依然很简单:
1 | __int64 __fastcall main(int a1, char **a2, char **a3) |
但是打开encrypto()
时出现了IDA无法反编译的错误 发现是text.40168C处发生了call无法解析的错误我们去看看这个铸币是怎么回事
1 | .text:000000000040168B FF D0 call rax |
上下文也看不出rax存放的是固定的某个函数还是什么 不要紧我们先nop掉它 顺利反编译了encrypto()
发现它调用了encrypto2()
(这些都是我自己命名的) 打开encrypto2()
发现它调用了encrypto()
… 这就是”两步舞”的含义 不管 先分析两个函数
1 | __int64 __fastcall encrypto(_BYTE *input_6) |
两个加密函数都调用了的check1_var_add(_BYTE *input_6)
实际上是一个通过当前”步数”(var
对应每个case块的加密)来返回当前加密的单词在整个flag中的位置(flag中每个单词用_
分割) 动态调试时发现步数和位置的对应关系如下:
1 | index: 4, 7, 2, 0, 3, 6, 5, 1, 8 |
其实得到了这些信息后一步步攻破每个case块的加密即可 但看出check1_var_add(_BYTE *input_6)
的作用实际上也是一个 以下给出每个加密块对应的解密脚本:
1 | # case 1: |
最后问题来了 实际上call rax对应到伪代码就是
1 | case 8LL: |
那么其实rax是根据计算得到的不确定的值 但是原来的程序少了这一步貌似也是甚至就应该这样照常运行 那么什么时候call rax相当于什么都没做呢?
- call $+5 ;花指令
- call rax ;rax -> ret 调用后立即返回
第一种显然不太可能 那就是当rax == retn(C3)
时了! 于是写出最后一块解密脚本:
1 | # case 8: |
最后组合起来得到最终flag:nbctf{L3f7_f0rw4Rd_r1gh7_rIghT_l3FT_1n_RigH7_b4cK_return}
(其实nbctf{L3f7_f0rw4Rd_r1gh7_rIghT_l3FTl_1n_RigH7_b4cK_return
也能让程序返回正确的提示 但显然是非预期解)