0x01 py逆向
给了一个main.py和out.txt 的文件,打开分析一下
import random
key1 = random.choices(range(256), k=20) #0到255里取20个随机数
key2 = list(range(256)) #0到255的列表
random.shuffle(key2) #随机排列
flag = open('flag.txt', 'rb').read() #读取flag,设置成input
def enc(n): #加密函数
q = key2[n]
w = key1[q % 20]
n ^= q
return n, w
x = 0x00000000
for i, c in enumerate(flag): #枚举遍历,第一个是下标,第二个是里面的东西
x <<= 8 #x左移动8位,一个字节
n, w = enc(c) #根据密钥和flag出两个数
if i % 2: #如果是奇数下标,swap交换
n, w = w, n
x |= n
x |= (w << ((2 * i + 1) * 8) ) #按位运算的优先级高于赋值运算
print(key1)
print(key2)
print(x)
然后手动调试一下前几项,很容易发现x 的组成是由w和n交替排列的,大概的规律就是
w34n33......w2n1w0n0w1n2......w33n34
根据output文件里的x的值,转码成十六进制
(0x)ea0149929a24de3a6ab642ebeaa16a02571f572449b67f1f57c4a4b67fc542027feb6a0eeaab4d9c42ab6a3e5124deab6a045724de244db69a16ea016ab6571f57a1a2fe4290
总共有70个字符,flag推算出是35位
根据这坨玩意写个抽象的脚本分离出w和n
enc="ea0149929a24de3a6ab642ebeaa16a02571f572449b67f1f57c4a4b67fc542027feb6a0eeaab4d9c42ab6a3e5124deab6a045724de244db69a16ea016ab6571f57a1a2fe4290"
enc_l=[]
enc_r=[]
print(len(enc))
for i in range(0,70):
enc_l.append(enc[i])
for i in range(70,140):
enc_r.append(enc[i])
n_o=[0xea,0x49,0x9a,0xde,0x6a,0x42,0xea,0x6a,0x57,0x57,0x49,0x7f,0x57,0xa4,0x7f,0x42,0x7f,0x6a]
n_j=[0xea,0x4d,0x42,0x6a,0x51,0xde,0x6a,0x57,0xde,0x4d,0x9a,0xea,0x6a,0x57,0x57,0xa2,0x42]
w_o=[0x0e,0xab,0x9c,0xab,0x3e,0x24,0xab,0x04,0x24,0x24,0xb6,0x16,0x01,0xb6,0x1f,0xa1,0xfe,0x90]
w_j=[0x01,0x92,0x24,0x3a,0xb6,0xeb,0xa1,0x02,0x1f,0x24,0xb6,0x1f,0xc4,0xb6,0xc5,0x02,0xeb]
#n的偶数部分,34到0
for i in range(0,70,4):
print("0x",end="")
print(enc_l[i],end="")
print(enc_l[i+1],end="")
print(",",end="")
print("\n")
#n的奇数部分,1到33
for i in range(2,70,4):
print("0x",end="")
print(enc_r[i],end="")
print(enc_r[i+1],end="")
print(",",end="")
#w的奇数部分,33到1,
print("\n")
for i in range(2,70,4):
print("0x",end="")
print(enc_l[i],end="")
print(enc_l[i+1],end="")
print(",",end="")
#w的偶数部分 ,0到34
print("\n")
for i in range(0,70,4):
print("0x",end="")
print(enc_r[i],end="")
print(enc_r[i+1],end="")
print(",",end="")
print("\n")
n=[]
w=[]
j=17
for i in range(0,17):
n.append(n_o[j])
n.append(n_j[i])
j-=1
print(n)
j=16
for i in range(18):
w.append(w_o[i])
w.append(w_j[j])
j-=1
print(w)
_n = [106, 234, 127, 77, 66, 66, 127, 106, 164, 81, 87, 222, 127,
106, 73, 87, 87, 222, 87, 77, 106,
154, 234, 234, 66, 106, 106, 87, 222, 87, 154, 162, 73, 66,234]
_w = [14, 235, 171, 2, 156, 197, 171, 182, 62, 196, 36, 31, 171,
182, 4, 36, 36, 31, 36, 2, 182,
161, 22, 235, 1, 182, 182, 58, 31, 36, 161, 146, 254, 1, 144]
代码有点乱,大概思路就是先把70个字符分成两部分,然后分别从左到右分离出下标奇数部分的n和w,下标偶数部分的w和n,然后把这些n和w排好顺序,分别交替输出得到n和w 的数组。
这个过程读者可以自行完成,我这个只是参考,而且感觉很慢。最后得到的_n和_w便是结果
(脚本里输出的n和w有些问题,分别多了一项和少了一项,笔者懒得去修正了然后手动修改了一下得到正确的n和w,然后这个_n和_w也是有些问题的,当时看题时把main.py里的下标是奇数时,n和w替换,看成了偶数时才替换,所以这里得到的_n其实是实际的w,_w是实际上的n,搞反了,不过万幸的是不影响后面的爆破,只要换一下就好了)
接下来把得到的n和w拿来爆破出c就可以组合成flag了,至福~
key1=[127, 81, 241, 40, 222, 128, 45, 87, 27, 154,
66,162, 73, 176, 172, 164, 106, 234, 77, 5]
key2=[155, 117, 124, 113, 104, 46, 151, 71, 144, 229, 152, 240,
199, 88, 103, 105, 245, 209, 13, 82, 166, 9, 201, 233, 228,
154, 19, 5, 30, 141, 81, 206, 246, 232, 107, 29, 208, 253,
187, 116, 98, 160, 60, 7, 220, 143, 80, 239, 52, 15, 94, 50,
149, 241, 57, 92, 230, 100, 31, 51, 36, 24, 39, 14, 25, 90, 101,
55, 194, 225, 157, 102, 2, 26, 148, 161, 180, 120, 223, 165, 32,
146, 185, 243, 119, 210, 172, 244, 1, 125, 44, 35, 169, 179, 188,
64, 207, 33, 137, 200, 142, 182, 250, 195, 28, 4, 79, 191, 86, 215,
96, 236, 91, 122, 196, 87, 118, 231, 126, 97, 147, 67, 132, 190, 234,
237, 43, 193, 252, 18, 212, 163, 56, 73, 123, 176, 162, 23, 192, 49,
21, 242, 171, 112, 153, 238, 203, 134, 167, 93, 115, 95, 8, 12, 65,
217, 248, 168, 219, 47, 211, 108, 76, 129, 145, 62, 156, 34, 218, 135,
48, 70, 75, 3, 249, 72, 202, 133, 183, 38, 37, 227, 164, 173, 159, 251,
0, 174, 54, 20, 136, 53, 138, 99, 226, 178, 42, 66, 150, 205, 204, 214,
197, 235, 110, 216, 63, 45, 184, 74, 41, 177, 27, 69, 130, 89, 61, 247,
255, 17, 254, 181, 131, 22, 224, 83, 189, 59, 114, 139, 111,
68, 6, 84,11, 127, 221, 106, 77, 109, 158,
170, 16, 121, 222, 186, 10, 58, 175, 40, 128, 198, 78, 85, 213, 140]
x = 3449711664888782790334923396354433085218951813669043815144799745483347584183883892868078716490762334737115401929391994359609927294549975954045314661787321463018287415952
w=[106, 234, 127, 77, 66, 66, 127, 106, 164, 81, 87, 222, 127,
106, 73, 87, 87, 222, 87, 77, 106,
154, 234, 234, 66, 106, 106, 87, 222, 87, 154, 162, 73, 66,234]
n=[14, 235, 171, 2, 156, 197, 171, 182, 62, 196, 36, 31, 171,
182, 4, 36, 36, 31, 36, 2, 182,
161, 22, 235, 1, 182, 182, 58, 31, 36, 161, 146, 254, 1, 144]
flag=""
def enc(n): #加密函数
q = key2[n]
w = key1[q % 20]
n ^= q
return n, w
for i in range(35):
for j in range(32, 127):
a , b = enc(j)
if a==n[i] and b==w[i]:
flag += chr(j)
break
print(flag)
nbctf{cr15s_cr0ss_str4wb3rry_s4uz3}
0x02 sands
看介绍就知道是迷宫题目,打开ida分析一下
有这几个关键的语句,和普通迷宫不同的是,这个迷宫会有‘S’,沙子没回合都会根据step移动来阻挡你的路线,如果沙子移动后正好和你的位置重合,会直接导致游戏失败(逻辑是沙子先动人后动),因为沙子撞人的判断是人移动后的,所以在这个迷宫中,只要你在沙子撞到你后移动走,也是能继续游戏的,这个是关键,知道这个小技巧后走迷宫就简单许多了。
比赛时为了节省时间可以省去不必要的东西,只保留关键的能帮助解题的判断语句,赛后我把迷宫完善了不少,已经能当做一个小游戏玩了。
#include<iostream>
int map[100]={38, 35, 35, 35, 48, 48, 48, 48, 48, 35,
48, 48, 35, 83, 48, 35, 35, 48, 48, 35, 35,
48, 83, 35, 48, 35, 83, 83, 48, 35, 35, 48,
48, 35, 48, 35, 48, 48, 35, 35, 48, 83, 48,
35, 48, 35, 48, 83, 83, 48, 48, 35, 35, 35,
48, 35, 48, 83, 48, 48, 48, 48, 48, 35, 48,
35, 48, 48, 48, 83, 35, 35, 48, 35, 48, 35,
35, 35, 35, 48, 48, 83, 48, 83, 48, 35, 48,
48, 83, 48, 48, 48, 83, 48, 48, 35, 76, 83,
48, 48};
int i=0,j=0,step=0;
char ch;
char step_arr[50]={0};
//i是横坐标,j是纵坐标
void move(char ch){
switch(ch){
case 'a':
if(i!=0){
if(map[10*j+i-1]=='0'||map[10*j+i-1]=='L'){
map[10*j+i-1]='&';
if(map[10*j+i]!='S'){
map[10*j+i]='0';
}
i--;
}
}
break;
case 'd':
if(i!=9){
if(map[10*j+i+1]=='0'||map[10*j+i+1]=='L'){
map[10*j+i+1]='&';
if(map[10*j+i]!='S'){
map[10*j+i]='0';
}
i++;
}
}
break;
case 's':
if(j!=9){
if(map[10*j+10+i]=='0'||map[10*j+10+i]=='L'){
map[10*j+10+i]='&';
if(map[10*j+i]!='S'){
map[10*j+i]='0';
}
j++;
}
}
break;
case 'w':
if(j!=0){
if(map[10*j-10+i]=='0'||map[10*j+i-10]=='L'){
map[10*j-10+i]='&';
if(map[10*j+i]!='S'){
map[10*j+i]='0';
}
j--;
}
}
break;
}
}
void set_map(){
int k;
for(k=0;k<100;k++){
if(k%10==0){
std::cout<<"\n";
}
printf("%c",map[k]);
}
}
void sand_move(){
int x,y;
switch(step%4){
case 3:
for(x=9;x>=0;x--){
for(y=0;y<=9;y++){
if(map[10*y+x]=='S'&&x!=9){
if(map[10*y+x+1]=='0'||map[10*y+x+1]=='&'){
map[10*y+x+1]='S';
map[10*y+x]='0';
}
}
}
}
break;
case 2:
for(y=0;y<=9;y++){
for(x=0;x<=9;x++){
if(map[10*y+x]=='S'&&y!=0){
if(map[10*y+x-10]=='0'||map[10*y+x-10]=='&'){
map[10*y+x-10]='S';
map[10*y+x]='0';
}
}
}
}
break;
case 1:
for(x=0;x<=9;x++){
for(y=0;y<=9;y++){
if(map[10*y+x]=='S'&&x!=0){
if(map[10*y+x-1]=='0'||map[10*y+x-1]=='&'){
map[10*y+x-1]='S';
map[10*y+x]='0';
}
}
}
}
break;
case 0:
for(y=9;y>=0;y--){
for(x=0;x<=9;x++){
if(map[10*y+x]=='S'&&y!=9){
if(map[10*y+x+10]=='0'||map[10/y+x+10]=='&'){
map[10*y+x+10]='S';
map[10*y+x]='0';
}
}
}
}
break;
}
}
int main(){
while(step<=49&&map[10*j+i]!='S'){
set_map();
std::cout<<"\n"<<"step:"<<step;
std::cout<<"\n"<<"sand de is "<<(step%4);
std::cout<<"\n"<<"(0是下移动,1是左移动,2是上移动,3是右移动)";
std::cout<<"\n"<<"enter your direcation:";
std::cin>>ch;
sand_move();
step_arr[step]=ch;
step++;
move(ch);
if(j==9&&i==6){
std::cout<<"yep"<<"\n";
for(int l=0;l<step;l++){
std::cout<<step_arr[l];
}
break;
}
}
return 0;
}
这样很浪费时间,不过赛后无所谓了。
然后这题还要连一下官方给的靶机,在linux虚拟机里面的命令行打入
nc 【ip】【端口】
连接成功后ls罗列一下文件,cat可以查看文件
0x03 twostep
这个题的逻辑就是反复调用两个不同的函数,然后分块加密,函数1中有几个子函数,每个子函数加密一块,函数2同理,然后调用法则是函数1到函数2,函数2再到函数1,每次调用时step(计数器)+1,然后case不同的step分块加密,最后根据一个数组word_404090[]知道这个加密块在整体flag具体的位置
#472036518 #123456789
下面是步数step,上面是位置。
函数1
__int64 __fastcall encry1(_BYTE *input)
{
__int64 result; // rax
char one; // [rsp+19h] [rbp-A7h]
__int16 v3; // [rsp+1Ah] [rbp-A6h]
__int16 v4; // [rsp+1Ch] [rbp-A4h]
__int16 v5; // [rsp+1Eh] [rbp-A2h]
unsigned __int64 i; // [rsp+20h] [rbp-A0h]
unsigned __int64 k; // [rsp+20h] [rbp-A0h]
__int64 v8; // [rsp+20h] [rbp-A0h]
unsigned __int64 m; // [rsp+20h] [rbp-A0h]
unsigned __int64 j; // [rsp+28h] [rbp-98h]
_BYTE *v11; // [rsp+30h] [rbp-90h]
_BYTE *v12; // [rsp+30h] [rbp-90h]
_BYTE *v13; // [rsp+30h] [rbp-90h]
__int16 v14[4]; // [rsp+38h] [rbp-88h]
__int64 v15[6]; // [rsp+40h] [rbp-80h]
__int64 v16[8]; // [rsp+70h] [rbp-50h]
__int16 v17; // [rsp+B6h] [rbp-Ah]
unsigned __int64 v18; // [rsp+B8h] [rbp-8h] 472036518
// 123456789
v18 = __readfsqword(0x28u);
v14[0] = 0x4808;
v14[1] = 0xC40;
v14[2] = 0x480C;
v14[3] = 0x408C;
v16[0] = 1LL;
v16[1] = 16LL;
v16[2] = 32LL;
v16[3] = 512LL;
v16[4] = 1024LL;
v16[5] = 2048LL;
v16[6] = 0x2000LL;
v16[7] = 0x4000LL;
v15[0] = 177LL;
v15[1] = 166LL;
v15[2] = 183LL;
v15[3] = 182LL;
v15[4] = 177LL;
v15[5] = 0xADLL;
v17 = 170;
switch ( step )
{
case 0LL:
if ( *input == 'n' && input[1] == 'b' && input[2] == 'c' && input[3] == 't' && input[4] == 'f' && input[5] == '{' )
result = encry2(input + 6); // step=0
else
result = 0LL;
break;
case 1LL:
v11 = step_change(input);
for ( i = 0LL; i <= 3; ++i ) // step=2
{
one = v11[i];
v4 = 0;
for ( j = 0LL; j <= 3; ++j )
{
v4 |= (one & 3) << (4 * j + 2);
one >>= 2;
}
if ( v4 != v14[i] )
return 0LL;
}
result = encry2(input);
break;
case 3LL:
v12 = step_change(input);
for ( k = 0LL; k <= 4; ++k ) // step=4
v100[k] = v12[k];
result = encry2(input);
break;
case 6LL:
v3 = *step_change(input);
v8 = 0LL; // step=7
while ( v3 )
{
v5 = v3 & -v3; // 等于1
if ( v8 == 8 )
return 0LL;
if ( v16[v8] == v5 )
++v8;
v3 ^= v5;
}
result = v8 == 8 && encry2(input);
break;
case 8LL:
v13 = step_change(input);
for ( m = 0LL; m <= 5; ++m ) // step=9
LOBYTE(v17) = v13[m] ^ v15[m]; // 去调试找找v17是多少
result = 1LL;
break;
default:
result = 0LL;
break;
}
return result;
}
函数2
__int64 __fastcall encry2(_BYTE *input)
{
__int64 result; // rax
unsigned __int64 i; // [rsp+18h] [rbp-E8h]
unsigned __int64 j; // [rsp+18h] [rbp-E8h]
unsigned __int64 k; // [rsp+18h] [rbp-E8h]
__int64 v5; // [rsp+20h] [rbp-E0h]
_BYTE *v6; // [rsp+28h] [rbp-D8h]
_BYTE *v7; // [rsp+28h] [rbp-D8h]
_BYTE *v8; // [rsp+28h] [rbp-D8h]
_BYTE *v9; // [rsp+28h] [rbp-D8h]
__int64 v10[6]; // [rsp+30h] [rbp-D0h]
__int64 v11[6]; // [rsp+60h] [rbp-A0h]
__int64 v12[6]; // [rsp+90h] [rbp-70h]
__int64 v13[8]; // [rsp+C0h] [rbp-40h] 472036518
// 123456789
v13[7] = __readfsqword(0x28u);
v13[0] = 54515LL;
v13[1] = 15689LL;
v13[2] = 4219LL;
v13[3] = 50297LL;
v13[4] = 43652LL;
v13[5] = 38919LL;
v10[0] = 14668LL;
v10[1] = 24063LL;
v10[2] = 37349LL;
v10[3] = 50716LL;
v10[4] = 61563LL;
v11[0] = 1843061LL;
v11[1] = 222420LL;
v11[2] = 5184810LL;
v11[3] = 4590105LL;
v11[4] = 2184197LL;
v5 = 0LL;
v12[0] = 1073741834LL;
v12[1] = 2415919110LL;
v12[2] = 939524099LL;
v12[3] = 536870913LL;
v12[4] = 1845493760LL;
switch ( step )
{
case 0LL:
v6 = step_change(input);
if ( (*v6 ^ 95) == v6[1] // step=1
&& (v6[1] ^ 117) == v6[2]
&& (v6[2] ^ 18) == v6[3]
&& (v6[3] ^ 56) == *v6
&& *v6 == 108 )
{
result = encry1(input);
}
else
{
result = 0LL;
}
break;
case 2LL:
v7 = step_change(input);
for ( i = 0LL; i <= 4; ++i ) // step=3
{
if ( v13[i] + v13[i + 1] * v7[i] != v11[i] )
return 0LL;
}
result = encry1(input);
break;
case 4LL:
v8 = step_change(input);
for ( j = 0LL; j <= 4; ++j ) // step=5
{
v5 += v100[j] + (v8[j] << 7);
if ( v5 != v10[j] )
return 0LL;
}
result = encry2(input);
break;
case 5LL:
v9 = step_change(input);
for ( k = 0LL; k <= 4; ++k ) // step=6
{
if ( v12[k] != ((v9[k] >> (k + 3)) | (v9[k] << (32 - (k + 3)))) )
return 0LL;
}
result = encry1(input);
break;
case 7LL:
qword_404308 = *step_change(input);
result = *&qword_404308 == 3.325947034342098e151 && encry1(input);// step=8
break;
default:
result = 0LL;
break;
}
return result;
}
大概思路就是这样,剩下逐个攻破加密块,按照word_404090里一一对应的关系排列组合,就能得出flag了。(调试太痛苦了,最后不调试还不知道,摆了)