BUUCTF刷题记录[Reverse]
SimpleRev
64位无壳程序,重点函数为Decry函数。
0x01 小端序存储
*(_QWORD *)src = 0x534C43444ELL;
v9 = 0x776F646168LL;
IDA直接转字符串的结果是
*(_QWORD *)src = 'SLCDN';
v9 = 'wodah';
Intel的CPU是小端序存储,因此程序载入内存后,字符串顺序应该反过来,也就是
*(_QWORD *)src = 'NDCLS';
v9 = 'hadow';
0x02 大写字母转小写字母
v3 = 0;
getchar();
v5 = strlen(key);
for ( i = 0; i < v5; ++i )
{
if ( key[v3 % v5] > 64 && key[v3 % v5] <= 90 )
key[i] = key[v3 % v5] + 32;
++v3;
}
大写字母和小写字母的ASCII码相差32,所以该段程序是将key中的大写字母转成小写字母。
0x03 混淆算法
while ( 1 )
{
v1 = getchar();
if ( v1 == 10 )
break;
if ( v1 == 32 )
{
++v2;
}
else
{
if ( v1 <= 96 || v1 > 122 )
{
if ( v1 > 64 && v1 <= 90 )
str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
}
else
{
str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
}
if ( !(v3 % v5) )
putchar(32);
++v2;
}
}
if ( !strcmp(text, str2) )
puts("Congratulation!\n");
else
puts("Try again!\n");
如果对ASCII码熟悉的话,很容易可以看出几个判断条件的目的是确保输入的v1是26的英文字母之一,大小写皆可,因此遍历26个英文字母,依次输出符合条件的字符即可。
0x04 EXP
src = "ADSFKNDCLS"
v3 = 0
v5 = len(src)
key = ""
for i in range(v5):
if (src[i] > "@" and src[i] <= "Z"):
key += (chr(ord(src[i]) + 32))
else:
key + (src[i])
#print("%s" %key)
text = "killshadow"
flag = ""
while (v3 < 10):
for i in range(65, 91):
if (ord(text[v3]) == ((i - 39 - ord(key[v3]) + 97) % 26 + 97)):
flag += chr(i)
v3 += 1
break
else:
if (i == 90):
for j in range(97, 122):
if (ord(text[v3]) == ((j - 39 - ord(key[v3]) + 97) % 26 + 97)):
flag += chr(j)
v3 += 1
break
print("flag{%s}" %flag)
flag{KLDQCUDFZO}
0x05 反思
- 大小端存储知识点不熟悉,变量赋值和载入内存时的字符串顺序转换不够熟悉。
- ASCII码不熟悉,不清楚函数的意图。
luck_guy
EXP
f1 = "GXY{do_not_"
s = [0x7F, 0x66, 0x6F, 0x60, 0x67, 0x75, 0x63, 0x69][::-1]
f2 = ""
for i in range(8):
if (i % 2 == 1):
f2 += chr(int(s[i]) - 2)
else:
f2 += chr(int(s[i]) - 1)
flag = f1 + f2
print("%s" %flag)
print("flag%s" %flag[3:])
flag{do_not_hate_me}
[BJDCTF2020]JustRE
比较直接,需要用户点击19999次弹出的框后,打印flag。
INT_PTR __stdcall DialogFunc(HWND hWnd, UINT a2, WPARAM a3, LPARAM a4)
{
CHAR String; // [esp+0h] [ebp-64h]
if ( a2 != 272 )
{
if ( a2 != 273 )
return 0;
if ( (_WORD)a3 != 1 && (_WORD)a3 != 2 )
{
sprintf(&String, Format, ++dword_4099F0);
if ( dword_4099F0 == 19999 )
{
sprintf(&String, aBjdDD2069a4579, 19999, 0);// 打印flag
SetWindowTextA(hWnd, &String);
return 0;
}
SetWindowTextA(hWnd, &String);
return 0;
}
EndDialog(hWnd, (unsigned __int16)a3);
}
return 1;
}
直接使用ipython将19999和0填入进去即可。
flag{1999902069a45792d233ac}
刮开有奖
0x01 总体分析
32位无壳,main函数比较简单,它的参数DialogFunc比较有问题。
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
DialogBoxParamA(hInstance, (LPCSTR)0x67, 0, DialogFunc, 0);
return 0;
}
DialogBoxParamA函数(winuser.h)
从对话框模板资源创建模式对话框。 在显示对话框之前,函数会将应用程序定义的值作为WM_INITDIALOG消息的 lParam 参数传递给对话框过程。 应用程序可以使用此值初始化对话框控件。
https://learn.microsoft.com/zh-CN/windows/win32/api/winuser/nf-winuser-dialogboxparama
DialogFunc函数中重点代码如下:
if ( (_WORD)a3 == 1001 )
{
memset(&String, 0, 0xFFFFu);
GetDlgItemTextA(hDlg, 1000, &String, 0xFFFF);
if ( strlen(&String) == 8 )
{
v7 = 'Z';
v8 = 'J';
v9 = 'S';
v10 = 'E';
v11 = 'C';
v12 = 'a';
v13 = 'N';
v14 = 'H';
v15 = '3';
v16 = 'n';
v17 = 'g';
sub_4010F0((int)&v7, 0, 10);
memset(&v26, 0, 0xFFFFu);
v26 = v23;
v28 = v25;
v27 = v24;
v4 = sub_401000((int)&v26, strlen(&v26));
memset(&v26, 0, 0xFFFFu);
v27 = v21;
v26 = v20;
v28 = v22;
v5 = sub_401000((int)&v26, strlen(&v26));
if ( String == v7 + 34
&& v19 == v11
&& 4 * v20 - 141 == 3 * v9
&& v21 / 4 == 2 * (v14 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
}
return 0;
}
- String的长度为8,因此flag为8位。
- 查看内存布局后发现,v7~v17是一段连续的内存空间,因此(int)&v7可以看做一段字符串头指针。
- String、v19~v25是一段连续的内存空间,长度为8,因此该段存储的flag。
0x02 sub_4010F0
int __cdecl sub_4010F0(int a1, int a2, int a3)
{
int result; // eax
int i; // esi
int v5; // ecx
int v6; // edx
result = a3;
for ( i = a2; i <= a3; a2 = i )
{
v5 = 4 * i;
v6 = *(_DWORD *)(4 * i + a1);
if ( a2 < result && i < result )
{
do
{
if ( v6 > *(_DWORD *)(a1 + 4 * result) )
{
if ( i >= result )
break;
++i;
*(_DWORD *)(v5 + a1) = *(_DWORD *)(a1 + 4 * result);
if ( i >= result )
break;
while ( *(_DWORD *)(a1 + 4 * i) <= v6 )
{
if ( ++i >= result )
goto LABEL_13;
}
if ( i >= result )
break;
v5 = 4 * i;
*(_DWORD *)(a1 + 4 * result) = *(_DWORD *)(4 * i + a1);
}
--result;
}
while ( i < result );
}
LABEL_13:
*(_DWORD *)(a1 + 4 * result) = v6;
sub_4010F0(a1, a2, i - 1);
result = a3;
++i;
}
return result;
}
直接用C语言按照伪代码写脚本即可,此过程遇到两个问题。
*(_DWORD *)
是强制类型转化,然后在提领指针。dword是指注册表的键值,每个word为2个字节,dword双字即为4个字节。
伪代码中的
*(_DWORD *)(4 * i + a1)
其实就是a1[i]
,*(_DWORD *)(a1 + 4 * result)
是a1[result]
。如
char *tmp = "aaaaaaaa"
类似的指针定义方式,是固定类型的字符串,不能改变,通过指针对字符串进行修改会报错。如
char tmp[]="aaaaaaaa"
类似的定义方式,可以对字符串进行修改。
加密脚本如下,ZJSECaNH3ng
加密后得到的结果是3CEHJNSZagn
。
#include <stdio.h>
int sub_4010F0(char *a1, int a2, int a3)
{
int result;
int i;
int v5;
int v6;
result = a3;
for (i = a2; i <= a3; a2 = i)
{
v5 = i;
v6 = a1[i];
if (a2 < result && i < result)
{
do
{
if (v6 > a1[result])
{
if (i >= result)
break;
++i;
a1[v5] = a1[result];
if (i >= result)
break;
while(a1[i] <= v6)
{
if (++i >= result)
goto LABEL_13;
}
if (i >= result)
break;
v5 = i;
a1[result] = a1[i];
}
--result;
}
while (i < result);
}
LABEL_13:
a1[result] = v6;
sub_4010F0(a1, a2, i-1);
result = a3;
++i;
}
return result;
}
int main()
{
char v7[] = "ZJSECaNH3ng";
sub_4010F0(v7, 0, 10);
printf("%s", v7);
return 0;
}
0x03 sub_401000
推测是base64编码,理由如下:
- 该函数两个参数分别是字符串头指针和字符串长度,函数开头定义了v4,赋值为字符串长度的4/3倍。
v2 = a2 / 3;
v3 = 0;
if ( a2 % 3 > 0 )
++v2;
v4 = 4 * v2 + 1;
- 函数使用到了字符串byte_407830,该字符串存储了base64映射表中的元素。
.rdata:00407830 ; char byte_407830[]
.rdata:00407830 byte_407830 db 41h ; DATA XREF: sub_401000+C0↑r
.rdata:00407831 aBcdefghijklmno db 'BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',0
0x04 EXP
memset(&v26, 0, 0xFFFFu);
v26 = v23;
v28 = v25;
v27 = v24;
v4 = sub_401000((int)&v26, strlen(&v26));
memset(&v26, 0, 0xFFFFu);
v27 = v21;
v26 = v20;
v28 = v22;
v5 = sub_401000((int)&v26, strlen(&v26));
if ( String == v7 + 34
&& v19 == v11
&& 4 * v20 - 141 == 3 * v9
&& v21 / 4 == 2 * (v14 / 9)
&& !strcmp(v4, "ak1w")
&& !strcmp(v5, "V1Ax") )
{
MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}
前文提到过,flag[0]、flag[1]–flag[7]分别对应String、v19–v25。
String,即flag[0] = v7 + 34。
v9,即flag[1] = v11。
sub_4010F0
函数加密后,v7~v17一段内存中存储的字符串是3CEHJNSZagn
。
v4是flag[5]、flag[6]、flag[7]组成的字符串base64编码后的结果。
v5是flag[2]、flag[3]、flag[4]组成的字符串base64编码后的结果。
import base64
v7 = '3'
v11 = 'J'
v4 = "ak1w"
v5 = 'V1Ax'
v5 = base64.b64decode(v5)
v4 = base64.b64decode(v4)
flag = ""
flag += chr(ord(v7) + 34)
flag += v11
flag += str(v5, encoding = "utf-8")
flag += str(v4, encoding = "utf-8")
print("flag{%s}" %flag)
flag{UJWP1jMp}
[ACTF新生赛2020]easyre
0x01 分析
32位,upx壳。
v4~v15一共12位,for循环也是12位,可推测flag有12位。
从if语句中可以看出flag的一部分是ACTF{}
,内容是v22,v23,v24。
从栈中查看,上述三个变量都是int型变量,分别可以存储4个字符型数据,也验证了flag有12位的推测。
从v4到v15分别与__data_start__
比较,索引值+1即为flag中对应位置的字符。
int __cdecl main(int argc, const char **argv, const char **envp)
{
/* Define variables */
__main();
v4 = 42;
v5 = 70;
v6 = 39;
v7 = 34;
v8 = 78;
v9 = 44;
v10 = 34;
v11 = 40;
v12 = 73;
v13 = 63;
v14 = 43;
v15 = 64;
printf("Please input:");
scanf("%s", v19);
if ( v19[0] != 'A' || v19[1] != 'C' || v19[2] != 'T' || v20 != 'F' || v21 != '{' || v25 != '}' )
return 0;
v16 = v22;
v17 = v23;
v18 = v24;
for ( i = 0; i <= 11; ++i )
{
if ( *(&v4 + i) != _data_start__[*((char *)&v16 + i) - 1] )
return 0;
}
printf("You are correct!");
return 0;
}
0x02 EXP
.data:00402000 public __data_start__
.data:00402000 ; char _data_start__[]
.data:00402000 __data_start__ db 7Eh ; DATA XREF: _main+EC↑r
.data:00402001 aZyxwvutsrqponm db '}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>='
.data:00402001 db '<;:9876543210/.-,+*)(',27h,'&%$# !"',0
.data:00402060 align 40h
不要忘记
__data_start__
的第一个字符,也就是地址0x00402000的db 7Eh
。不要忘记
27h
,对应的是转义符\
。为防止字符被转义,Python可以使用
r/R
来表明非转义的原始字符串。
data_start = r'~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(' + chr(0x27) + r'&%$# !"'
flag = ''
v4 = [42,70,39,34,78,44,34,40,73,63,43,64]
for i in v4:
flag += chr(data_start.find(chr(i)) + 1)
print("flag{%s}" %flag)
flag{U9X_1S_W6@T?}
[ACTF新生赛2020]rome
32位无壳,重点代码很短,其实就是一段凯撒加密
result = [81, 115, 119, 51, 115, 106, 95, 108, 122, 52, 95, 85, 106, 119, 64, 108]
flag = ""
for re in result:
if re > 64 and re <= 90:
for i in range(65, 91):
if ((i - 51) % 26 + 65) == re:
flag += chr(i)
break
elif re > 96 and re <= 122:
for j in range(97, 123):
if ((j - 79) % 26 + 97) == re:
flag += chr(j)
break
else:
flag += chr(re)
print("flag{%s}" %flag)
flag{Cae3ar_th4_Gre@t}