hgame 2022 week4 re

发布于 2024-03-15  102 次阅读


1.WOW:

首先用die查看一下,选择32位的ida打开:

int __cdecl main(int argc, const char **argv, const char **envp)
{
 char v4[64]; // [esp+0h] [ebp-168h] BYREF
 char v5[64]; // [esp+40h] [ebp-128h] BYREF
 int v6[17]; // [esp+80h] [ebp-E8h] BYREF
 char v7[64]; // [esp+C4h] [ebp-A4h] BYREF
 char v8[16]; // [esp+104h] [ebp-64h] BYREF
 _QWORD Buf2[5]; // [esp+114h] [ebp-54h] BYREF
 char v10[4]; // [esp+13Ch] [ebp-2Ch] BYREF
 int v11; // [esp+140h] [ebp-28h]
 int v12; // [esp+144h] [ebp-24h]
 int v13; // [esp+148h] [ebp-20h]
 int v14; // [esp+14Ch] [ebp-1Ch]
 int v15; // [esp+150h] [ebp-18h]
 int v16; // [esp+154h] [ebp-14h]
 int v17; // [esp+158h] [ebp-10h]
 int v18; // [esp+15Ch] [ebp-Ch]
 int v19; // [esp+160h] [ebp-8h]
 int i; // [esp+164h] [ebp-4h]

 memset(v6, 0, sizeof(v6));
 v6[0] = 68;
 memset(v8, 0, sizeof(v8));
 *(_DWORD *)v10 = 0;
 v11 = 0;
 v12 = 0;
 v13 = 0;
 v14 = 0;
 v15 = 0;
 v16 = 0;
 v17 = 0;
 v18 = 0;
 v19 = 0;
 memset(Buf2, 0, sizeof(Buf2));
 sub_401940(&unk_404D60);
 sub_401850(&unk_404D60, &unk_4051A0);
 sub_4012B0();
 sub_401A50(Format);
 sub_401AC0(a40s, (char)v10);
 for ( i = 0; i < 4; ++i )
{
   sub_401850(&v10[8 * i], v5);
   sub_401410(v5, v7);
   sub_4018D0(v7, &Buf2[i]);
}
 sub_4019B0((char *)&loc_401C73 + 51);
 sub_4019B0(&loc_401C73);
 if ( !memcmp(byte_404D40, Buf2, 0x20u) )
   sub_401A50(aYouWin);
 else
   sub_401A50(aError);
 memset(v10, 0, 0x28u);
 for ( i = 0; i < 4; ++i )
{
   sub_401850(&Buf2[i], v7);
   sub_401630(v7, v4);
   sub_4018D0(v4, &v10[8 * i]);
}
 return 0;
}

分析可知,是一段对称加密,所以我们只需要把加密的数据在调试的时候放进对比的位置就能让程序执行加载flag的步骤,并且把flag放在内存里面。

2.ezvm

由题目名字可知,这是一道VM的题,先有64位ida打开程序看一下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
 int v3; // eax

 sub_40B110(argc, argv, envp);
 sub_497E40(&unk_4A1E00, "input your flag:", 16i64);
 sub_4993C0(&unk_4A1E00);
 while ( 1 )
{
   v3 = dword_49F020[dword_49F020[0] + 109];
   if ( v3 == -1 )
     return 0;
   switch ( v3 )
  {
     case 0:
       sub_401770(&dword_49F020[3], &dword_49F020[2]);
       break;
     case 1:
       sub_401775(&dword_49F020[2]);
       break;
     case 2:
       sub_401779(&dword_49F020[2]);
       break;
     case 3:
       sub_40178C(&dword_49F020[3], &dword_49F020[7]);
       break;
     case 4:
       sub_401798();
       break;
     case 5:
       sub_4017BB();
       break;
     case 6:
       sub_4017DE();
       break;
     case 7:
       sub_401825();
       break;
     case 8:
       sub_40183B();
       break;
     case 9:
       sub_401851();
       break;
     case 10:
       sub_401867();
       break;
     case 11:
       sub_40187D();
       break;
     case 12:
       sub_4018BE((unsigned int)dword_49F020[6]);
       break;
     case 13:
       sub_4018CE((unsigned int)dword_49F020[6]);
       break;
     case 14:
       sub_401791((unsigned int)dword_49F020[2]);
       break;
     case 15:
       sub_401893((unsigned int)dword_49F020[3], (unsigned int)dword_49F020[5]);
       break;
     case 16:
       sub_4018DE(&dword_49F020[3]);
       break;
     case 17:
       sub_4018F3(&dword_49F020[3]);
       break;
     case 18:
       sub_401904();
       break;
     case 19:
       sub_40193A();
       break;
     case 20:
       sub_401953();
       break;
     case 21:
       sub_40177D();
       break;
     default:
       break;
  }
   ++dword_49F020[0];
}
}

对每一个case进行翻译即可

大致逻辑应该是输入每一个字符,左移一位,和一个值异或比较

可以用爆破来做:

# -*- coding:utf-8 -*-
s="hgame{abcdefghijklmnopqrstuvwxy}"

# 对输入的处理逻辑:输入的每个字符,左移1位,和一个值异或,然后比较

print(hex((ord("h")<<1)^0x5e))# 0x8e
print(hex((ord("g")<<1)^0x46))# 0x88

# my_res: 构造的输入s,经过程序处理后的结果
my_res=[0x8e,0x88,0xa3,0x99,0xc4,0xa5,0x8b,0xdb,0x97,0x96,0xfc,0xfb,0xe7,0x91,0xb1,0xef,0xb2,0xe3,0xcf,0xc4,0x85,0xde,0xc0,0xb4,0xa0,0xb6,0xdf,0xa2,0xad,0xd3,0x92,0xc1]
# 逆向对输入的处理逻辑,得到输入的每个字符左移1位后,要去异或的值的集合
xor_box=[]
for i in range(len(s)):
   tmp=(ord(s[i])<<1)&0xff
   xor_box.append(tmp^my_res[i])

# res: 真正的密文
res=[0x8e,0x88,0xa3,0x99,0xc4,0xa5,0xc3,0xdd,0x19,0xec,0x6c,0x9b,0xf3,0x1b,0x8b,0x5b,0x3e,0x9b,0xf1,0x86,0xf3,0xf4,0xa4,0xf8,0xf8,0x98,0xab,0x86,0x89,0x61,0x22,0xc1]
flag=""
for i in range(len(res)):
   for j in range(32,128):
       if (j<<1)^xor_box[i]==res[i]:
           flag+=chr(j)
print(flag)
# hgame{Ea$Y-Vm-t0-PrOTeCT_cOde!!}

3.server

先用die查一下,是64位的程序,没有壳的保护,并且是用Go语言写的

找到主函数main_main

while ( (unsigned __int64)&retaddr <= *(_QWORD *)(v4 + 16) )
   runtime_morestack_noctxt();

这一个循环是检查栈的可用空间,能申请更多的空间

然后我们发现,主函数的回调函数

main_HttpHandleFuncvoid __fastcall net_http__ptr_Server_ListenAndServe()
{
__int64 v0; // rax
__int64 v1; // r14
__int64 v2; // rcx
__int64 v3; // [rsp-20h] [rbp-28h]
void *retaddr; // [rsp+8h] [rbp+0h] BYREF
__int64 v6; // [rsp+10h] [rbp+8h]

while ( (unsigned __int64)&retaddr <= *(_QWORD *)(v1 + 16) )
{
  v6 = v0;
  runtime_morestack_noctxt();
  v0 = v6;
}
if ( !*(_DWORD *)(v0 + 120) )
{
  v3 = net_Listen();
  if ( !v2 )
    net_http__ptr_Server_Serve(v3);
}
}

我们用8.3的ida运行一下试试,看来8.3还是比7.7聪明一点:

__int64 __golang main_HttpHandleFunc(__int64 a1, int a2, http_Request *a3, int a4, int a5)
{
 __int64 v5; // r14
 char *ptr; // rax
 signed __int64 len; // rbx
 int v8; // r8d
 int v9; // r9d
 int v10; // r10d
 int v11; // r11d
 string v12; // kr00_16
 __int64 v13; // rcx
 unsigned __int64 v14; // rdx
 unsigned __int64 v15; // rsi
 int v16; // eax
 int v17; // r10d
 int v18; // r11d
 __int64 v20; // rbx
 int v21; // r8d
 int v22; // r9d
 int v23; // r10d
 int v24; // r11d
 __int64 v25; // rax
 int v26; // r8d
 int v27; // r9d
 int v28; // r10d
 int v29; // r11d
 int v30; // eax
 int v31; // r10d
 int v32; // r11d
 int v33; // eax
 int v34; // r10d
 int v35; // r11d
 __int64 v36; // [rsp+0h] [rbp-1368h]
 __int64 v37; // [rsp+0h] [rbp-1368h]
 __int64 v38; // [rsp+0h] [rbp-1368h]
 __int64 v39[154]; // [rsp+8h] [rbp-1360h] BYREF
 size_t v40; // [rsp+4D8h] [rbp-E90h]
 __int64 v41; // [rsp+4E0h] [rbp-E88h]
 unsigned __int64 v42; // [rsp+4E8h] [rbp-E80h]
 __int64 v43; // [rsp+4F0h] [rbp-E78h]
 char v44[1216]; // [rsp+4F8h] [rbp-E70h] BYREF
 __int64 v45; // [rsp+9B8h] [rbp-9B0h] BYREF
 char v46[1216]; // [rsp+9C0h] [rbp-9A8h] BYREF
 __int64 v47; // [rsp+E80h] [rbp-4E8h] BYREF
 char v48[1216]; // [rsp+E88h] [rbp-4E0h] BYREF
 char *v49; // [rsp+1348h] [rbp-20h]
 __int64 v50; // [rsp+1350h] [rbp-18h]
 _ptr_http_Request v51; // [rsp+1358h] [rbp-10h]
 void *retaddr; // [rsp+1368h] [rbp+0h] BYREF
 int v54; // [rsp+1370h] [rbp+8h]
 __int64 v55; // [rsp+1370h] [rbp+8h]
 _ptr_http_Request v59; // [rsp+1380h] [rbp+18h]
 string v60; // 0:rbx.8,8:rcx.8

 while ( (unsigned __int64)&retaddr < 0x12E8 || (unsigned __int64)&v39[15] <= *(_QWORD *)(v5 + 16) )
{
   v55 = a1;
   v59 = a3;
   runtime_morestack_noctxt();
   a1 = v55;
   a3 = v59;
}
 v54 = a1;
 v51 = a3;
 net_http__ptr_Request_ParseForm(a3);
 v60.ptr = "flag";
 v60.len = 4LL;
 v12 = net_http__ptr_Request_FormValue(v51, v60);
 len = v12.len;
 ptr = v12.ptr;
 if ( v12.len )
{
   v40 = v12.len;
   v49 = v12.ptr;
   v13 = 0LL;
   v14 = 0LL;
   v15 = 0LL;
   while ( len > v13 )
  {
     v50 = v15;
     v41 = v13;
     v42 = v14;
     a4 = strconv_FormatInt((unsigned __int8)ptr[v13], 16, v13, a4, v15, v8, v9, v10, v11, v36, v39[0]);
     v20 = v50;
     v25 = runtime_concatstring2(0, v50, v42, a4, 16, v21, v22, v23, v24, v37, v39[0], v39[1], v39[2]);
     v13 = v41 + 1;
     v14 = v20;
     v15 = v25;
     ptr = v49;
     len = v40;
  }
   v43 = 99LL;
   qmemcpy(v44, &unk_69B9F0, sizeof(v44));
   main_encrypt(v15, v14, 0LL, (__int64)&v45, (int)&unk_69B9F0 + 1216, v8, v9, v10, v11, v36, v39[0]);
   v47 = v38;
   qmemcpy(v48, v39, sizeof(v48));
   v45 = v43;
   qmemcpy(v46, v44, sizeof(v46));
   if ( (unsigned __int8)runtime_memequal(&v47, &v45, 1224LL) )
  {
     v30 = runtime_convI2I(
            (unsigned int)&RTYPE_io_Writer,
             v54,
             a2,
            (unsigned int)&v47,
            (unsigned int)&v45,
             v26,
             v27,
             v28,
             v29);
     return fmt_Fprintf(v30, v54, (unsigned int)"Congradulation!", 15, 0, 0, 0, v31, v32);
  }
   else
  {
     v33 = runtime_convI2I(
            (unsigned int)&RTYPE_io_Writer,
             v54,
             a2,
            (unsigned int)&v47,
            (unsigned int)&v45,
             v26,
             v27,
             v28,
             v29);
     return fmt_Fprintf(v33, v54, (unsigned int)"Wrong!", 6, 0, 0, 0, v34, v35);
  }
}
 else
{
   v16 = runtime_convI2I((unsigned int)&RTYPE_io_Writer, v54, a2, a4, a5, v8, v9, v10, v11);
   return fmt_Fprintf(v16, v54, (unsigned int)"Please input flag!", 18, 0, 0, 0, v17, v18);
}
}

根据大佬的做法,我们通过分析可以得到加密过程为RSA和异或运算,使用爆破来解决:

from Crypto.Util.number import *
#pip install pycryptodome -i https://pypi.tuna.tsinghua.edu.cn/simple
import gmpy2
#pip install gmpy2

a = [99,85,4,3,5,5,5,3,7,7,2,8,8,11,1,2,10,4,2,13,8,9,12,9,4,13,8,0,14,0,15,13,14,10,2, 2,1,7,3,5,6,4,6,7,6,2,2,5,3,3,9,6,0,11,13,11,0,2,3,8,3,11,7,1,11,5,14,5,0,10,14,15, 13,7,13,7,14,1,15,1,11,5,6,2,12,6,10,4,1,7,4,2,6,3,6,12,5,12,3,12,6,0,4,15,2,14,7,0 ,14,14,12,4,3,4,2,0,0,2,6,2,3,6,4,4,4,7,1,2,3,9,2,12,8,1,12,3,12,2,0,3,14,3,14,12,9 ,1,7,15,5,7,2,2,4]

for j in range(256): # 爆破
num = j
a = [99,85,4,3,5,5,5,3,7,7,2,8,8,11,1,2,10,4,2,13,8,9,12,9,4,13,8,0,14,0,15,13,14,10,2, 2,1,7,3,5,6,4,6,7,6,2,2,5,3,3,9,6,0,11,13,11,0,2,3,8,3,11,7,1,11,5,14,5,0,10,14,15, 13,7,13,7,14,1,15,1,11,5,6,2,12,6,10,4,1,7,4,2,6,3,6,12,5,12,3,12,6,0,4,15,2,14,7,0 ,14,14,12,4,3,4,2,0,0,2,6,2,3,6,4,4,4,7,1,2,3,9,2,12,8,1,12,3,12,2,0,3,14,3,14,12,9 ,1,7,15,5,7,2,2,4]
for i in range(len(a)-1,-1,-1):
num ^= a[i]
a[i] = a[i] ^ num
for i in range(len(a)-1,-1,-1):
num ^= a[i]
a[i] = a[i] ^ num
try: # 将无法转换的情况直接丢弃,说明该情况必然不是flag
enc = int(“”.join(map(chr,a)))
except ValueError:
continue

p = 92582184765240663364795767694262273105045150785272129481762171937885924776597
q = 107310528658039985708896636559112400334262005367649176746429531274300859498993
e = 950501
r = (p-1)*(q-1)
d = gmpy2.invert(e,r)
m = pow(enc,d,p*q)
print(long_to_bytes(m))

4.hardasm

64位的程序,用ida打开来看一下main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
 char *v6684; // rcx
 int result; // eax
 __m256 v6688; // [rsp+20h] [rbp-50h] BYREF

 __asm
{
   vmovaps [rbp+var_10], xmm7
   vmovaps [rbp+var_20], xmm6
   vstmxcsr dword ptr [rsp+70h+var_50]
}
 LODWORD(v6688.m256_f32[0]) |= 0x8040u;
 __asm
{
   vldmxcsr dword ptr [rsp+70h+var_50]
   vxorps  xmm0, xmm0, xmm0
   vmovaps ymmword ptr [rsp+70h+var_50], ymm0
   vzeroupper
}
 sub_140008070(argc, &v6688, envp);
 __asm
{
   vmovdqa ymm0, ymmword ptr [rsp+70h+var_50]
   vmovdqa ymm1, cs:ymmword_14000A040
   vmovdqa ymm2, cs:ymmword_14000A060
   vmovdqa ymm3, cs:ymmword_14000A080
   vmovdqa ymm4, cs:ymmword_14000A0A0
   vmovdqa ymm5, cs:ymmword_14000A0C0
   vmovdqa ymm6, cs:ymmword_14000A0E0
   vmovdqa ymm7, cs:ymmword_14000A100
   vpermd  ymm4, ymm7, ymm4
  ........

统计一下可以看到:

里面出现了这几指令:vpsubb,vpermd,vpshufb,vpaddb,vpxor

其实会一些汇编的能看出来这几个指令的意思;

第一个执行两个字节向量之间的逐元素减法运算

第二个用于对两个双字向量进行按位排列重组

第三个用于按位重新排列字节向量中的数据

第四个逐元素加法运算

第五个就是异或运算

然后就可以使用idapython解码

import numpy
def vpaddb(a,b):
global num
for i in range(32):
 num[a][i]+=num[b][i]
def vpsubb(a,b):
global num
for i in range(32):
 num[a][i]-=num[b][i]
def vpxor(a,b):
global num
for i in range(32):
 num[a][i]^=num[b][i]
def vpshufb(a,b):
global num
c=[0]*32
for i in range(16):
 if num[b][i]&0x80:
   c[i]=0
 else:
   c[i]=num[a][num[b][i]&0xF]
 if num[b][i+16]&0x80:
   c[i+16]=0
 else:
   c[i+16]=num[a][16+(num[b][i+16]&0xF)]
num[a]=c
def invvpshufb(a,b):
global num
c=[0]*32
for i in range(16):
    if num[b][i]&0x80:
       c[i]=0
    else:
       c[num[b][i]&0xF]=num[a][i]
    if num[b][i+16]&0x80:
       c[i+16]=0
    else:
       c[16+(num[b][i+16]&0xF)]=num[a][i+16]
num[a]=c
def vpermd(a,b):
global num
c=[0]*32
for i in range(8):
 c[i*4]=num[a][num[b][i*4]*4]
 c[i*4+1]=num[a][num[b][i*4]*4+1]
 c[i*4+2]=num[a][num[b][i*4]*4+2]
 c[i*4+3]=num[a][num[b][i*4]*4+3]
num[a]=c
def invvpermd(a,b):
global num
c=[0]*32
for i in range(8):
 c[num[b][i*4]*4]=num[a][i*4]
 c[num[b][i*4]*4+1]=num[a][i*4+1]
 c[num[b][i*4]*4+2]=num[a][i*4+2]
 c[num[b][i*4]*4+3]=num[a][i*4+3]
num[a]=c
op=[]
flag=[147,203,231,147,169,129,13,182,216,221,156,127,192,77,205,240,0,160,159,34,137,239
,84,93,239,0,141,254,94,76,208,236]
num=numpy.uint8([[104,103,97,109,101,123,114,105,103,104,116,95,121,111,117,114,95,
97,115,109,95,105,115,95,103,111,111,100,33,33,125,0],
[
240,255,100,38,242,143,64,238,238,39,7,239,136,10,33,20,195,252,112,229,168,243,245
,26,212,60,177,12,229,188,185,27],
[
13,192,132,197,14,128,80,255,40,26,128,72,29,193,227,29,52,81,155,53,188,213,244,195,196,64,144,7,42,192,45,144],
[
137,161,62,192,229,20,95,197,95,20,176,208,37,31,232,245,176,52,54,194,199,160,178,
60,94,126,156,164,152,232,84,11],
[
51,95,98,104,13,100,168,255,143,153,167,148,158,154,41,52,39,54,214,130,194,109,232
,170,150,74,101,192,12,55,25,201],
[
143,33,168,55,67,9,7,51,166,135,76,74,161,116,75,230,85,19,91,63,28,215,185,158,57,
96,29,198,145,138,54,139],
[ 0,1,8,9,10,2,3,4,5,12,13,14,6,7,11,15, 0,6,7,8,9,10,2,3,4,5,13,14,11,12,1,15],
[ 0,0,0,0,4,0,0,0,5,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,6,0,0,0,7,0,0,0]])#八个寄存器
ea=0x140001081
while True : #运行一遍汇编,验证加密过程是否正确,并得到ymm1~ymm7的数据
if ea >= 0x0140007F17:
 break
opcode=print_insn_mnem(ea) # 得到汇编助记符
opnum1=int(print_operand(ea,0)[-1]) #得到第一个操作数的序号(即ymm0取0)
opnum2=int(print_operand(ea,1)[-1])
opnum3=int(print_operand(ea,2)[-1])
op.append([opcode,opnum1,opnum2,opnum3])
if opcode=='vpaddb':
 vpaddb(opnum1,opnum3)
elif opcode=='vpsubb':
 vpsubb(opnum1,opnum3)
elif opcode=='vpxor':
 vpxor(opnum1,opnum3)
elif opcode=='vpshufb':
 vpshufb(opnum1,opnum3)
elif opcode=='vpermd':
 vpermd(opnum1,opnum2)
ea=next_head(ea)
num[0]=flag
for i in op[::-1]: # 倒序解密
if i[0]=='vpaddb':
 vpsubb(i[1],i[3])
elif i[0]=='vpsubb':
 vpaddb(i[1],i[3])
elif i[0]=='vpxor':
 vpxor(i[1],i[3])
elif i[0]=='vpshufb':
 invvpshufb(i[1],i[3])
elif i[0]=='vpermd':
 invvpermd(i[1],i[2])
for i in num[0]:
try:
 print(chr(i),end='')
except UnicodeEncodeError:
 print(i,end='')


The world's full of lonely people afraid to make the first move.