本文为看雪论坛优秀文章
看雪论坛作者ID:WangONC
把程序放进IDA PRO中,通过字符串定位到主要代码,发现有这样的内容:
R后得到字符串,调换顺序得到最终的flag。
debug
本题主要考察idapro远程调试。程序为elf。查看程序伪代码发现一串没有规律的变量,猜测可能为flag,但是要经过某种计算。且有些不是ASCII,猜测可能为flag,但是要经过某种计算。下面有一个:sub_556EC85DF9B4((__int64)&v8, (__int64)&s, v3);
这是唯一一个对字符串进行操作的函数,把程序放进linux中进行动态调试,在这句代码后添加断点,执行后查看相应内存即可看到flag。ReadAsm2
input= [0x0, 0x67, 0x6e, 0x62, 0x63, 0x7e, 0x74, 0x62, 0x69, 0x6d,
0x55, 0x6a, 0x7f, 0x60, 0x51, 0x66, 0x63, 0x4e, 0x66, 0x7b,
0x71, 0x4a, 0x74, 0x76, 0x6b, 0x70, 0x79, 0x66 , 0x1c]
for i in range(1,29):
print(chr(input[i]^i),end='')
480小时精通c++
先把文件放入010edito,发现是一个elf文件,没有加壳。进入idapro,查看伪代码,通过字符串定位到主要代码,发现主要代码很简单,且程序中有字符串,但是字符串并不是flag。猜测给出的字符串可能是经过处理或者未经过处理字符串,运行程序得到的结果就是程序中的字符串。伪代码中给定的c++代码比较复杂,回去看汇编代码发现有一段被nop掉:在nop之前 lea rax,[rbp+var_50]应该就是得到的最终输出数据。 明显为加密函数,主要函数调用很多个子函数,没有其他内容,只看子函数的算法就行。进入一个子函数后,从这一部分可以得知:a1为输入字符串,a2为长度,且核心算法为异或,所以为可逆加密。到此得知程序nop掉的就是加密算法的执行,加密后的字符串是加密后添加上去的,程序本身并没有执行加密算法。所以既然是可逆,把加密后的算法重新带入加密函数即可得到flag。修改当前执行到加密函数,把加密后的字符串栈地址填入,长度为38,执行加密后重新查看栈即可得到flag。py交易
本题目给出一个pyc文件,使用python反编译工具可以直接得到python代码。def encode(message):
s = ''
for i in message:
x = ord(i) ^ 32
x = x + 16
s += chr(x)
return base64.b64encode(s)
从这个函数就知道了代码中给出的字符串的编码方式。写一个脚本执行逆过程即可。key1='^SdVkT#S ]`Y\\!^)\x8f\x80ism' #这个字符串是将代码中给出的字符串base64解码后的字符串
s=''
for i in key1:
x=ord(i)-16
x=x^32
print(chr(x),end='')
Our 16bit wars
得到程序后,发现程序是16位程序,无法执行和动态调试,也不能看到直观的伪代码,只能分析汇编代码。在程序数据区能发现两个字符段,第一段为输入提示,第二段是无意义的字符串,猜测第二段字符串可能是加密后的flag。在主函数中发现代码很少,调用了只调用了一个函数,所以之观察那个函数即可。在主函数中发现也是调用了不同的函数。分别观察各个函数。所以主要关注加密函数即可。查看加密函数,本想尝试逆算法,然后发现算法中存在逻辑左移和右移,算法不可逆,无法倒推出flag。然后想到可以根据算法进行爆破,可以把ASCII中可见字符带入算法,然后和加密后的字符串进行对比,对比后即可得到flag。Our 16bit Games
这个题目给出的程序是一个16位程序,不能直接执行和动态调试。直接把程序放入idapro中查看汇编代码。带码比较复杂,没有找到直接存储字符串的区域。向下找代码的时候发现有这样一段代码:可能就是flag,经过计算之后调用输出,但是bx寄存器的值未知,无法进行计算。flag的最后一位通常是‘}’,带入最后一段计算的到bl的值为0xde。所以经过交换之后,隔一段应该又是0xde,可以一直一算出部分flag。直到计算到最开始,得到了C和F,猜测中间可能为T:计算得到bx寄存器另一半的值为0xc0,最后全都计算出来即可得到flag。maze
这是一个elf文件,拖进idapro分析。查看源代码.看到了这样的一个字符串:目前还不能看出来其含义。分析伪代码,整体流程为先输入flag,然后对比长度,可以看出flag长度为24位,且格式为nctf{xxxxxxxx},所以{}内长度应该为18位。再下面是从字符串第六位开始遍历,分别比较是不是'oO0.'这四个字符,每一种情况分别对应不同的函数调用。这四个函数的参数分别v9和&v9+1,即v9和v9的下一个存储空间。再看下面的400690函数,有三个参数,分别是开始看到的字符串、v9和&v9+1、400690函数中有这样两句代码。result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);
LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
a1是字符串,a2和a3分别是v9和&v9+1,观察代码,a38,再结合题目,意思为迷宫,给出的字符产串长度为64,正好可以组成一个8 8方阵,所以猜测v9和&v9+1可能是坐标,且对比为' '或'#',正好字符串中存在着脸肿字符。再结合下面的代码:if ( asc_601060[8 * (signed int)v9 + SHIDWORD(v9)] != '#' )
SHIDWORD为IDA的宏定义,为 #define SHIDWORD(x) (((int32)&(x)+1)) ,印证了是坐标的想法。所以再看上面的四个函数,是分别对横坐标和纵坐标的修改,即oO0,四种字符分别对用四种点的移动方向,那个8*8方阵这正好可以组成一个迷宫,最后终点是#。(用0代替了空格),根据v9的初始化值,起点应该是左上角的点,通过空格走到字符'#'正好是18步,根据走的方向,即可得到flag。bt
这道题给出的是一个elf文件。放入idapro分析,定位到主要代码,查看伪代码。发现程序中有两段比较长且无规律的字符串,开始猜测是十六进制字符串,后来发现并不是。分析整个程序的执行顺序,程序先输入一段字符串,然后会经历两次判断,两次判断都通过才会提示正确,且格式化输入为64位,输出语句printf("TQL! TQL! flag: nctf{%s}\n", byte_601100);
可以得知输入的64位字符串并不包含nctf{},在两次判断之前,会经历两个函数,这两个函数是两个递归,然后再与给定字符串相比较。int64 __fastcall sub_400666(signed int a1)
{
int v1; // eax
__int64 result; // rax
if ( a1 <= 63 )
{
v1 = dword_601064++;
*(&s1 + v1) = byte_601100[a1];
sub_400666(2 * a1 + 1);
result = sub_400666(2 * (a1 + 1));
}
return result;
}
观察第一个递归函数,发现函数对输入字符串的顺序进行了改变,然后与给定字符串进行对比,且是从0按位遍历的。int64 __fastcall sub_4006BE(signed int a1, __int64 a2)
{
int v2; // eax
__int64 result; // rax
if ( a1 <= 63 )
{
sub_4006BE(2 * a1 + 1, a2);
v2 = dword_601064++;
*(&s1 + v2) = byte_601100[a1];
result = sub_4006BE(2 * (a1 + 1), a2);
}
return result;
}
再两个函数之间存在一个对变量置零的过程,发现这个函数与第一个函数形式相似,也是从0开始遍历改变字符串顺序的递归函数,取额使用的是同一个输入数据段。所以两个给定的字符串应该是用输入字符串通过两个函数执行后的不同结果,只需要逆出一个函数的算法就可以得到原字符串,即flag。因为递归执行比较复杂,所以直接使用脚本进行代码还原。s = []
byte_601100 = []
dword = 0
result = 0
for i in range(64):
byte_601100.append(i)
for i in range(65):
s.append(99)
def test(a1):
global s, result, dword
if (a1 <= 63):
dword+=1
v1 = dword
s[0 + v1] = byte_601100[a1]
test(2 * a1 + 1)
result = test(2 * (a1 + 1))
return result
test(0)
print(s)
得到的数组数字顺序即是字符串改变后的顺序。(第一位的99为初始值,未进行操作,忽略)t=[ 0, 1, 3, 7, 15, 31, 63, 32, 16, 33, 34, 8, 17, 35, 36, 18, 37, 38, 4, 9, 19, 39, 40, 20, 41, 42, 10, 21, 43, 44, 22, 45, 46, 2, 5, 11, 23, 47, 48, 24, 49, 50, 12, 25, 51, 52, 26, 53, 54, 6, 13, 27, 55, 56, 28, 57, 58, 14, 29, 59, 60, 30, 61, 62]
q='bcec8d7dcda25d91ed3e0b720cbb6cf202b09fedbc3e017774273ef5d5581794'
a=[]
for i in range(64):
a.append('')
for i in range(64):
a[t[i]]=q[i]
for i in range(64):
print(a[i],end='')
把得到的字符串放如程序中验证,发现输出了正确的结果。所以把最后得到的输出结果放入nctf{}中即为最flag。WxyVM
本题目是一个elf,放入idapro中查看代码,查看伪代码,发现代程序代码比较简单,通过代码可以得知flag长度为24位。if ( *(&byte_604B80 + i) != dword_601060[i] )
输入的字符串处理后和dword_601060对比,所以只要逆算法把dword_601060还原就能得到flag了。进入函数查看代码,发现函数主要有一个长度是15000的数组,且三个一组,一共循环5000次。下面有一个选择,共分为五种运算,由每组中的第一个数字控制。result = byte_6010C0[i + 1];
*(&byte_604B80 + result) += v3;
每一组中的第二个数字控制了输入字符串的第几位进行运算。所以可以写出脚本,把15000位数组复制进脚本中,把五种运算中的+-*分别换成-+/即可。然后观察计算后的数组串,格式为int,输入进行对比的为字符串,观察数据发现明显每隔四位只有低地址位有值,其他三位应该是初始化的结果。且输入数据和本数据格式不一样,不能直接比较,应该直接取四位中的低地址位比较。签到题
题目比较简单,但是很麻烦。程序放入idapro中,查看伪代码,主函数中引用了两个函数,其他全都是输出函数。其中一个函数只是引入了一个变量,所以主要运算应该在另一个函数中。查看那个函数,发现大量的变量和运算。运算结束后把每一位对一个字符串进行对比。从下方循环可以得知flag一共有49位,把下方已知字符串中的49位分别带回到v2到v50中,解出方程组即可得到最终的flag。Single
本程序是一个elf程序,放进idapro中进行调试。查看伪代码。主函数部分比较简单,主要调用了这三个函数sub_40070E(&s);
sub_40078B(&s, (__int64)&unk_602080);
sub_400AD4((__int64)&unk_602080);
size_t __fastcall sub_40070E(const char *a1)
{
size_t result; // rax
int i; // [rsp+1Ch] [rbp-14h]
if ( strlen(a1) > 0x51 )
sub_4006F6(a1);
for ( i = 0; ; ++i )
{
result = strlen(a1);
if ( i >= result )
break;
if ( a1[i] <= 47 || a1[i] > 57 )
sub_4006F6(a1);
}
return result;
}
第一个函数主要限制了输入字符串的格式,这个函数限制了输入字符串的长度和字符,长度不超过81,字符为0到9。size_t __fastcall sub_40078B(const char *a1, __int64 a2)
{
size_t result; // rax
int i; // [rsp+1Ch] [rbp-14h]
for ( i = 0; ; ++i )
{
result = strlen(a1);
if ( i >= result )
break;
if ( a1[i] != 48 )
{
if ( !a1[i] || *(_BYTE *)(i + a2) )
sub_4006F6(a1);
*(_BYTE *)(i + a2) = a1[i] - 48;
}
}
return result;
}
主要要求输入字符串在非0的情况下,给定数据unk_602080中的对用的数字要为0,否则跳出失败。下面的语句把输入字符串中的字符数字转化为数字存储在给定数据对应为0的位置。第三个函数中有三个函数,前两个函数比较相似,目前还不能看出来目的是什么,但是其中有这样一句:++s[*(unsigned __int8 *)(9 * i + j + a1)]
这里可以看出i和j可能是坐标,且另一个相似的函数中也有这个语句,i与j相反,则本题目可能是一个9*9方阵,所以输入长度为81。然后看最后一个函数,一部分代码结构与前两个类似,只不过比前两个又多了两层限制为3的循环,且都有这句代码: ++s[*(unsigned __int8 *)(9 * j + k + a1)];
按照坐标的思想,第一个函数是按行遍历的,第二个函数是按列遍历的,最后一个函数是按照3*3方块每行遍历,然后遍历9个方块。三个函数都出现的相似语句作用是把字符串s的对应a1(嵌入输入数据后的原来给定数据)按一定顺序遍历的值的位置自增1,例如a1中按行(或列)遍历的值分别为1、2、3,则s[1],s[2],s[3]分别自增1,然后在下边进行条件匹配判断。for ( l = 1; l <= 9; ++l )
{
if ( s[l] != 1 )
sub_4006F6(s);
}
s中从1到9按位遍历,如果有不是1的位置,则跳出错误。结合上面的嵌入操作,就是说按某顺序遍历过的位置要有从1到9所有的数字。结合上面的三种遍历方式,可以判断出这道题是个数独。数独的题目就是原本给定的81个数字。
将这81个数字写成是9*9方阵,0的地方就是要填入的数字。00 03 00 06 00 00 00 00 00
06 00 00 00 03 02 04 09 00
00 09 00 01 00 07 00 06 00
07 04 06 00 00 00 00 00 00
00 01 08 00 00 00 06 03 00
00 00 00 00 00 00 01 04 07
00 08 00 09 00 04 00 07 00
00 07 04 02 01 00 00 00 06
00 00 00 00 00 03 00 01 00
完成数独后,把题目原先存在的数字变成0(第二个函数的限制),就得到了最后的结果。在最后输入flag的过程中,直接输入得到的字符串,外面加上nctf{}都不是正确的flag。后来发现flag格式是flag{xxxxxxx}。看雪ID:WangONC
https://bbs.pediy.com/user-home-891116.htm
*本文由看雪论坛 WangONC 原创,转载请注明来自看雪社区。