Reverse
Padlock
一进来,我超,直接给原码?
但是点开之后我才发现自己想的太简单了……
格式方面的问题,可以在线找一个reformater解决,配合手动去注释和格式纠正得到以下可编译代码:
可以看见,作者通过define预定义函数和指针变量,简化库函数以方便操作,之后的部分就是一串简单的字符变换,通过转换字符集中的对应字符,而后将字符四个一组存入对应地址S中,在进行形似X + S[u++] - S[u++] + (S[u++] ^ S[u++])
的变换得到原字符串。
那么问题来了,我们在哪里找到密文,又在哪里找到X,也就是代码中的-b[1][u / 4]
?之后的三目运算符判断又是怎么回事?
第一个问题很好解释,由于作者直接将读字符的for循环的下标设在Q的指针上,再加上手动搜索程序名quine的定义,我们可以明白,待处理字符集就是程序代码本身。但由于我们需要原代码(解混淆前的原装源代码)作为字符集,而原代码因为某些特性过于古老而难以编译,这里推荐使用Dev-C++的环境强行编译,哪怕不可跑通,也可以塞进ida,然后再从ida导出字符集数组。
但是第二个问题呢?由于b在代码中以指针形式存在,不论以二维调用、一维调用和变量调用都难以解释,于是我尝试了调用C数组,调用D数组,甚至调用Q本身,但依旧没有成效。
后来我注意到,在Q中预定义的b,从始至终,就没有被赋值或调用。也就是说,它打从一开始就是不存在的。
并且,对于三目运算符的问题,一开始我将其视作01字串的类似物来操作,但也一筹莫展。直到我注意到运算符的结果是“X”和“O”,我才想起,这会不会是判断输出的正误?尝试一番,果真如此。
于是,在经历了一下午的绞尽脑汁过后,终于产出了如下题解:
#include <string.h>
#include <stdio.h>
unsigned char H[5140], * D = H;
int M[99999], *C = M, *S = M + 293;
unsigned char L[] =
{
0x69, 0x6E, 0x74, 0x2A, 0x42, 0x3D, 0x4D, 0x2B, 0x35, 0x34,
...
0x6D, 0x5F, 0x73, 0x74, 0x75, 0x66, 0x2A, 0x2F, 0x22, 0x00
};
int main()
{
freopen("output.txt","w",stdout);
int i = 0,l = strlen(L);
while(i<l){
if ( L[i] == 124) *C++ = 10;
else if ( L[i] == 126) *C++ = 32;
else if ( L[i] == 33) *C++ = 34;
else if ( L[i] > 34) *C++ = L[i];
*D++ = L[i] == 32 ? '\n' : L[i] ;
i++;
}
for(int u = 0; u < l * 4 ;)
putchar( S[u++] - S[u++] + (S[u++] ^ S[u++]));
printf("\n");
// for(int u = 0; u < l;)putchar(H[u++]);
// printf("\n");
fclose(stdout);
return 0;
}
最后在output.txt文件中找到对应flag:
bctf{qu1n3_1s_4ll_ab0ut_r3p371t10n_4nD_m4n1pul4710n_OwO_OuO_UwU}
chicago
我们尝试跑一下程序:
得到两个报错信息。再看一下反编译:
尝试在短时间内理解如此混乱的代码显然是不现实,且不经济的。于是我们打算找到一些有用信息以进行推断。
首先,刚才运行的时候,输出中会产生Bad length
和called `Option::unwrap()` on a `None` value
的报错,并且,如果输入的值不相同也会产生报错:
我们回到代码,又找到了一个判断strlen的类似物:
以及在跳过判负函数之后sub_1CCD0下的一个cat flag
(关于判负函数如何查找,可以在公共字符集中找到刚才的报错内容逆推,或者在核心函数中查找调用的字符或数据:
自此,我们可以推断出输入的几个要求:
1、只能是数字
2、只能是相同的字符
3、长度大于10
再加上这道题看似可以当pwn做,由是,我们可以半自动暴力遍历payload,以尝试查找偏移点:
from pwn import *
from time import *
context.log_level = 'debug'
payload = b'1'*10
while 1:
r = process('./chicago')
r.sendline(payload)
print(payload)
r.interactive()
payload += b'1'
但跑着跑着就出flag了,本地运行跑出cat flag
指令后最后成功解出如下:
bctf{s7r1pp3d_ru57_15_a_p41n_1n_th3_n3ck}
但是在后续nc测试的时候,又出现任意>10长度的0字符也可以触发catflag的情况,由于本人没弄清源码原理,官方wp也含混不清,所以怀疑是出题人的失误导致了严重非预期,求各位dalao的解析。
baby noah
看似是一道类pwn的交互题,与pwn的某两道题是同系列,打开,无输出:
进主函数:
逻辑如下:
1、从文件读字符串,存入byte_ 4568
2、判断map的大小是否在1200以内,默认24行50列
3、从stdin读入map(+2应该是为了换行),直到最后只剩一个换行符
4、对文本持续进行sub_ 1450变换,直到跑通或者无路可走
5、判断整张地图是否符合sub_ 1450 return 1,且dword_ 4030是否=201527,如果是则输出flag
从变量名和代码特征推断是地图题,接下来就是sub_ 1450的逻辑问题。
发现sub_ 1450存在连片的case判断:
已知该函数特判的字符集为!#%&*+,-./:<>@^_gpv|~
。从零碎的信息可以推断,如果路程跑得过大,或者是触发了某些判负条件,就会最终return 2导致判负跳出while,最明显的就是跳到Label6。当然,过程中return 0并不会导致一些奇怪情况的发生。
我们可以通过搜索找到唯一的return 1,那就是最后的点为“@”。
按照题目描述,相较于pwn版本,这题是莫得hack的。虽然我们实质上可以对dword_ 4030构造函数进行覆写来getflag,但是,我们首先得构造一个可以跑过sub_ 1450的地图,这个地图必须能走到终点“@”,且路程符合题设要求。
回到算法部分,整个判断集赋予了一些符号和变量特殊定义。推断得知:
v0:当前点的状态
dword_ 4030:走过的步数(不能超过3224432,否则判负)
dword_ 4550、dword_ 4554:当前的航速向量
dword_ 4560、dword_ 4564:执行完当前步数后的横纵坐标
dword_ 455C(行,对应y轴)、dword_ 4558(列,对应x轴):横纵坐标上限
dword_ 4034:残余步数/体力值(推断依据:在许多函数中出现,且<=0则判负,但也不能嗯刷体力超过99,否则也判负)
byte_ 4038:正体不明,推测是历史路径相关的一些判定
是的,这个地图题甚至使用了以横纵坐标计算为基础的航向系统,取代了传统的wasd,这就意味着这道题的难度又上了亿个档次。
不过好在,这道题的边界限制是“基于边界取模的循环”,也就是模拟世界地图,所以边界方面的事情基本可以抛诸脑后,我们只要绘制一张航线就行……吧。
我们再来看看其它的符号(个人对于横纵坐标的理解可能有偏差,如下操作表仅供参考):
无体力值需求的部分(虽然在之后的判定中也会因为体力不够而扑街)
小于号:y坐标航速沿负向+1,随后以变更后航速与航向行进一个单位时间
大于号:y坐标航速沿正向+1,随后以变更后航速与航向行进一个单位时间
上三角标/异或号:x坐标航速沿负向+1,随后以变更后航速与航向行进一个单位时间
字母v:x坐标航速沿正向+1,随后以变更后航速与航向行进一个单位时间
非特殊符号:判断体力值是否>99,若大于判负,随后byte_4038[当前体力值+1] = 0
,体力值+1,并以当前航速与航向行进一个单位时间
体力值需求>=1的部分
感叹号:当前体力值下byte_ 4038取反,并以当前航速与航向行进一个单位时间
井号:体力值+1,byte_4038[当前体力值+1] = byte_4038[当前体力值]
,之后以当前航速与航向行进一个单位时间
句点号:输出当前体力值下byte_ 4038,并以当前航速与航向行进一个单位时间(pwn爷可利用)
冒号:输出当前体力值下byte_ 4038,并以当前航速与航向行进一个单位时间(pwn爷可利用)
下划线:y坐标航速视当前体力值下byte_ 4038是/否为0沿正向/负向+1,随后以变更后航速与航向行进一个单位时间
竖线:x坐标航速视当前体力值下byte_ 4038是/否为0沿正向/负向+1,随后以变更后航速与航向行进一个单位时间
波浪线:体力值-1,并以当前航速与航向行进一个单位时间
体力值需求>=2的部分
百分号/取模号:体力值-1,然后
byte_4038[当前体力值] %= byte_4038[当前体力值+1]
,并以当前航速与航向行进一个单位时间
与号:体力值-1,然后byte_4038[当前体力值] &= byte_4038[当前体力值+1]
,并以当前航速与航向行进一个单位时间
星号/乘号:体力值-1,然后byte_4038[当前体力值] *= byte_4038[当前体力值+1]
,并以当前航速与航向行进一个单位时间
加号:体力值-1,然后byte_4038[当前体力值] += byte_4038[当前体力值+1]
,并以当前航速与航向行进一个单位时间
逗号:体力值-1,然后byte_4038[当前体力值] = byte_4038[当前体力值+1]
,以当前航速与航向行进一个单位时间
减号:体力值-1,然后byte_4038[当前体力值] -= byte_4038[当前体力值+1]
,以当前航速与航向行进一个单位时间
左斜杠/除号:体力值-1,然后byte_4038[当前体力值] /= byte_4038[当前体力值+1]
,然后以当前航速与航向行进一个单位时间
字母g:判断byte_4038[当前体力值-1]
是否越过y坐标边界,byte_4038[当前体力值]
是否越过x坐标边界,随后体力值-1,然后以当前航速与航向行进一个单位时间,并使byte_4038[变更后体力值] = map[byte_4038[变更后体力值]][byte_4038[变更后体力值+1]]
体力值需求>=3的部分
字母p:判断
byte_4038[当前体力值-2]
是否越过y坐标边界,byte_4038[当前体力值-1]
是否越过x坐标边界,随后体力值-3,然后以当前航速与航向行进一个单位时间,并使map[byte_4038[变更后体力值+1]][byte_4038[变更后体力值+2]]=byte_4038[变更前体力值]
如上,我们整理了所有的海图格式,接下来就是生成方法的部分了。但是,如果要利用A* 算法加上暴力遍历,怎么说至少也要O(log^2 n * block_types)的时间复杂度,并且由于小地图上长路径导致的一些必要的回环,以及速度、向量之类的计算,实际复杂度只增不减。
算了,丢给算法佬吧,我都多久没碰算法了QAQ
Safe
Arduino逆向,本就没什么数电经验的我更加雪上加霜
通过avr-objdump的指令调出一段汇编,但是我也不怎么会手撕这么长的汇编,一时间也没找到导出代码的方法,遂放掉了
00000000 <.sec1>:
0: 0c 94 61 00 jmp 0xc2 ; 0xc2
4: 0c 94 7e 00 jmp 0xfc ; 0xfc
8: 0c 94 7e 00 jmp 0xfc ; 0xfc
c: 0c 94 7e 00 jmp 0xfc ; 0xfc
10: 0c 94 7e 00 jmp 0xfc ; 0xfc
14: 0c 94 7e 00 jmp 0xfc ; 0xfc
18: 0c 94 7e 00 jmp 0xfc ; 0xfc
1c: 0c 94 7e 00 jmp 0xfc ; 0xfc
20: 0c 94 7e 00 jmp 0xfc ; 0xfc
……