CTF逆向入門:迷宮題學習記錄(持續更新)
**
目錄
- **CTF逆向入門:迷宮題學習記錄(持續更新)**
- (前言)
- 一、 逆向迷宮題概述
- 二、 具體題目分析
- 1. 2019華南師大CTF新生賽maze
- 2.2020華南師大CTF新生賽maze
- 3.攻防世界新手區:NJUPT CTF 2017 maze
- 4.BUUCTF:不一樣的flag
- 三,總結
(前言)
本文將不斷更新以具體題目為載體,分析CTF逆向迷宮題的解題思路與方法,由于本人是新手,學識有限,若有錯漏或不當之處,歡迎指出,
提示:以下是本篇文章正文內容,下面案例可供參考
一、 逆向迷宮題概述
迷宮題當然是走迷宮最后得到flag啦!
主要步驟:
1.分析程式的主函式,找到迷宮
2分析程式,確定走迷宮的方法(上下左右…怎么走)
3寫程式或手走迷宮,得到flag
二、 具體題目分析
1. 2019華南師大CTF新生賽maze
題目地址:https://github.com/scnu-sloth/hsctf-2019-freshmen
在hex-view里看到了迷宮,下一步是找走迷宮的規則和方法
點開check函式看到
bool __cdecl check(char *flag)
{
char *v1; // eax
int v2; // eax
char *cur; // [esp+Ch] [ebp-4h]170個字符,其中有一個是空字符'0'
cur = &maze[14];//這是一個含14*12的迷宮
while ( *flag && *cur != '*' )//由此可見'*'是迷宮的墻
{
v1 = flag++;
v2 = *v1;
if ( v2 == 'd' )
{
++cur;//d為向右走一步
}
else if ( v2 > 'd' )
{
if ( v2 == 's' )
{
cur += 13;//s為向右13步,在本二維陣列中為向左下方一步或向右13步
}
else
{
if ( v2 != 'w' )
return 0;
cur -= 13;//w為向右上方一步或向左13步
}
}
else
{
if ( v2 != 'a' )
return 0;
--cur;//a為向左一步
}
}
return *cur == '#';//'#'是迷宮終點
}
由題意,這是個14*12的迷宮,@是起點,#是終點,flag走迷宮的路徑
d:向前一步,a:向后一步,s:向前13步,w:向后13步
由main函式可見,flag長度即走迷宮步數一共為24步
手畫迷宮如下,X表示不可走,O表示可走
寫代碼生成迷宮
int main()
{
char s[]="**************@************-************-***-**-*****--*****-*****-***#**-*****--**----******-*****-******-****--******---**-*******-*-----******-------*****************";
int i,j;
for(i=0;i<12;i++)
{
for(j=0;j<14;j++)
{
if(s[i*14+j]=='-')
cout<<"0"<<' ';
else if(s[i*14+j]=='*')
cout<<"X"<<' ';
else
cout<<s[i*14+j]<<' ';
}
cout<<endl;
}
return 0;
}

太菜了不會寫程式走迷宮,只能手走 qvq.
flag{sssssdsssddsdddwwdwwaaaw}
2.2020華南師大CTF新生賽maze
題目地址https://github.com/scnu-sloth/hsctf-2020-freshmen
拖進IDA反編譯,查看main函式
flag長度限制為59,應該是迷宮的最短路徑
并且發現了一個CreateMap函式,應該里面有關于迷宮的資訊,點開來看
int CreateMap()
{
int result; // eax
int v1; // [esp+4h] [ebp-Ch]
unsigned __int16 v2; // [esp+Ah] [ebp-6h]
int i; // [esp+Ch] [ebp-4h]
for ( i = 0; i <= 15; ++i )
{
v2 = num[i];//num陣列有16個數
v1 = 1;//v2是無符號整型
do
{
map[16 * i + 16 - v1] = v2 & 1;//map是二維陣列,有256個數,
v2 >>= 1;//相當于v2=v2/2^1
result = v1++;
}//這是轉二進制
while ( result && v1 <= 16 );
}
return result;
}
這道迷宮題與眾不同,要我們根據函式生成迷宮
該程式的意思是把num中的每個數轉為無符號整型再轉為二進制儲存再map陣列中,原本16個數字變成16*16=256個數字,根據題意該迷宮是個16X16迷宮,因此我們想要得到迷宮,必須知道回圈中每個v2的值,我想到通過動態除錯一一記錄下16個v2的值(該方法要有耐心,肯定有更好的方法)
接下來進行windows本地動態除錯
設定斷點
在locals里查看變數的值
寫python腳本輸出迷宮
a=[0xffff,0x83F7,0xBBF7,0xBB17,0xBB57,0xB857,0xBF57,0xBF17,0xBFB7,0xBFB7,0x8611,0xF7B5,0xF7B5,0x7B4,0xFF87,0xFFFF]
print(a)
for i in a:
i=bin(i)
print(i)
好了,迷宮到手了,顯然入口是map[13][0],出口是map[13][15],0可走,1不可走,讓我們回到IDA,接下來要看看怎么走
點開check函式
int __cdecl check(char *Str)
{
int v2; // eax
char Destination[4]; // [esp+1Ch] [ebp-7Ch] BYREF
char v4[96]; // [esp+20h] [ebp-78h] BYREF
size_t v5; // [esp+80h] [ebp-18h]
int v6; // [esp+84h] [ebp-14h]
int v7; // [esp+88h] [ebp-10h]
int i; // [esp+8Ch] [ebp-Ch]
v5 = strlen(Str);
*(_DWORD *)Destination = 0;
memset(v4, 0, sizeof(v4));
strcpy(Destination, Str);
v4[1] = 0;
if ( strcmp(Destination, "flag{") || Str[v5 - 1] != '}' )
return 0;
v7 = 13;
v6 = 0;//表示起點為map[13][0]
for ( i = 5; i < (int)(v5 - 1); ++i )
{
v2 = Str[i];
if ( v2 == 'l' )
{
++v6;//向右走
}
else
{
if ( v2 > 'l' )
return 0;
switch ( v2 )
{
case 'k':
--v7;//向上走
break;
case 'h':
--v6;//向左走
break;
case 'j':
++v7;//向下走
break;
default:
return 0;
}
}
if ( map[16 * v7 + v6] )//v6表示左右,v7表示上下
return 0;
}
return 1;
}
走迷宮路徑如下
Get the flag! flag{llllkkkhhhkkkkkkkkklllljjjjllljjljjjjjjjlllkkkklljjjl}
3.攻防世界新手區:NJUPT CTF 2017 maze
題目地址:https://adworld.xctf.org.cn/task/answer?type=reverse&number=4&grade=0&id=5084&page=1
拖進IDA發現迷宮所在


分析main函式
int v9; // [rsp+0h] [rbp-28h] BYREF
int v10[9]; // [rsp+4h] [rbp-24h] BYREF
v10[0] = 0;
v9 = 0;//迷宮從最左上角開始走
puts("Input flag:");
scanf("%s", &s1);
if ( strlen(&s1) != 24 || strncmp(&s1, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )//如果是nctf{開頭且長度為24就進入下一步
{
LABEL_22://要盯緊它,判斷什么情況下會失敗
puts("Wrong flag!");
exit(-1);
}
v3 = 5LL;
if ( strlen(&s1) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&s1 + v3);
v5 = 0;
if ( v4 > 'N' )
{
if ( (unsigned __int8)v4 == 'O' )
{
v6 = sub_400650(v10);//這里對v10進行了操作
goto LABEL_14;
}
if ( (unsigned __int8)v4 == 'o' )
{
v6 = sub_400660(v10);//這里對v10進行了操作
goto LABEL_14;
}
}
else
{
if ( (unsigned __int8)v4 == '.' )
{
v6 = sub_400670(&v9);//這里對&v9進行了操作
goto LABEL_14;
}
if ( (unsigned __int8)v4 == '0' )
{
v6 = sub_400680(&v9);//這里對&v9進行了操作
LABEL_14:
v5 = v6;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)sub_400690((__int64)asc_601060, v10[0], v9) )//這里要關注障礙檢測,還有非常重要的東西
goto LABEL_22;
if ( ++v3 >= strlen(&s1) - 1 )
{
if ( v5 )
break;
LABEL_20:
v7 = "Wrong flag!";
goto LABEL_21;
}
}
}
if ( asc_601060[8 * v9 + v10[0]] != '#' )//終點為#
goto LABEL_20;
v7 = "Congratulations!";
LABEL_21:
puts(v7);
return 0LL;
}
由偽代碼可見,這是個8*8的迷宮,有四個判斷條件,分別進入了四個函式,
‘O’,‘o’,‘0’,’.'這四個字符分別控制不同方向
O左,o右,0下,.上(稍后會說怎么判斷)
四個方向函式如下
bool __fastcall sub_400650(_DWORD *a1)//O
{
int v1; // eax
v1 = (*a1)--;//向左一步
return v1 > 0;//防止越界
}
bool __fastcall sub_400660(int *a1)//o
{
int v1; // eax
v1 = *a1 + 1;//向右一步
*a1 = v1;
return v1 < 8;
}
bool __fastcall sub_400670(_DWORD *a1)//.
{
int v1; // eax
v1 = (*a1)--;//向上一步
return v1 > 0;
}
bool __fastcall sub_400680(int *a1)//0
{
int v1; // eax
v1 = *a1 + 1;
*a1 = v1;//向下一步
return v1 < 8;
}
可是怎么分辨v9和v10各自控制的是上下還是左右呢?
點開sub_400690函式,看到
__int64 __fastcall sub_400690(__int64 a1, int a2, int a3)
{//a1是陣列的首地址,a2是v10,a3是v9
__int64 result; // rax
result = *(unsigned __int8 *)(a1 + a2 + 8LL * a3);//a3乘以8表明v10在二維陣列中表示上下
LOBYTE(result) = (_DWORD)result == ' ' || (_DWORD)result == '#';
return result;
}
從這里我們得知v10表示行,就是控制上下,v9表示列,就是控制左右,
而且只有‘ ’ 和#可以走,否則回傳return 0,’*'是迷宮的墻,
寫代碼生成迷宮,0可走,X為墻,
#include<iostream>
using namespace std;
int main()
{
char s[]=" ******* * **** * **** * *** *# *** *** *** *********";
int i,j;
for(i=0;i<8;i++)
{
for(j=0;j<8;j++)
{
if(s[i*8+j]==' ')
cout<<"0"<<' ';
else if(s[i*8+j]=='*')
cout<<"X"<<' ';
else
cout<<"#"<<' ';
}
cout<<endl;
}
return 0;
}
走迷宮得到flag為 nctf{o0oo00O000oooo…OO}
4.BUUCTF:不一樣的flag
題目地址:https://buuoj.cn/challenges
拖進IDA查看main函式
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3[29]; // [esp+17h] [ebp-35h] BYREF
int v4; // [esp+34h] [ebp-18h]
int v5; // [esp+38h] [ebp-14h] BYREF
int i; // [esp+3Ch] [ebp-10h]
_BYTE v7[12]; // [esp+40h] [ebp-Ch] BYREF
__main();
v4 = 0;
strcpy(v3, "*11110100001010000101111#");//這就是迷宮
while ( 1 )
{
puts("you can choose one action to execute");
puts("1 up");
puts("2 down");
puts("3 left");
printf("4 right\n:");//控制方向
scanf("%d", &v5);
if ( v5 == 2 )
{
++*(_DWORD *)&v3[25];
}
else if ( v5 > 2 )
{
if ( v5 == 3 )
{
--v4;
}
else
{
if ( v5 != 4 )
LABEL_13:
exit(1);
++v4;
}
}
else
{
if ( v5 != 1 )
goto LABEL_13;
--*(_DWORD *)&v3[25];
}
for ( i = 0; i <= 1; ++i )
{
if ( *(int *)&v3[4 * i + 25] < 0 || *(int *)&v3[4 * i + 25] > 4 )
exit(1);
}
if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == '1' )
exit(1);//是個5*5的迷宮且1為墻
if ( v7[5 * *(_DWORD *)&v3[25] - 41 + v4] == '#' )
{//‘#’是迷宮終點
puts("\nok, the order you enter is the flag!");
exit(0);
}
}
}
一目了然,顯然是一道迷宮題,
1,2,3,4分別控制上下左右
using namespace std;
int main()
{
char s[]="*11110100001010000101111#";
int i,j;
for(i=0;i<5;i++)
{
for(j=0;j<5;j++)
{
if(s[i*5+j]=='0')
cout<<"0"<<' ';
else if(s[i*5+j]=='1')
cout<<"X"<<' ';
else
cout<<s[i*5+j]<<' ';
}
cout<<endl;
}
return 0;
}
走迷宮
Get the flag!: flag{222441144222}
三,總結
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/256689.html
標籤:python
上一篇:Python中可迭代物件和迭代器
下一篇:java中的io流
