香山杯2023初赛
一. RE
1.URL从哪儿来
-
看main函数
-
int __cdecl main(int argc, const char **argv, const char **envp) { HMODULE ModuleHandleW; // eax HMODULE v4; // eax HMODULE v5; // eax _BYTE *v7; // [esp+4h] [ebp-28Ch] HGLOBAL hResData; // [esp+8h] [ebp-288h] HRSRC hResInfo; // [esp+Ch] [ebp-284h] _BYTE *lpAddress; // [esp+10h] [ebp-280h] FILE *Stream; // [esp+1Ch] [ebp-274h] DWORD dwSize; // [esp+20h] [ebp-270h] DWORD i; // [esp+28h] [ebp-268h] struct _PROCESS_INFORMATION ProcessInformation; // [esp+30h] [ebp-260h] BYREF struct _STARTUPINFOA StartupInfo; // [esp+40h] [ebp-250h] BYREF CHAR Buffer[260]; // [esp+84h] [ebp-20Ch] BYREF CHAR TempFileName[260]; // [esp+188h] [ebp-108h] BYREF ModuleHandleW = GetModuleHandleW(0); hResInfo = FindResourceW(ModuleHandleW, (LPCWSTR)0x65, L"e_ou"); v4 = GetModuleHandleW(0); hResData = LoadResource(v4, hResInfo); v7 = LockResource(hResData); v5 = GetModuleHandleW(0); dwSize = SizeofResource(v5, hResInfo); lpAddress = VirtualAlloc(0, dwSize, 0x1000u, 4u); if ( !lpAddress ) return 1; for ( i = 0; i < dwSize; ++i ) { if ( v7[i] && v7[i] != 120 ) lpAddress[i] = v7[i] ^ 0x78; else lpAddress[i] = v7[i]; } if ( !GetTempPathA(0x104u, Buffer) ) return 1; if ( !GetTempFileNameA(Buffer, "ou.exe", 0, TempFileName) ) return 1; Stream = fopen(TempFileName, "wb"); if ( !Stream ) return 1; if ( fwrite(lpAddress, 1u, dwSize, Stream) == dwSize ) { fclose(Stream); VirtualFree(lpAddress, 0, 0x8000u); memset(&StartupInfo, 0, sizeof(StartupInfo)); memset(&ProcessInformation, 0, sizeof(ProcessInformation)); StartupInfo.cb = 68; GetStartupInfoA(&StartupInfo); StartupInfo.wShowWindow = 0; CreateProcessA(TempFileName, 0, 0, 0, 1, 0, 0, 0, &StartupInfo, &ProcessInformation); CloseHandle(ProcessInformation.hProcess); CloseHandle(ProcessInformation.hThread); DeleteFileA(TempFileName); return 0; } else { fclose(Stream); return 1; } }
-
第一种方法(最简单的):直接动调,在CreateProcessA函数处下断点即可,导出ou.26B6.tmp文件,查壳,拖入IDA分析,逻辑很简单,-30后base64就行
-
第二种方法:将原来的exe拖入resource hack里导出bin文件,再写脚本进行异或,输出bin文件,再IDA分析即可
-
下面是bin文件输出方法:
-
arr = 0x78 with open('C:\\Users\\lenovo\\Desktop\\E_OU101.bin', 'rb') as f: b = f.read() b = bytearray(b) for i in range(len(b)): if b[i]!=0 & b[i]!=120: b[i] = b[i] ^ arr else: b[i]=b[i] with open('C:\\Users\\lenovo\\Desktop\\COD_de1', 'wb') as f: f.write(b)
-
脚本简单不在此赘述
2.hello_py
-
该题运用chaquopy来调用python代码,根据题目提示我们找到python源码,在app.imy里找到hello.py,代码如下:
-
from java import jboolean ,jclass #line:1 import struct #line:3 import ctypes #line:4 def MX (O0O00OOO00OO00O00 ,O0OO0O00OO0O000OO ,OO000OO000000O0O0 ,OOO00O00OOO000OOO ,OO0OOO0OOO0OOOO0O ,O0OO000O0000O000O ):#line:7 OOO000O0O0OO00000 =(O0O00OOO00OO00O00 .value >>5 ^O0OO0O00OO0O000OO .value <<2 )+(O0OO0O00OO0O000OO .value >>3 ^O0O00OOO00OO00O00 .value <<4 )#line:8 OOO0OOOOOO0O0OO00 =(OO000OO000000O0O0 .value ^O0OO0O00OO0O000OO .value )+(OOO00O00OOO000OOO [(OO0OOO0OOO0OOOO0O &3 )^O0OO000O0000O000O .value ]^O0O00OOO00OO00O00 .value )#line:9 return ctypes .c_uint32 (OOO000O0O0OO00000 ^OOO0OOOOOO0O0OO00 )#line:11 def encrypt (OO0OO0O0O0O0O0OO0 ,OOO0O0OO0O0OOO000 ,OO0OOOO0OO0OOO0O0 ):#line:14 O0OOO0OO00O0000OO =0x9e3779b9 #line:15 OOOO0OOOO00O0OOOO =6 +52 //OO0OO0O0O0O0O0OO0 #line:16 O00OO00000O0OO00O =ctypes .c_uint32 (0 )#line:18 OO0OOOO0O0O0O0OO0 =ctypes .c_uint32 (OOO0O0OO0O0OOO000 [OO0OO0O0O0O0O0OO0 -1 ])#line:19 OOOOO00000OOOOOOO =ctypes .c_uint32 (0 )#line:20 while OOOO0OOOO00O0OOOO >0 :#line:22 O00OO00000O0OO00O .value +=O0OOO0OO00O0000OO #line:23 OOOOO00000OOOOOOO .value =(O00OO00000O0OO00O .value >>2 )&3 #line:24 for OO0O0OOO000O0000O in range (OO0OO0O0O0O0O0OO0 -1 ):#line:25 OOO0OO00O0OO0O000 =ctypes .c_uint32 (OOO0O0OO0O0OOO000 [OO0O0OOO000O0000O +1 ])#line:26 OOO0O0OO0O0OOO000 [OO0O0OOO000O0000O ]=ctypes .c_uint32 (OOO0O0OO0O0OOO000 [OO0O0OOO000O0000O ]+MX (OO0OOOO0O0O0O0OO0 ,OOO0OO00O0OO0O000 ,O00OO00000O0OO00O ,OO0OOOO0OO0OOO0O0 ,OO0O0OOO000O0000O ,OOOOO00000OOOOOOO ).value ).value #line:27 OO0OOOO0O0O0O0OO0 .value =OOO0O0OO0O0OOO000 [OO0O0OOO000O0000O ]#line:28 OOO0OO00O0OO0O000 =ctypes .c_uint32 (OOO0O0OO0O0OOO000 [0 ])#line:29 OOO0O0OO0O0OOO000 [OO0OO0O0O0O0O0OO0 -1 ]=ctypes .c_uint32 (OOO0O0OO0O0OOO000 [OO0OO0O0O0O0O0OO0 -1 ]+MX (OO0OOOO0O0O0O0OO0 ,OOO0OO00O0OO0O000 ,O00OO00000O0OO00O ,OO0OOOO0OO0OOO0O0 ,OO0OO0O0O0O0O0OO0 -1 ,OOOOO00000OOOOOOO ).value ).value #line:30 OO0OOOO0O0O0O0OO0 .value =OOO0O0OO0O0OOO000 [OO0OO0O0O0O0O0OO0 -1 ]#line:31 OOOO0OOOO00O0OOOO -=1 #line:32 return OOO0O0OO0O0OOO000 #line:34 def check (O0000000000O0O0O0 ):#line:63 print ("checking~~~: "+O0000000000O0O0O0 )#line:64 O0000000000O0O0O0 =str (O0000000000O0O0O0 )#line:65 if len (O0000000000O0O0O0 )!=36 :#line:66 return jboolean (False )#line:67 O00OO00000OO0OOOO =[]#line:69 for O0O0OOOOO0OOO0OOO in range (0 ,36 ,4 ):#line:70 OO0OO0OOO000OO0O0 =O0000000000O0O0O0 [O0O0OOOOO0OOO0OOO :O0O0OOOOO0OOO0OOO +4 ].encode ('latin-1')#line:71 O00OO00000OO0OOOO .append (OO0OO0OOO000OO0O0 )#line:72 _O00OO0OOOOO00O00O =[]#line:73 for O0O0OOOOO0OOO0OOO in O00OO00000OO0OOOO :#line:74 _O00OO0OOOOO00O00O .append (struct .unpack ("<I",O0O0OOOOO0OOO0OOO )[0 ])#line:75 print (_O00OO0OOOOO00O00O )#line:77 OO0OO0OOO000OO0O0 =encrypt (9 ,_O00OO0OOOOO00O00O ,[12345678 ,12398712 ,91283904 ,12378192 ])#line:78 OOOOO0OOO0OO00000 =[689085350 ,626885696 ,1894439255 ,1204672445 ,1869189675 ,475967424 ,1932042439 ,1280104741 ,2808893494 ]#line:85 for O0O0OOOOO0OOO0OOO in range (9 ):#line:86 if OOOOO0OOO0OO00000 [O0O0OOOOO0OOO0OOO ]!=OO0OO0OOO000OO0O0 [O0O0OOOOO0OOO0OOO ]:#line:87 return jboolean (False )#line:88 return jboolean (True )#line:90 def sayHello ():#line:92 print ("hello from py")#line:93
-
虽然混淆了,但是可以看出来是XXTea加密
-
后面直接套脚本解密即可
-
import ctypes def MX(z, y, total, tea_key, p, e): temp1 = (z .value >> 5 ^ y .value << 2)+(y .value >> 3 ^ z .value << 4) temp2 = (total .value ^ y .value)+(tea_key[(p & 3) ^ e .value] ^ z .value) return ctypes.c_uint32(temp1 ^ temp2) def decrypt(n, v, key): delta = 0x9e3779b9 rounds = 6 + 52//n total = ctypes.c_uint32(rounds * delta) y = ctypes.c_uint32(v[0]) e = ctypes.c_uint32(0) while rounds > 0: e.value = (total.value >> 2) & 3 for p in range(n-1, 0, -1): z = ctypes.c_uint32(v[p-1]) v[p] = ctypes.c_uint32((v[p] - MX(z,y,total,key,p,e).value)).value y.value = v[p] z = ctypes.c_uint32(v[n-1]) v[0] = ctypes.c_uint32(v[0] - MX(z,y,total,key,0,e).value).value y.value = v[0] total.value -= delta rounds -= 1 return v key_str = [689085350, 626885696, 1894439255, 1204672445, 1869189675, 475967424, 1932042439, 1280104741, 2808893494] res = encrypt_input = decrypt(9, key_str, [12345678, 12398712, 91283904, 12378192]) for i in res: print(hex(i)[2:])
-
手动恢复大端序,然后16进制转字符串即可
-
flag{c1f8ace6-4b46-4931-b25b-a1010a89c592}
3. nesting
-
竟然是一道vm题,与最近的研究方向撞上了哈哈
-
双重vm,大致有两种方法:手搓+脚本还原或是用intel pin进行爆破
-
首先介绍第一种方法:intel pin爆破
-
找到程序的输入点,再找到程序的cmp点,有两个case:0x1E21,0x1EC9,通过输入fl,fi等不同字符串发现,两处执行次数不同,用pin去统计执行次数,这样我们可以逐字节爆破。
-
首先在 source/tools/ManualExamples 路径下 make obj-intel64/inscount0.so TARGET=intel64,编译生成计数器的so文件(即为一种pintool),可以记录程序执行的指令的条数
-
注:想知道pintool功能可以:$ pin -t your_pintool -h -- your_application
-
同样的,编译32位pintool使用:make obj-ia32/inscount0.so
-
可以用 make all TAEGET=intel64 make all TAEGET=ia32 来编译所有的pintool
-
编译完以后生成输出文件 ../../../pin -t obj-intel64/inscount0.so -o inscount0.log -- 文件路径
-
这时可以再去写脚本进行爆破,另外的,在编译pintool之前可以把.c源码文件中的一些源码改掉以适应情况
-
对于此题:
-
首先我们只想统计指定行代码的执行次数,所以将inscount0.c中:
-
VOID docount() { icount++; }
-
改为:
-
VOID docount(void *ip) { if ((long long int)ip == 0x0000555555555E21) icount++; }
-
并将
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
-
改为
-
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, IARG_END);
-
加入IARG_INST_PTR参数,是为了检测指令的地址,传入ip
-
注意用
sudo su echo 0 > /proc/sys/kernel/randomize_va_space
-
将ALSR保护关掉
-
改完后在将inscount0进行64位编译,生成输出文件,之后编写脚本如下:
-
from pwn import * import subprocess def run(msg): cmd = [ "/home/tanggerr/Downloads/pin-3.28-98749-g6643ecee5-gcc-linux/pin", "-t", "/home/tanggerr/Downloads/pin-3.28-98749-g6643ecee5-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so", "-o", "/home/tanggerr/Downloads/pin-3.28-98749-g6643ecee5-gcc-linux/source/tools/ManualExamples/inscount0.log", "--", "/home/tanggerr/nesting" ] # 启动进程,并设置 stdin, stdout, stderr 为 PIPE,以便与进程交互 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 发送数据到进程 p.stdin.write(msg.encode()) p.stdin.flush() # 从进程接收数据 output = p.stdout.readline() # print(output) # 结束进程 p.terminate() return int(read("/home/tanggerr/Downloads/pin-3.28-98749-g6643ecee5-gcc-linux/source/tools/ManualExamples/inscount0.log").split(" ")[1]) def read(fname): with open(fname) as f: return f.read() # print(run("fl")) charset = string.printable l = [] flag = "" counter = 0 while(True): max_chr = 0 first_iteration = True # 初始化标志为 True for chr in charset: tmp = run(flag + chr) if first_iteration: # 只在第一次迭代时执行 max_value = tmp first_iteration = False # 设置标志为 False if tmp > max_value: max_chr = chr max_value = tmp break print(max_chr) flag += str(max_chr) print(flag) #flag{2c7c093b-f648-11ed-a716-701ab8caaafe}
-
等待其爆破出flag{2c7c093b-f648-11ed-a716-701ab8caaafe}
-
接着看第二种方法:
-
手搓两层vm,这里我学习了LaoGong战队的解法,请见2023 10.15 香山杯 wp - LaoGong - 飞书云文档 (feishu.cn)
-
只能说手搓是真的nb(tql,orz