-
-
[原创]Buuctf-reverse-crackMe
-
2022-4-6 11:32 11704
-
crackMe
ps:学习了一下大家写文章的方式,以后就都这么写了,之前的第一篇就懒得改了,参考了网上师傅的wp
题目链接:https://buuoj.cn/challenges#crackMe
题目分析
通过题目内容可以了解到,用户名是welcomebeijing,密码MD5的小写哈希就是我们的flag。点开程序,可以看到关键字符串,大致的思路就是通过这个字符串可以定位到关键函数,再进行进一步分析。
函数分析
先用查壳软件看一下程序情况
是一个无壳的32位PE文件,用IDA32打开,定位到主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | int wmain() { FILE * v0; / / eax FILE * v1; / / eax char v3; / / [esp + 3h ] [ebp - 405h ] char v4; / / [esp + 4h ] [ebp - 404h ] BYREF char v5[ 255 ]; / / [esp + 5h ] [ebp - 403h ] BYREF char Format ; / / [esp + 104h ] [ebp - 304h ] BYREF char v7[ 255 ]; / / [esp + 105h ] [ebp - 303h ] BYREF char v8; / / [esp + 204h ] [ebp - 204h ] BYREF char v9[ 255 ]; / / [esp + 205h ] [ebp - 203h ] BYREF char v10; / / [esp + 304h ] [ebp - 104h ] BYREF char v11[ 255 ]; / / [esp + 305h ] [ebp - 103h ] BYREF printf( "Come one! Crack Me~~~\n" ); v10 = 0 ; memset(v11, 0 , sizeof(v11)); v8 = 0 ; memset(v9, 0 , sizeof(v9)); while ( 1 ) { do { do { printf( "user(6-16 letters or numbers):" ); scanf( "%s" , &v10); #接收user v0 = ( FILE * )sub_4024BE(); fflush(v0); } while ( !(unsigned __int8)sub_401000(&v10) ); printf( "password(6-16 letters or numbers):" ); scanf( "%s" , &v8); #接收password v1 = ( FILE * )sub_4024BE(); fflush(v1); #这几个并没有对接收的字符串做什么操作,就不管了 } while ( !(unsigned __int8)sub_401000(&v8) ); #检查是不是字母或者数字 sub_401090(&v10); Format = 0 ; memset(v7, 0 , sizeof(v7)); v4 = 0 ; memset(v5, 0 , sizeof(v5)); v3 = (( int (__cdecl * )(char * , char * ))loc_4011A0)(& Format , &v4); #这个应该是判断后输出再试一次的字符串 if ( (unsigned __int8)sub_401830(&v10, &v8) ) #关键的验证函数 { if ( v3 ) break ; } printf(&v4); } printf(& Format ); return 0 ; } |
对于一些不是很重要的函数的作用我都直接在上面的函数注明了,比较重要的,我会在下文着重分析。
sub_401090函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | _BYTE * __cdecl sub_401090(_BYTE * a1) { _BYTE * result; / / eax int v2; / / [esp + Ch] [ebp - 18h ] int v3; / / [esp + 10h ] [ebp - 14h ] _BYTE * v4; / / [esp + 14h ] [ebp - 10h ] int i; / / [esp + 18h ] [ebp - Ch] char v7; / / [esp + 20h ] [ebp - 4h ] char v8; / / [esp + 22h ] [ebp - 2h ] unsigned __int8 v9; / / [esp + 23h ] [ebp - 1h ] for ( i = 0 ; i < 256 ; + + i ) byte_416050[i] = i; v2 = 0 ; v9 = 0 ; v3 = 0 ; result = a1; v4 = a1; do LOBYTE(result) = * v4; while ( * v4 + + ); while ( v2 < 256 ) { v8 = byte_416050[v2]; v9 + = v8 + a1[v3]; v7 = byte_416050[v9]; + + v3; byte_416050[v9] = v8; byte_416050[v2] = v7; result = (_BYTE * )v3; if ( v3 > = v4 - (a1 + 1 ) ) v3 = 0 ; + + v2; } return result; } |
这个函数就是对user进行了一系列操作,最后把值赋给了byte_416050这个数组,我看着很像RC4加密,后面可以直接动调出来
sub_401830关键函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | bool __cdecl sub_401830( int a1, const char * a2) { int v3; / / [esp + 18h ] [ebp - 22Ch ] int v4; / / [esp + 1Ch ] [ebp - 228h ] int v5; / / [esp + 28h ] [ebp - 21Ch ] unsigned int v6; / / [esp + 30h ] [ebp - 214h ] char v7; / / [esp + 36h ] [ebp - 20Eh ] char v8; / / [esp + 37h ] [ebp - 20Dh ] char v9; / / [esp + 38h ] [ebp - 20Ch ] unsigned __int8 v10; / / [esp + 39h ] [ebp - 20Bh ] unsigned __int8 v11; / / [esp + 3Ah ] [ebp - 20Ah ] char v12; / / [esp + 3Bh ] [ebp - 209h ] int v13; / / [esp + 3Ch ] [ebp - 208h ] BYREF char v14; / / [esp + 40h ] [ebp - 204h ] BYREF char v15[ 255 ]; / / [esp + 41h ] [ebp - 203h ] BYREF char v16; / / [esp + 140h ] [ebp - 104h ] BYREF char v17[ 255 ]; / / [esp + 141h ] [ebp - 103h ] BYREF v4 = 0 ; v5 = 0 ; v11 = 0 ; v10 = 0 ; v16 = 0 ; memset(v17, 0 , sizeof(v17)); v14 = 0 ; memset(v15, 0 , sizeof(v15)); v9 = 0 ; v6 = 0 ; v3 = 0 ; while ( v6 < strlen(a2) ) { if ( isdigit(a2[v6]) ) { v8 = a2[v6] - 48 ; #这里将输入的字符0-9转化成十进制的0-9,就是转换一下类型 } else if ( isxdigit(a2[v6]) ) { if ( * ((_DWORD * )NtCurrentPeb() - >SubSystemData + 3 ) ! = 2 ) #反调试的 a2[v6] = 34 ; v8 = (a2[v6] | 0x20 ) - 87 ; #意思是减87加32,也就是将字母,例如a转化成16进制数a(就是10) } else { v8 = ((a2[v6] | 0x20 ) - 97 ) % 6 + 10 ; #将其他的字符转化到十六进制数a-f之间(10-16) } __rdtsc(); __rdtsc(); v9 = v8 + 16 * v9; if ( !(( int )(v6 + 1 ) % 2 ) ) #将输入的密码两位一组,存放在v14数组里面 { * (&v14 + v3 + + ) = v9; v9 = 0 ; } + + v6; } while ( v5 < 8 ) { v10 + = byte_416050[ + + v11]; v12 = byte_416050[v11]; v7 = byte_416050[v10]; byte_416050[v10] = v12; byte_416050[v11] = v7; if ( (( int )NtCurrentPeb() - >UnicodeCaseTableData & 0x70 ) ! = 0 ) #反调试 v12 = v10 + v11; * (&v16 + v5) = byte_416050[(unsigned __int8)(v7 + v12)] ^ * (&v14 + v4); #关键的异或操作 if ( (unsigned __int8) * (_DWORD * )&NtCurrentPeb() - >BeingDebugged ) #反调试 { v10 = - 83 ; v11 = 43 ; } sub_401710(&v16, a1, v5 + + ); v4 = v5; if ( v5 > = (unsigned int )(&v14 + strlen(&v14) + 1 - v15) ) v4 = 0 ; } v13 = 0 ; sub_401470(&v16, &v13); return v13 = = 0xAB94 ; } |
一下子就能看见有好几个反调试的语句,第一个while循环里的意思就是将输入的密码,例如123456,按0x12,0x34,0x56这样两位切割,然后放在v14数组里面。接下来重点就是第二个循环,我们先来看这个sub_401710函数
sub_401710函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | int __cdecl sub_401710( int a1, const char * a2, signed int a3) { int result; / / eax signed int v4; / / [esp + 4h ] [ebp - 58h ] struct _STARTUPINFOW StartupInfo; / / [esp + 14h ] [ebp - 48h ] BYREF memset(&StartupInfo, 0 , sizeof(StartupInfo)); StartupInfo.cb = 68 ; GetStartupInfoW(&StartupInfo); v4 = strlen(a2); if ( StartupInfo.dwX || StartupInfo.dwY || StartupInfo.dwXCountChars || StartupInfo.dwYCountChars || StartupInfo.dwFillAttribute || StartupInfo.dwXSize || StartupInfo.dwYSize ) { if ( a3 < = v4 ) result = ( int )&a2[a3]; else result = ( int )&a2[v4]; } else if ( a3 < = v4 ) #a3是肯定小于v4的,所以只会执行这条语句 { result = a2[a3] ^ * (unsigned __int8 * )(a3 + a1); * (_BYTE * )(a3 + a1) = result; } else { result = a3 + a1; * (_BYTE * )(a3 + a1) + = byte_416050[v4 + a3] & a2[v4]; } return result; } |
在上面这个函数中 v4是user用户名的长度,a3是两位一组后的密码分组个数计数,v4是永远大于a3的。else if里面的操作就是把V6和用户名异或再把值存放在v16里,为了区别,这里将新的v16记作v16'。然后就是sub_401470,我们通过return语句可以推出,v13 == 0xAB94,通过这个我们就可以推出v16'的值。
sub_401470函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | _DWORD * __usercall sub_401470@<eax>( int a1@<ebx>, _BYTE * a2, _DWORD * a3) { _DWORD * _EAX; / / eax char v5; / / al char _AL; / / al _DWORD * result; / / eax if ( * a2 ! = 'd' ) * a3 ^ = 3u ; else * a3 | = 4u ; if ( a2[ 1 ] ! = 'b' ) { * a3 & = 0x61u ; _EAX = (_DWORD * ) * a3; } else { _EAX = a3; * a3 | = 0x14u ; } __asm { aam } if ( a2[ 2 ] ! = 'a' ) * a3 & = 0xAu ; else * a3 | = 0x84u ; if ( a2[ 3 ] ! = 'p' ) * a3 >> = 7 ; else * a3 | = 0x114u ; if ( a2[ 4 ] ! = 'p' ) * a3 * = 2 ; else * a3 | = 0x380u ; if ( * ((_DWORD * )NtCurrentPeb() - >SubSystemData + 3 ) ! = 2 ) { if ( a2[ 5 ] ! = 'f' ) * a3 | = 0x21u ; else * a3 | = 0x2DCu ; } if ( a2[ 5 ] ! = 's' ) { v5 = (char)a3; * a3 ^ = 0x1ADu ; } else { * a3 | = 0xA04u ; v5 = (char)a3; } _AL = v5 - (~(a1 >> 5 ) - 1 ); __asm { daa } if ( a2[ 6 ] ! = 'e' ) * a3 | = 0x4Au ; else * a3 | = 0x2310u ; if ( a2[ 7 ] ! = 'c' ) { * a3 & = 0x3A3u ; result = (_DWORD * ) * a3; } else { result = a3; * a3 | = 0x8A10u ; } return result; } |
这里面的a2就是v16',只要v16'的每一位值能等于这里面if判断语句的每一个值就会输出v13 == 0xAB94,所以v16'=dbappfsec。然后我们就可以通过v16'和用户名异或得出v16,再把v16和byte_416050数组异或就可以得出我们要的两位一组分割后的密码,最后拼起来就行。现在我们唯一缺的就是byte_416050数组,这里可以通过动态调试得出
byte_416050数组
动态调试之前需要先将前面的反调试语句处理一下,将下图处的jz改为jmp就行了
然后我们再去看一下关键位置的汇编代码
可以看见我们只需要观察ecx的值就可以得出byte_416050数组的值,动调得出值为0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD。ps:这里动调的值,不知道是程序原因还是什么原因,动调出来的值跟网上动调出来的值不一样,最后搬的网上师傅动调出来的值,有师傅知道的可以在评论区指点一手。
exp
1 2 3 4 5 6 7 8 9 10 11 12 | v16_ = "dbappsec" user = "welcomebeijing" v16 = [] for i in range ( 8 ): a = ord (v16_[i]) ^ ord (user[i]) v16.append(a) password = "" byte_416050 = [ 0x2a , 0xd7 , 0x92 , 0xe9 , 0x53 , 0xe2 , 0xc4 , 0xcd ] for i in range ( 8 ): password + = hex (byte_416050[i]^v16[i])[ 2 :] print (passw0rd) #39d09ffa4cfcc4cc |
奇怪的是这个结果程序是可以通过的,但是提交到Buuctf没通过,网上的一些wp的结果提交到Buuctf是可以的,但是通不过程序的检验,不知道是不是Buuctf的flag错了。网上其他wp的结果是4eb5f3992391a1ae,大家可以自己试一下。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。