2023SharkCTF-week2-Reverse-wp
2023SharkCTF-week2-Reverse-wp
2023.8.21 by Shen_Fan
before_main
基本阅读ida反汇编代码逻辑后可以发现该程序读入一个字符串,然后使用sub_4015DA对字符串进行处理并返回一个指针作为处理后的结果,再将处理后的结果与Str2比较并输出正误。
sub_4015DA由标志性的base64字母表ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/与Str2的形态可以确定为未换表的base64,考虑直接对Str2解密。
解密后得到:
1 | sh@rkctf{something_run_before_main!} |
并且输入程序时显示flag错误,鉴定为假flag。
对Str2查找交叉引用发现一个函数对Str2进行了赋值,猜测该函数在main前运行,对该函数中的赋值进行base64解密得到:
1 | sharkctf{N3w_Bln7_l3_S0_us3fv1_iN_Pr07r@mlng} |
获得flag。
a_cup_of_tea
分析ida返回表代码逻辑后发现程序读入一个字符串,并在判断长度是否为23后将其与一个局部变量数组一起传入一个encrypt函数,然后将Str数组的前16个字节与res数组比较后输出正误
(为啥23字节的数组最后只比对了前16个字节呢,因为有人出题出傻了,给各位师傅磕头了)
对encrypt函数进行简单逆向后得到解密函数(也可以直接照抄ctf-wiki上tea加密的解密函数)
1 | void decrypt(unsigned int *a1, unsigned int* a2) { |
解密后得到flag
1 | i7_IS_JuSt_4_CUP |
结合hint得到最终flag
1 | sharkctf{i7_IS_JuSt_4_CUP_Of_7e4} |
ez_maze
分析逻辑后得知该迷宫由数字0123操作,否则会报出”invalid move”退出,对应的操作下的变量v5猜测为当前位置。0对应+1,向右;1对应-1,向左;2对应-6,猜测为向上;3对应+6,猜测为向下。那么迷宫的列数应该为6,这样+6或-6可以在行之间移动。 当v5=5时循环退出,猜测位置5为终点。迷宫的逻辑为将当前位置与0x2a异或,若异或结果为0,则输出hit the wall并退出。最终退出循环时若位置为5且无多余步骤,就输出flag。
考虑直接拷贝迷宫手动走,墙为0x2a,将进行与0x2a异或的数组byte_403020提取得:
1 | unsigned char ida_chars[] = |
稍微排版一下让列数为6
1 | unsigned char ida_chars[] = |
可以观察到唯一道路的迷宫,手动走出flag:
1 | sharkctf{003313300022220} |
Hungry_Snake
本周的压轴题,由于第二周的签到题离奇消失临时整的。
本题的难度主要在于在屎山一般的代码中寻找flag加密的主逻辑并写脚本恢复,
题目给出了结构体信息,先找教程在ida中恢复结构体(你也不想盯着一堆偏移硬看吧)
对于较为复杂的题目,我们一般从flag的输入和输出下手,追踪flag去了哪里并分析其逻辑,这里从输入入手。
1 | printf("flag:"); |
首先flag被scanf读入至数组Str,然后对Str进行了长度判断
1 | gameInit(Str); |
下一次也是最后一次出现时是在游戏初始化过程,由于Str为局部变量一般不会被其他函数访问,可以考虑Str通过游戏初始化被完全转移了。
进入gameInit函数并只关注与他参数有关的代码部分
1 | memset(&game.in[game.total], 0, 0x30ui64); |
结合上文
1 | game.width = 16; |
得知game.in为新分配的一块大小为地图大小+48字节的内存,而game.in前game.total字节被设置为’.’,猜测为存储地图,之后的48字节由上面的strcpy(&game.in[game.total], a1);猜测为存储输入的flag。
之后线索断了,但我们知道应该去game.in偏移为game.total的地方找我们的输入
查看游戏主循环
1 | scanKey(); |
scanKey经查看发现只获取输入并按一定规则改变蛇的方向。printMap仅仅是将game.in的前game.total字节输出,故排除。
moveSnake函数中没有直接使用game.in中的game.total以后的部分,但是在贪吃蛇所处位置值为’*’后,该函数会调用一个updateMap函数,进入后发现该函数即为对game.in的game.total偏移进行加密的函数。
1 | unsigned __int64 updateMap() |
注意到加密过程中的部分密钥来自于game结构体的一些内容,需要注意。
除此以外没有其他部分再使用game.total以后的内容,故猜测flag仅进行了这一次加密。
游戏胜利结束后,main会调用一个checkFlag函数对game.in的game.total偏移后的内容与一个数组aetx进行比较并返回结果,故猜测aetx为flag加密后的结果。
回看加密,该加密每次吃到东西都会调用一次,结合胜利条件game.snake.len=game.total可知总调用次数为255次(初始长度为1),同时加密过程中所使用的delay_time初始值为190,之后递减10至50便不会变化。针对以上特征编写解密脚本:
1 |
|
得到最终flag:
1 | sharkctf{WoW_yOU_arE_pl4y1NG_Sn@kE_5o_w3ll!!} |