0x01 easycrackme
昨天小伙伴发来一个逆向题目让帮忙做一下,拿到程序是个elf文件,先放到kali里面运行一下,题目提示要输入一个key
可以看到程序一共有6关,通过这6关拿到flag
先来看下第一关
size_t __fastcall check1(const char *a1)
{
size_t result; // rax@3
size_t v2; // [sp+18h] [bp-8h]@1
puts("=== 关卡 1 ===");
v2 = strlen(a1);
if ( a1[v2 - 1] == 10 )
a1[v2 - 1] = 0;
result = strlen(a1);
if ( result != 34 )
{
puts("-- 通关失败");
exit(1);
}
return result;
}
简单分析一下,第一关只是对长度进行检查,长度应该为34才对,输入一个长度为34的字符串试试。
第一关通关成功,再来看看第二关。
int __fastcall check2(const char *a1)
{
int result; // eax@1
puts("=== 关卡 2 ===");
result = strncmp(a1, "flag{", 5uLL);
if ( result )
{
puts("-- 通关失败");
exit(1);
}
return result;
}
第二关是对输入字符串的前5个字符进行校验,如果前5个字符为flag{
就会通关
来试一下
第二关通关成功,来看下第三关
__int64 __fastcall check3(const char *a1)
{
__int64 result; // rax@1
puts("=== 关卡 3 ===");
result = a1[strlen(a1) - 1];
if ( (_BYTE)result != 125 )
{
puts("-- 通关失败");
exit(1);
}
return result;
}
第三关是取最后一位字符串和125进行比较,125是}
的ascii码,所以第三关是判断最后一位字符串是不是}
来试一下
第三关通关成功,继续看下第四关
void __fastcall check4(const char *a1)
{
const char v1; // [sp+16h] [bp-2Ah]@2
const char v2; // [sp+17h] [bp-29h]@8
unsigned __int64 i; // [sp+18h] [bp-28h]@1
char *v4; // [sp+28h] [bp-18h]@1
char *s1; // [sp+38h] [bp-8h]@1
puts("=== 关卡 4 ===");
v4 = (char *)(strchr(a1, 95) - a1); // 95 == _
s1 = (char *)malloc(((unsigned __int64)(v4 - 5) >> 1) + 1);
for ( i = 0LL; i < (unsigned __int64)(v4 - 5) >> 1; ++i )
{
v1 = a1[2 * i + 5];
if ( v1 <= 47 || v1 > 57 )
{
if ( v1 > 96 && v1 <= 102 )
v1 -= 87;
}
else
{
v1 -= 48;
}
v2 = a1[2 * i + 6];
if ( v2 <= 47 || v2 > 57 )
{
if ( v2 > 96 && v2 <= 102 )
v2 -= 87;
}
else
{
v2 -= 48;
}
s1[i] = v2 | 16 * v1;
}
s1[(unsigned __int64)(v4 - 5) >> 1] = 0;
if ( strcmp(s1, "olympics") )
{
puts("-- 通关失败");
exit(1);
}
free(s1);
}
第四关稍微复杂一点,整体程序前三关是对输入字符串的格式进行判断,后三关是对输入的字符串内容进行判断,后三关分别对应着三串字符串,通过_
连接,拼接起来得到flag
第四关就是第一个字符串,首先程序会找到字符串中_
的位置,然后根据_
的位置作为循环的长度进行处理,最终经过处理的字符串和olympics
进行比较,如果相等就通关,如果不相等就输出通关失败。
对字符串处理的关键代码在for循环内,简单分析一下for循环是对字符串中第5位到第20位进行处理,s1就是要比较的字符串,分别取单数字符的ascii和双数字符的ascii乘16再进行位运算,写个脚本爆破一下
str = 'olympics'
for i in str:
for v1 in range(33, 127):
for v2 in range(33, 127):
count = v1
if count <= 47 or count > 57:
if count > 96 and count <= 102:
count -= 87
else:
count -= 48
if v2 <= 47 or v2 > 57:
if v2 > 96 and v2 <= 102:
v2 -= 87
else:
v2 -= 48
if (v2 | 16 * count) == ord(i):
print(i,ord(i),'v1=', chr(v1), v1, 'v2=', chr(v2), v2)
爆破得到符合条件的解不止一个,所以这道题目应该都多个flag,随便找一个符合条件的解运行一下看看
可以看到已经通关,继续看下第五关
void __fastcall check5(const char *a1)
{
char *v1; // rax@1
char v2; // si@3
unsigned __int64 i; // [sp+18h] [bp-28h]@1
signed __int64 v4; // [sp+20h] [bp-20h]@1
char *v5; // [sp+28h] [bp-18h]@1
char *s1; // [sp+38h] [bp-8h]@1
puts("=== 关卡 5 ===");
v1 = strchr(a1, 95);
v4 = v1 + 1 - a1;
v5 = (char *)(strchr(v1 + 1, 95) - a1);
s1 = (char *)malloc((size_t)&v5[-v4 + 1]);
for ( i = 0LL; i < (unsigned __int64)&v5[-v4]; ++i )
{
if ( i & 1 )
v2 = 33;
else
v2 = 32;
s1[i] = *(&a1[i] + v4) ^ v2;
}
s1[2 * (_QWORD)&v5[-v4]] = 0;
if ( strcmp(s1, "in") )
{
puts("-- 通关失败");
exit(1);
}
free(s1);
}
第五关比较简单,就是两个字符串分别与32和33进行异或得到in
,所以正确的字符串应该是IO
,在这里就出现问题了,不知道我的电脑什么原因,输入正确的字符串无法通关第五关
这时我以为是我做错了,要到了别人的wp,仔细看了一下没什么问题,只是第四关解的方式不一样,可是我心想第四关也和第五关没关系啊,让朋友在他的电脑上试一下,发现是可以的,后来我又换了Ubuntu试了一下发现是可以的,不知道为啥
那继续第六关
__int64 __fastcall check6(const char *a1)
{
char *v1; // rax@1
char *v2; // rax@1
char *v3; // rax@1
signed __int64 v4; // rax@7
signed __int64 v5; // rcx@8
__int64 v6; // rdi@8
__int64 v7; // rsi@8
int v8; // eax@13
int v9; // eax@16
char *s; // [sp+8h] [bp-1A8h]@1
int v12; // [sp+1Ch] [bp-194h]@12
signed __int64 v13; // [sp+20h] [bp-190h]@4
signed __int64 v14; // [sp+28h] [bp-188h]@4
unsigned __int64 v15; // [sp+30h] [bp-180h]@11
__int64 v16; // [sp+38h] [bp-178h]@11
signed __int64 v17; // [sp+40h] [bp-170h]@1
signed __int64 v18; // [sp+48h] [bp-168h]@1
char *s1; // [sp+58h] [bp-158h]@8
int v20[82]; // [sp+60h] [bp-150h]@8
__int64 v21; // [sp+1A8h] [bp-8h]@1
s = (char *)a1;
v21 = *MK_FP(__FS__, 40LL);
puts("=== 关卡 6 ===");
v1 = strchr(a1, 95);
v2 = strchr(v1 + 1, 95);
v17 = v2 + 1 - a1;
v3 = strchr(v2 + 1, 125);
v18 = v3 - a1;
if ( ((_BYTE)v3 - (_BYTE)a1 - (_BYTE)v17) & 3 )
{
puts("-- 通关失败");
exit(1);
}
v13 = 3 * ((unsigned __int64)(v18 - v17) >> 2);
v14 = v18 - v17;
while ( 1 )
{
v4 = v14--;
if ( !v4 || *(&a1[v14] + v17) != 61 )
break;
--v13;
}
s1 = (char *)malloc(v13 + 1);
v5 = 40LL;
v6 = (__int64)v20;
v7 = (__int64)">";
while ( v5 )
{
*(_QWORD *)v6 = *(_QWORD *)v7;
v7 += 8LL;
v6 += 8LL;
--v5;
}
v15 = 0LL;
v16 = 0LL;
while ( v15 < v13 )
{
v12 = (v20[*(&s[v15] + v17) - 43] << 6) | v20[*(&s[v17 + 1] + v15) - 43];
if ( *(&s[v17 + 2] + v15) == 61 )
v8 = v12 << 6;
else
v8 = (v12 << 6) | v20[*(&s[v17 + 2] + v15) - 43];
if ( *(&s[v17 + 3] + v15) == 61 )
v9 = v8 << 6;
else
v9 = (v8 << 6) | v20[*(&s[v17 + 3] + v15) - 43];
s1[v16] = v9 >> 16;
if ( *(&s[v17 + 2] + v15) != 61 )
s1[v16 + 1] = BYTE1(v9);
if ( *(&s[v17 + 3] + v15) != 61 )
s1[v16 + 2] = v9;
v15 += 4LL;
v16 += 3LL;
}
if ( strcmp(s1, "china") )
{
puts("-- 通关失败");
exit(1);
}
free(s1);
return *MK_FP(__FS__, 40LL) ^ v21;
}
第六关看到字符串china
,有了前两关的经验猜测是处理后的字符串和china
进行比较,但是看这个代码比较复杂啊,这里说实话一开始没看到,看到别人的wp说这里有左移6和等号等字符,判断是base64的解码(看来自己的知识储备还是不够,这段代码要是猜不出来是base64解码再去分析要浪费很多时间了),所以将china
进行base64编码后得到flag
题目到这已经得到了flag,应该已经结束了,但是看了其他人的wp发现第四关检查的应该是olympics
的十六进制6f6c796d70696373
,在上边用脚本爆破的时候就说过了第四关应该有很多个解,所以说这个题目出的很有问题,虽然不是一道很好的题目,但是还是记录一下。
0x02 freestyle
第二届网刃杯网络安全大赛题目
查看伪代码,主函数中发现两个功能函数
__int64 fun1()
{
char s[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Welcome to Alaska!!!");
puts("please input key: ");
fgets(s, 20, stdin);
if ( 4 * (3 * atoi(s) / 9 - 9) != 4400 )
exit(0);
puts("ok,level_1 over!nn");
return 1LL;
}
__int64 fun2()
{
char s[24]; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Welcome to Paradise Lost!!!");
puts("The code value is the smallest divisible");
puts("please input key: ");
fgets(s, 20, stdin);
if ( 2 * (atoi(s) % 56) != 98 )
exit(0);
puts("ok,level_2 over!");
return 1LL;
}
分析代码得到,只是简单的数学运算,得到fun1的值为3327,fun2的值为105
题目提示flag是md5格式,也就是3327105的md5值
flag{31a364d51abd0c8304106c16779d83b1}
0x03 Re_function
第二届网刃杯网络安全大赛题目
题目比赛的时候没做出来,拿到手是个压缩吧,有密码,一开始爆破没成功,他这个其实是压缩包后面跟着一串十六进制数据,里面是压缩包的密码,这个也是后来看别人的wp才知道的,一开始也用十六进制编辑器打开看了,也发现了后面的十六进制数据,当时没多想。
png的文件头
把数据复制出来,在线解密网站
得到一半图片,密码为3CF8
解压后得到一个exe和一个elf文件
先分析下exe运行一下是要输入一个flag,看了看伪代码,没看明白
经过分析发现一个main函数,但是没法反编译,后来看了很多的wp直说是换表的base64,但是还不是很理解。
去OD进行动调看看
搜索字符串直接断到输入的位置,并且根据OD给出的提示发现字符串长度为28位,F8继续调试
找到对输入字符串进行处理的位置,这里是每隔2位,把输入的字符串和0x37进行xor,F8继续调试
在这里可以看到处理后输入的字符串和要进行对比的28位字符串,前边判断是每隔2位和0x37进行xor,写脚本还原之前的字符串
str = [0x64, 0x71, 0x54, 0x54, 0x64, 0x78, 0x74, 0x78, 0x64, 0x41, 0x40, 0x48, 0x70, 0x6D, 0x18, 0x4A, 0x41, 0x78, 0x66, 0x72, 0x41,0x78, 0x5E, 0x4E, 0x5D, 0x52, 0x0E]
for i in range(0,len(str),2):
str[i] ^= 0x37
print(bytes(str))
# SqcTSxCxSAwHGm/JvxQrvxiNjR9
其实到这里这个exe就已经分析完了,打开elf文件,找到换的解密表进行解密就可以了
这里是更换的表,在线解密
也可以通过python脚本进行解密
import base64
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' #标准表
b = 'FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+' #新表
c = 'SqcTSxCxSAwHGm/JvxQrvxiNjR9='
trantab = c.maketrans(b, a)
print(base64.b64decode(c.translate(trantab)))
flag{we1come_t0_wrb}
0x04 眼力大考验
2022年蓝贝国际创新创业大赛“数字技术+信息安全领域赛道网络攻防大赛”
拿到程序,无壳,主函数伪代码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
const char *v3; // eax
char v5; // dl
char v6; // cl
char v7; // bl
__main();
if ( argc == 2 )
{
v3 = argv[1];
if ( *v3 == 51 && v3[1] == 54 && v3[2] == byte_403005 )// 62h
{
v5 = v3[3];
if ( v5 == byte_403008 && v3[4] == 50 && v3[5] == 52 && v3[6] == 52 && v3[7] == 50 )
{
v6 = v3[8];
if ( v6 == table )
{
v7 = v3[9];
if ( v7 == byte_403007
&& v7 == v3[10]
&& v6 == v3[11]
&& v3[12] == 52
&& v3[13] == 57
&& v3[14] == 49
&& v3[15] == 48
&& v3[16] == 53
&& v3[17] == 51
&& v3[18] == 48
&& v3[19] == 50
&& v7 == v3[20]
&& v6 == v3[21]
&& v5 == v3[22]
&& v3[23] == 56
&& v5 == v3[24]
&& v7 == v3[25]
&& v3[26] == byte_403006
&& v3[27] == byte_403009
&& v3[28] == 50
&& v3[29] == 49
&& v6 == v3[30]
&& v3[31] == 48 )
{
printf("flag{%s}n", v3);
}
}
}
}
}
else
{
printf("Usage: %s pass", *argv);
}
return 0;
}
很简单的代码逻辑,v3就是flag字符串,32位,根据主函数分析出字符串内容,但是v3[10]、v3[11]、v3[20]、v3[21]、v3[22]、v3[24]、v3[25]、v3[30]的值不知道。
分析出来字符串:36be2442ad 49105302 8 cf21 0
剩下的字符串只能去内存中找了,祭出OD,动调一下
在内存中挨个找出缺少的字符串,然后拼接成flag:36be2442adda49105302dae8edcf21a0
flag{36be2442adda49105302dae8edcf21a0}
0x05 隐秘的角落
DASCTF2022.07赋能赛
拿到程序还是先运行一下
程序是go写的,找到主函数main_main
void __cdecl main_main()
{
__int64 v0; // rdi
__int64 v1; // rsi
__int64 v2; // r8
__int64 v3; // r9
__int64 v4; // [rsp+8h] [rbp-88h]
_QWORD *v5; // [rsp+8h] [rbp-88h]
_QWORD *v6; // [rsp+50h] [rbp-40h]
_QWORD v7[2]; // [rsp+58h] [rbp-38h] BYREF
_QWORD v8[2]; // [rsp+68h] [rbp-28h] BYREF
__int64 v9[2]; // [rsp+78h] [rbp-18h] BYREF
sync___ptr_WaitGroup__Add((__int64)&main_wg, 1LL);
runtime_newobject((__int64)&unk_4B0DA0, v4);
v6 = v5;
v9[0] = (__int64)&unk_4B0DA0;
v9[1] = (__int64)&off_4E9BB0; // hi,ctfer. give me a flag:
fmt_Fprintln((__int64)&go_itab__os_File_io_Writer, os_Stdout, (__int64)v9, 1LL);
v8[0] = &unk_4AE9C0;
v8[1] = v6;
fmt_Fscanf(
v0,
v1,
(const char *)&go_itab__os_File_io_Reader,
(__int64)v8,
v2,
v3,
(__int64)&go_itab__os_File_io_Reader,
os_Stdin,
(__int64)"%s",
2LL,
(__int64)v8,
1LL,
1);
runtime_newproc(0x10u, (char)&checkflag, *v6);
v7[0] = &unk_4B0DA0;
v7[1] = &off_4E9BC0; // Who am I? where am I? what am I doing?
fmt_Fprintln((__int64)&go_itab__os_File_io_Writer, os_Stdout, (__int64)v7, 1LL);
sync___ptr_WaitGroup__Wait((__int64)&main_wg);
}
针对go中的一些函数不太清楚,但是这个程序的主函数并不复杂,通过刚才运行程序时所出现的字符串加上主函数进行分析,应该关键点就在第36行checkflag
函数中,跟进看一下
void __golang main_checkflag(__int64 a1, __int64 a2)
{
char v2; // al
__int64 v3; // [rsp+18h] [rbp-70h]
char v4; // [rsp+18h] [rbp-70h]
__int64 v5; // [rsp+20h] [rbp-68h]
__int64 v6; // [rsp+28h] [rbp-60h]
__int64 v7; // [rsp+30h] [rbp-58h]
char v8[32]; // [rsp+40h] [rbp-48h] BYREF
_QWORD v9[2]; // [rsp+60h] [rbp-28h] BYREF
_QWORD v10[2]; // [rsp+70h] [rbp-18h] BYREF
v3 = runtime_stringtoslicebyte((__int64)v8, a1, a2);
main_Myencode(v3);
if ( v5 == byte_55EA78 )
{
runtime_memequal((__int64)main_enc, v3, byte_55EA78, v3);
v2 = v4;
}
else
{
v2 = 0;
}
if ( v2 )
{
v10[0] = &unk_4B0DA0;
v10[1] = &off_4E9B90; // Yes,flag is: DASCTF{md5(Input)}
fmt_Fprintln((__int64)&go_itab__os_File_io_Writer, os_Stdout, (__int64)v10, 1LL, 1LL, v6, v7);
}
else
{
v9[0] = &unk_4B0DA0;
v9[1] = &off_4E9BA0; // No,Did you find me?
fmt_Fprintln((__int64)&go_itab__os_File_io_Writer, os_Stdout, (__int64)v9, 1LL, 1LL, v6, v7);
}
sync___ptr_WaitGroup__Add((__int64)&main_wg, -1LL);
}
跟进继续分析发现,flag的值为输入值的md5
checkflag
函数的内容也比较简单,关键点在于第14行main_Myencode
函数,这个函数当时我在做这道题目的时候就简单看了看,没太注意,导致漏掉了关键的地方,跟进main_Myencode
函数
__int64 __usercall main_Myencode@<rax>(__int64 a1, __int64 a2)
{
__int64 v3; // [rsp+18h] [rbp-50h]
__int64 v4; // [rsp+20h] [rbp-48h]
char v5[32]; // [rsp+38h] [rbp-30h] BYREF
__int64 v6; // [rsp+58h] [rbp-10h]
v6 = runtime_makeslice((__int64)&unk_4B0EE0, a2, a2);
v3 = runtime_stringtoslicebyte((__int64)v5, (__int64)main_enc_key, qword_55E898);
crypto_rc4_NewCipher(v3, v4);
crypto_rc4___ptr_Cipher__XORKeyStream(v3, v6, a2, a2, a1, a2);
return a2;
}
从里面调用的函数名可以知道这是RC4算法,那么我们就需要找到密文和key,key在main_Myencode
函数中第六行main_enc_key
,找到key的值为thisiskkk
这里有两种方式找到密文,第一种就是通过静态分析:
可以通过checkflag
函数找到main_enc
跟进unk_54df80
,但是这里的数据还不是真正的密文,是加密前的数据
真正的密文要跟进main_inti_0
函数,查看加密算法
signed __int64 __usercall main_init_0@<rax>()
{
_BYTE *v0; // rdx
signed __int64 v1; // rbx
signed __int64 result; // rax
v0 = main_enc;
v1 = *(_QWORD *)&byte_55EA78;
for ( result = 0LL; result < v1; ++result )
{
if ( (unsigned __int64)result >= *(_QWORD *)&byte_55EA78 )
runtime_panicIndex();
*((_BYTE *)main_enc + result) = v0[result] ^ 0x23;
}
return result;
}
可以看到密文是和0x23进行xor后的数据,直接写脚本xor一下得到密文。
第二种方式是直接通过动调得到密文,密文所在位置是0x54df80
可以直接用gdb,在下一条命令的位置下断点,然后跳到0x54df80
的位置查看内存信息
运行到断点处,jump一下
得到密文
这里知道了密文和key,网上找了个解密脚本改了改
key = 'thisiskkk'
data = [0xFB, 0xC6, 0xA6, 0x9D, 0xC4, 0xDB, 0x7B, 0x56, 0xB6, 0x46,
0xA6, 0xC0, 0x85, 0x64, 0x7A, 0x9A, 0x37, 0x4C, 0x10, 0x96,
0xE9, 0xA7, 0x28, 0xC4, 0xB1, 0x2D, 0xF1, 0xDE, 0x47, 0x3B,
0xB5, 0xF3, 0x2C, 0x7D, 0x67, 0x1D]
s = [0] * 256
for i in range(256) :
s[i] = i
print(s)
j = 0
for i in range(256) :
j = (j + s[i] + ord(key[i % len(key)])) % 256
print(j)
s[i], s[j] = s[j], s[i]
i = 0
j = 0
res = ""
for c in data :
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
res = res + chr(c ^ s[(s[i] + s[j]) % 256])
print(res)
#56e83694-f976-11eb-b343-faffc201c8e0
后开大佬给说了一下在线解密网站也可以解出
DASCTF{9e1963bbbb1285b993c862a5a6f12604}
E
N
D
关
于
我
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。
原文始发于微信公众号(Tide安全团队):比赛中遇到的一些简单的逆向题目