【CTF刷題之旅】XCTF嘉年華體驗賽逆向題re2的writeup
這道題採用動靜結合的方法嘗試了一下
動態除錯的時候想加快進度 跳過sleep() 遇到那兩個jle跳轉 直接修改SF標誌位為0來修改執行流程 可以看到 左側箭頭會變成虛線
對了 第三個jle在sub_400C9A()中
main()函式如下
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { unsigned int v3; // eax int v4; // eax int v5; // eax void *my_mima; // [rsp+8h] [rbp-18h] unsigned int *my_zhanghao; // [rsp+10h] [rbp-10h] signed int j; // [rsp+18h] [rbp-8h] signed int i; // [rsp+1Ch] [rbp-4h] my_zhanghao = malloc(0x3E8uLL); #申請分配記憶體空間給我們的輸入 my_mima = malloc(0x3E8uLL); v3 = time(0LL); srand(v3); #srand()用來設定rand()產生隨機數時的隨機數種子,引數seed必須是整數,通常可以用time(0)的返回值作為seed.如果每次seed都設定相同的值,rand()產生的隨機數值每次都一樣 puts(a31m); #紅條出現 puts(a33m); #黃條出現 下面以此類推 puts(a32m); puts(a36m); puts(a34m); puts(a35m); puts(a34m_0); puts(a36m); puts(a32m_0); puts(a33m_0); puts(a31m_0); puts("\x1B[0mWelcome to Catalyst systems"); printf("Loading", a2); fflush(stdout); for ( i = 0; i <= 29; ++i ) #這裡就是等待 輸出30個"." { v4 = rand(); sleep(v4 % (3 * i + 1)); putchar('.'); fflush(stdout); } putchar(10); printf("Username: "); __isoc99_scanf("%s", my_zhanghao); #輸入賬號 printf("Password: ", my_zhanghao); __isoc99_scanf("%s", my_mima); #輸入密碼 printf("Logging in", my_mima); fflush(stdout); for ( j = 0; j <= 29; ++j ) #又是等待30個"." { v5 = rand(); sleep(v5 % (j + 1)); putchar('.'); fflush(stdout); } putchar(10); #下面這五個函式是得到我們賬號密碼的關鍵 sub_400C9A(my_zhanghao); sub_400CDD(my_zhanghao); sub_4008F7(my_zhanghao); sub_400977(my_zhanghao, my_mima); sub_400876(my_zhanghao, my_mima); return 0LL; }
那我們先看下sub_400C9A()這個函式是來幹嘛的 分析可知關鍵在於sub_400C41()這個函式
__int64 __fastcall sub_400C9A(__int64 a1) { int my_zhanghao; // [rsp+1Ch] [rbp-4h] for ( my_zhanghao = 0; my_zhanghao <= 49 && *(my_zhanghao + a1); ++my_zhanghao ) ; #這裡for迴圈後來看了師傅的wp才知道是用來限定輸入的賬號如果字元數大於50位元組 就按50位元組進行計算 作為引數傳入sub_400C41() return sub_400C41(my_zhanghao); }
那我們來分析吧
__int64 __fastcall sub_400C41(int i) { __int64 result; // rax if ( 4 * (i >> 2) != i || 4 * (i >> 4) == i >> 2 || (result = (i >> 3), !result) || i >> 4 ) #這個條件看得我發懵 我用python解的 { puts("invalid username or password"); exit(0); } return result; }
for i in range(1,20):
if 4 * (i >> 2)== i and 4 * (i >> 4) != i >> 2:
if i << 4:
if i << 3 != 0:
print i
#4,8,12
#這裡我解出來三個值 師傅wp是沒有4的 還好這個不影響結果 這個等補坑吧
#20181026來補坑 4是不對的
#i必須是4的整數倍,但必須大於4,不能是16的整數倍,i必須小於16
#所以只有8,12符合
#具體可以看這個帖子:https://bbs.pediy.com/thread-247423-1.htm#1566256
下面sub_400CDD()這個函式用來計算正確的賬號 也就是正確的使用者名稱
signed __int64 __fastcall sub_400CDD(unsigned int *my_zhanghao)
{
signed __int64 result; // rax
__int64 v2; // [rsp+10h] [rbp-20h]
__int64 v3; // [rsp+18h] [rbp-18h]
__int64 v4; // [rsp+20h] [rbp-10h]
v4 = *my_zhanghao;
v3 = my_zhanghao[1];
v2 = my_zhanghao[2];
if ( v4 - v3 + v2 != 1550207830
|| v3 + 3 * (v2 + v4) != 12465522610LL
|| (result = 3651346623716053780LL, v2 * v3 != 3651346623716053780LL) )
{
puts("invalid username or password");
exit(0);
}
return result;
}
這裡求解用z3再好不過啦 求出十進位制
from z3 import *
v4 = Int('v4')
v3 = Int('v3')
v2 = Int('v2')
s = Solver()
s.add(v4 - v3 + v2 == 1550207830)
s.add(v3 + 3 * (v2 + v4) == 12465522610)
s.add(v2 * v3 == 3651346623716053780)
if s.check() != sat:
print 'unsat'
else:
m = s.model()
print m
#s2=[1868915551,1953724780,1635017059]
轉成十六進位制
s2=[1868915551,1953724780,1635017059]
for i in s2:
print hex(i)
#s3 = [0x61746163,0x7473796c,0x6f65635f]
因為是小端序 手動調整下順序 最後得到賬號catalyst_ceo
import binascii
from libnum import n2s,s2n
print n2s(0x63617461) + n2s(0x6c797374) + n2s(0x5f63656f)
#catalyst_ceo
這時你就會驚奇的發現前面那個求賬號長度的sub_400C9A()這個函式根本用不上嘛。。
對下面的sub_4008F7()頓時也沒啥興趣了 這個是用來限定字元範圍 對輸入的使用者名稱進行遍歷 每個字元十六進位制範圍是
(0x60-0x7A) 並且有一個字元必須是"_"
__int64 __fastcall sub_4008F7(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = *(i + a1);
if ( !result )
break;
if ( (*(i + a1) <= 0x60 || *(i + a1) > 'z') && *(i + a1) != '_' )
{
puts("invalid username or password");
exit(0);
}
}
return result;
}
直接看sub_400977()吧
__int64 __fastcall sub_400977(_DWORD *my_zhanghao, _DWORD *my_mima)
{
int v2; // ebx
int v3; // ebx
int v4; // ebx
int v5; // ebx
int v6; // ebx
int v7; // ebx
int v8; // ebx
int v9; // ebx
int v10; // ebx
int v11; // ebx
unsigned int v12; // ebx
__int64 result; // rax
int i; // [rsp+2Ch] [rbp-14h]
for ( i = 0; *(my_mima + i); ++i ) #這個for迴圈用來限定密碼字元範圍
{
if ( (*(my_mima + i) <= 96 || *(my_mima + i) > 122)
&& (*(my_mima + i) <= 64 || *(my_mima + i) > 90)
&& (*(my_mima + i) <= 47 || *(my_mima + i) > 57) )
{
puts("invalid username or password");
exit(0);
}
}
srand(my_zhanghao[1] + *my_zhanghao + my_zhanghao[2]); #srand()用來設定rand()產生隨機數時的隨機數種子
v2 = *my_mima;
if ( v2 - rand() != 1441465642 )
{
puts("invalid username or password");
exit(0);
}
v3 = my_mima[1];
if ( v3 - rand() != 251096121 )
{
puts("invalid username or password");
exit(0);
}
v4 = my_mima[2];
if ( v4 - rand() != -870437532 )
{
puts("invalid username or password");
exit(0);
}
v5 = my_mima[3];
if ( v5 - rand() != -944322827 )
{
puts("invalid username or password");
exit(0);
}
v6 = my_mima[4];
if ( v6 - rand() != 647240698 )
{
puts("invalid username or password");
exit(0);
}
v7 = my_mima[5];
if ( v7 - rand() != 638382323 )
{
puts("invalid username or password");
exit(0);
}
v8 = my_mima[6];
if ( v8 - rand() != 282381039 )
{
puts("invalid username or password");
exit(0);
}
v9 = my_mima[7];
if ( v9 - rand() != -966334428 )
{
puts("invalid username or password");
exit(0);
}
v10 = my_mima[8];
if ( v10 - rand() != -58112612 )
{
puts("invalid username or password");
exit(0);
}
v11 = my_mima[9];
v12 = v11 - rand();
result = v12;
if ( v12 != 605226810 )
{
puts("invalid username or password");
exit(0);
}
return result;
}
和前面說的一樣
srand()用來設定rand()產生隨機數時的隨機數種子,引數seed必須是整數,通常可以用time(0)的返回值作為seed
但是如果每次seed都設定相同的值,rand()產生的隨機數值每次也都一樣
那麼我們就可以直接在每個if語句那裡下上斷點 動態除錯即可獲取rand()產生的值啦
F8步過call _rand 此時eax = 0x684749
也就是rand() = 0x684749
v2 = 0x00684749 + 0x55EB052A
接著得到v3 v4......
v2 = 00684749 + 55EB052A
v3 = 673CE537 + 0EF76C39
v4 = 7B4505E7 + CC1E2D64
v5 = 70A0B262 + C7B6C6F5
v6 = 33D5253C + 26941BFA
v7 = 515A7675 + 260CF0F3
v8 = 596D7D5D + 10D4CAEF
v9 = 7CD29049 + C666E824
v10 = 59E72DB6 + FC89459C
v11 = 4654600D + 2413073A
python轉成字串就是密碼
要注意進的高位1直接捨棄 還有注意小端序
import binascii
from libnum import n2s,s2n
v2 = 0x00684749 + 0x55EB052A
v3 = 0x673CE537 + 0x0EF76C39
v4 = 0x7B4505E7 + 0xCC1E2D64
v5 = 0x70A0B262 + 0xC7B6C6F5
v6 = 0x33D5253C + 0x26941BFA
v7 = 0x515A7675 + 0x260CF0F3
v8 = 0x596D7D5D + 0x10D4CAEF
v9 = 0x7CD29049 + 0xC666E824
v10 = 0x59E72DB6 + 0xFC89459C
v11 = 0x4654600D + 0x2413073A
mima = [v2,v3,v4,v5,v6,v7,v8,v9,v10,v11]
for i in mima:
print hex(i)
'''0x56534c73
0x76345170
0x14763334b
0x138577957
0x5a694136
0x77676768
0x6a42484c
0x14339786d
0x156707352
0x6a676747'''
print n2s(0x734c5356)+n2s(0x70513476)+n2s(0x4b336347)+n2s(0x57795738)+n2s(0x3641695a)+n2s(0x68676777)+n2s(0x4c48426a)+n2s(0x6d783943)+n2s(0x52737056)+n2s(0x4767676a)
#sLSVpQ4vK3cGWyW86AiZhggwLHBjmx9CRspVGggj
最後那個函式改天再研究 直接輸入正確的賬號密碼 即可得到flag
ALEXCTF{1_t41d_y0u_y0u_ar3__gr34t__reverser__s33}
參考連結:https://blog.csdn.net/weixin_43090100/article/details/82180752