normal14-17
normal14
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
| int64 __fastcall main(int a1, char **a2, char **a3) { char v4[16]; char v5[16]; char v6[16]; char v7[16]; char v8[112]; char v9[1000]; unsigned __int64 v10;
v10 = __readfsqword(0x28u); puts("[sign in]"); printf("[input your flag]: "); __isoc99_scanf("%99s", v8); sub_96A(v8, (__int64)v9); __gmpz_init_set_str((__int64)v7, (__int64)"ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35", 16LL); __gmpz_init_set_str((__int64)v6, (__int64)v9, 16LL); __gmpz_init_set_str( (__int64)v4, (__int64)"103461035900816914121390101299049044413950405173712170434161686539878160984549", 10LL); __gmpz_init_set_str((__int64)v5, (__int64)"65537", 10LL); __gmpz_powm((__int64)v6, (__int64)v6, (__int64)v5, (__int64)v4); if ( (unsigned int)__gmpz_cmp((__int64)v6, (__int64)v7) ) puts("GG!"); else puts("TTTTTTTTTTql!"); return 0LL; }
|
这个题我想查看函数发现没东西,那么证明这个函数是c中自带的,但是这个我感觉好熟悉。看到65537,多半是RSA,当时极客大挑战做过几道,就直接给脚本了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| from gmpy2 import* from libnum import* from Crypto.Util.number import long_to_bytes import gmpy2 n=103461035900816914121390101299049044413950405173712170434161686539878160984549 c=0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 e=65537 p=366669102002966856876605669837014229419 q=282164587459512124844245113950593348271 phi=(p-1)*(q-1) d=gmpy2.invert(e,phi) flag=pow(c,int(d),n) print(n2s(flag))
|
我们将n分为两个质因数,则剩下的就是c,由此可以求flag
normal16
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
| int __cdecl main_0(int argc, const char **argv, const char **envp) { DWORD v3; DWORD v4; char Str[260]; int v7; char String1[260]; char Destination[260];
memset(Destination, 0, sizeof(Destination)); memset(String1, 0, sizeof(String1)); v7 = 0; printf("pls input the first passwd(1): "); scanf("%s", Destination); if ( strlen(Destination) != 6 ) { printf("Must be 6 characters!\n"); ExitProcess(0); } v7 = atoi(Destination); if ( v7 < 100000 ) ExitProcess(0); strcat(Destination, "@DBApp"); v3 = strlen(Destination); sub_40100A((BYTE *)Destination, v3, String1); if ( !_strcmpi(String1, "6E32D0943418C2C33385BC35A1470250DD8923A9") ) { printf("continue...\n\n"); printf("pls input the first passwd(2): "); memset(Str, 0, sizeof(Str)); scanf("%s", Str); if ( strlen(Str) != 6 ) { printf("Must be 6 characters!\n"); ExitProcess(0); } strcat(Str, Destination); memset(String1, 0, sizeof(String1)); v4 = strlen(Str); sub_401019((BYTE *)Str, v4, String1); if ( !_strcmpi("27019e688a4e62a649fd99cadaafdb4e", String1) ) { if ( !(unsigned __int8)sub_40100F(Str) ) { printf("Error!!\n"); ExitProcess(0); } printf("bye ~~\n"); } } return 0; }
|
这道题只看主函数应该是很好理解的 。
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
| int __cdecl sub_401230(BYTE *pbData, DWORD dwDataLen, LPSTR lpString1) { DWORD i; CHAR String2[4]; BYTE v6[20]; DWORD pdwDataLen; HCRYPTHASH phHash; HCRYPTPROV phProv;
if ( !CryptAcquireContextA(&phProv, 0, 0, 1u, 0xF0000000) ) return 0; if ( CryptCreateHash(phProv, 0x8004u, 0, 0, &phHash) ) { if ( CryptHashData(phHash, pbData, dwDataLen, 0) ) { CryptGetHashParam(phHash, 2u, v6, &pdwDataLen, 0); *lpString1 = 0; for ( i = 0; i < pdwDataLen; ++i ) { wsprintfA(String2, "%02X", v6[i]); lstrcatA(lpString1, String2); } CryptDestroyHash(phHash); CryptReleaseContext(phProv, 0); return 1; } else { CryptDestroyHash(phHash); CryptReleaseContext(phProv, 0); return 0; } } else { CryptReleaseContext(phProv, 0); return 0; } }
|
这个里面全是api函数,根据参数来确定是哪种hash加密。0x8004u对应sha1加密,我学习了一下脚本的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import hashlib password1 = "" arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] for i1 in arr: for i2 in arr: for i3 in arr: for i4 in arr: for i5 in arr: for i6 in arr: password1 = i1 + i2 + i3 + i4 + i5 + i6 + "@DBApp" if(hashlib.sha1(password1.encode("utf-8")).hexdigest().upper() =="6E32D0943418C2C33385BC35A1470250DD8923A9"): print(password1) break
|
从参数来看第二个函数:
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
| int __cdecl sub_401040(BYTE *pbData, DWORD dwDataLen, LPSTR lpString1) { DWORD i; CHAR String2[4]; BYTE v6[16]; DWORD pdwDataLen; HCRYPTHASH phHash; HCRYPTPROV phProv;
if ( !CryptAcquireContextA(&phProv, 0, 0, 1u, 0xF0000000) ) return 0; if ( CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash) ) { if ( CryptHashData(phHash, pbData, dwDataLen, 0) ) { CryptGetHashParam(phHash, 2u, v6, &pdwDataLen, 0); *lpString1 = 0; for ( i = 0; i < pdwDataLen; ++i ) { wsprintfA(String2, "%02X", v6[i]); lstrcatA(lpString1, String2); } CryptDestroyHash(phHash); CryptReleaseContext(phProv, 0); return 1; } else { CryptDestroyHash(phHash); CryptReleaseContext(phProv, 0); return 0; } } else { CryptReleaseContext(phProv, 0); return 0; } }
|
参数为:0x8003u是个md5函数,但md5爆破的话是很慢的。所以我们找到最后对比时的函数:
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
| char __cdecl sub_4014D0(LPCSTR lpString) { LPCVOID lpBuffer; DWORD NumberOfBytesWritten; DWORD nNumberOfBytesToWrite; HGLOBAL hResData; HRSRC hResInfo; HANDLE hFile;
hFile = 0; hResData = 0; nNumberOfBytesToWrite = 0; NumberOfBytesWritten = 0; hResInfo = FindResourceA(0, (LPCSTR)0x65, "AAA"); if ( !hResInfo ) return 0; nNumberOfBytesToWrite = SizeofResource(0, hResInfo); hResData = LoadResource(0, hResInfo); if ( !hResData ) return 0; lpBuffer = LockResource(hResData); sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite); hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0); if ( hFile == (HANDLE)-1 ) return 0; if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) ) return 0; CloseHandle(hFile); return 1; }
|
查阅了一下:FindResourceA:确定指定模块中指定类型和名称的资源的位置。
RTF格式:{\rtf1\ansiHello!\parThis is some {\b bold} text.\par}
我们分析一下,首先用ResourceHacker软件查找名为“AAA”的资源并取出数据,接着进入sub_401005,取出的数据跟
输入的密码2+输入的密码1+“@DBApp”进行了异或,最后生成了.rtf文件,最后的flag应该就在.rtf文件里。
那么如何求密码2呢,我们只需要找到rtf文件将其文件头和AAA前6位异或就行了
1 2 3 4 5 6 7 8 9
| rtf="{\\rtf1" AAA=[0x05,0x7D,0x41,0x15,0x26,0x01] password2="" for i in range(6): password2 += chr(AAA[i] ^ ord(rtf[i])) print(password2)
|
我们运行程序输入密码,回出现一个文件里面存有flag
Flag{N0_M0re_Free_Bugs}
normal17
刚开始没法反编译,
我们找到这里,是栈帧不平衡导致的,将pop处的改为0x0,即可。修改的原则是,只需在出现负数的那个地方的上一行,按alt+k,调整成跟这个数一摸一样的值就可以了。
我们找到main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int __cdecl main_0(int argc, const char **argv, const char **envp) { HANDLE Thread; HANDLE hObject;
sub_4110FF(); ::hObject = CreateMutexW(0, 0, 0); j_strcpy(Destination, &Source); hObject = CreateThread(0, 0, StartAddress, 0, 0, 0); Thread = CreateThread(0, 0, sub_41119F, 0, 0, 0); CloseHandle(hObject); CloseHandle(Thread); while ( dword_418008 != -1 ) ; sub_411190(); CloseHandle(::hObject); return 0; }
|
稍微能看懂一点,hObject一般代表句柄,CreateThread代表着创建线程,看来这个题创建了两个线程.
进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
线程
线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。
- 对于单核CPU而言:多线程就是一个CPU在来回的切换,在交替执行。
- 对于多核CPU而言:多线程就是同时有多条执行路径在同时(并行)执行,每个核执行一个线程,多个核就有可能是一块同时执行的。
第一个线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| char *__cdecl sub_411940(int a1, int a2) { char *result; char v3;
v3 = *(_BYTE *)(a2 + a1); if ( (v3 < 'a' || v3 > 122) && (v3 < 65 || v3 > 90) ) exit(0); if ( v3 < 97 || v3 > 122 ) { result = off_418000[0]; *(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 38]; } else { result = off_418000[0]; *(_BYTE *)(a2 + a1) = off_418000[0][*(char *)(a2 + a1) - 96]; } return result; }
|
这个线程的很清楚,就是加加减减。
第二个线程:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void __stdcall __noreturn sub_411B10(int a1) { while ( 1 ) { WaitForSingleObject(hObject, 0xFFFFFFFF); if ( dword_418008 > -1 ) { Sleep(0x64u); --dword_418008; } ReleaseMutex(hObject); } }
|
CreateThread API 会创建新线程,这个题是一个典型的双线程问题.CreateMutex 创建一个互斥体,用于防止多线程中出现资源争用,即多个线程同时读写同一个资源的情况,所创建的互斥体的句柄会存到全局变量 hObject 中,WaitForSingleObject 等待互斥体的使用权空闲出来,并获取使用权,然后再访问和其他线程共享的资源,访问完后,用 ReleaseMutex 释放使用权,给其他线程使用的机会。那么这两个线程共享的数据是什么呢,看参数我们就知道是dword_418008。
这两个线程一前一后创建,理论上是 StartAddress 先获得使用权,后来的 sub_41119F 进入等待状态,前者执行一次循环后释放使用权,与此同时后者等待结束、获得使用权,进入循环,循环完后释放使用权,前者又获得使用权,如此循环往复。
所以就相当于我给出的字符串是第一位照抄,第二位进行线程一的变化。经过线程后与Source对比
1 2 3 4 5 6 7 8 9 10
| int sub_411880() { int i; for ( i = 0; i < 29; ++i ) { if ( Source[i] != off_418004[i] ) exit(0); } return printf("\nflag{%s}\n\n", Destination); }
|
给出脚本:
1 2 3 4 5 6 7 8 9 10 11 12
| data1="TOiZiZtOrYaToUwPnToBsOaOapsyS" data2="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm" flag="" for i in range(len(data1)): if i%2==0: flag+=data1[i] else: if ord(data1[i])>ord('a') and ord(data1[i])<ord('z'): flag+=chr(data2.find(data1[i])+38) else: flag+=chr(data2.find(data1[i])+96) print(flag)
|
最后这个E是什么都行,因为dword_418008只有29位!
得到ThisisthreadofwindowshahaIsES