Back
Featured image of post HWS2022冬令营预选赛 Writeup

HWS2022冬令营预选赛 Writeup

Written by S1eepy & ZhuZi

RE

babyVM

将花指令patch掉

将patch好的应用到文件。

能看出来是个虚拟机逆向题

动态调试的过程中可以看到程序向内存里写了一段数据

0xFF,0x223,0x23B,0x237,0x237,0x24B,0x22B,0xFB,0x22B,0x223,0x24F,0xEF,0x237,0xEF,0x24F,0x24F,0x223,0x223,0x23B,0x237,0xFF,0x233,0x233,0x233,0x237,0x24B,0x233,0x24F,0x22B,0x22B,0x24B,0xEF

这个部分把上述数据每一个都减了0x63

程序获得用户的输入

比较的过程中可以发现,用户输入的长度应该是0x26

并且在后面比较的过程中,flag{}这几个字符占了6个。

再向后跟踪的过程中发现

用户的输入和0x42做了异或

再把结果左移两位,和前面的数据做了比较,因此最后的exp是:

dump = [156, 448, 472, 468, 468, 488, 456, 152, 456, 448, 492, 140, 468, 140, 492, 492, 448, 448, 472, 468, 156, 464, 464, 464, 468, 488, 464, 492, 456, 456, 488, 140]

for i in range(len(dump)):
    print(chr((dump[i]>>2)^0x42),end='')
    
print()
# e247780d029a7a992247e6667869008a

EasyVM

和上题一样,去除应该应该去除的花指令。

然后引发了一个异常

观察异常处理函数

看到输入的长度为42

在这个函数里面输入经过了base64编码,而且和0xa,0xb,0xc,0xd异或

然后根据函数地址表进行虚拟机逻辑。

动态调试中可以发现函数将输入经过特制变换的 base64 和前一个结果异或,和 0xee 异或,再进行比较,然后动态调试的过程中把最终的结果提取出来,可以写出脚本。

import base64

dump = [0x00,0xBE, 0x36, 0xAC, 0x27, 0x99, 0x4F, 0xDE, 0x44, 0xEE, 0x5F, 
  0xDA, 0x0B, 0xB5, 0x17, 0xB8, 0x68, 0xC2, 0x4E, 0x9C, 0x4A, 
  0xE1, 0x43, 0xF0, 0x22, 0x8A, 0x3B, 0x88, 0x5B, 0xE5, 0x54, 
  0xFF, 0x68, 0xD5, 0x67, 0xD4, 0x06, 0xAD, 0x0B, 0xD8, 0x50, 
  0xF9, 0x58, 0xE0, 0x6F, 0xC5, 0x4A, 0xFD, 0x2F, 0x84, 0x36, 
  0x85, 0x52, 0xFB, 0x73, 0xD7, 0x0D, 0xE3]
x = [0xa,0xb,0xc,0xd]

dump = dump[::-1]


dump1 = []
for i in range(1,len(dump)):
    dump1.append(dump[i]^dump[i-1]^0xee)


dump1 = dump1[::-1]


dump2 = []
for i in range(len(dump1)):
    dump2.append((dump1[i]^x[(i)%4]).to_bytes(1,byteorder='big'))

print (base64.b64decode(b''.join(dump2)))
# b'flag{2586dc76-98d5-44e2-ad58-d06e6559d82a}'

Crypto

babyrsa

把 n 扔到 factordb 上直接分解了出来,拿到 pq,直接解密就行

from libnum import n2s
def exgcd(a, b):
	if b == 0:
		return 1, 0, a
	else:
		x, y, q = exgcd(b, a % b)
		x, y = y, (x - (a // b) * y)
		return x, y, q

p = 98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033
q = 133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513
N = 13123058934861171416713230498081453101147538789122070079961388806126697916963123413431108069961369055630747412550900239402710827847917960870358653962948282381351741121884528399369764530446509936240262290248305226552117100584726616255292963971141510518678552679033220315246377746270515853987903184512948801397452104554589803725619076066339968999308910127885089547678968793196148780382182445270838659078189316664538631875879022325427220682805580410213245364855569367702919157881367085677283124732874621569379901272662162025780608669577546548333274766058755786449491277002349918598971841605936268030140638579388226573929
assert p * q == N
e = 2199344405076718723439776106818391416986774637417452818162477025957976213477191723664184407417234793814926418366905751689789699138123658292718951547073938244835923378103264574262319868072792187129755570696127796856136279813658923777933069924139862221947627969330450735758091555899551587605175567882253565613163972396640663959048311077691045791516671857020379334217141651855658795614761069687029140601439597978203375244243343052687488606544856116827681065414187957956049947143017305483200122033343857370223678236469887421261592930549136708160041001438350227594265714800753072939126464647703962260358930477570798420877
flag = 1492164290534197296766878830710549288168716657792979479408332026408553210558539364503279432780006256047888761718878241924947937039103166564146378209168719163067531460700424309878383312837345239570897122826051628153030129647363574035072755426112229160684859510640271933580581310029921376842631120847546030843821787623965614564745724229763999106839802052036834811357341644073138100679508864747009014415530176077648226083725813290110828240582884113726976794751006967153951269748482024859714451264220728184903144004573228365893961477199925864862018084224563883101101842275596219857205470076943493098825250412323522013524
d = exgcd(e, (p - 1) * (q - 1))[0]
print(n2s(pow(flag, d, N)))

flag:

crypto_Elgamal

首先是一个 LCG 的链式求解,通过五个值解出 A,B,q

分析 elgmal 签名,同样使用了 LCG,那么有两个加密值就能够恢复消息,推导如下

$$ c_{2'}\equiv mh^{Ar_1+B} \equiv m(h^{r_1})^Ah^B \equiv mh^{r_1}(h^{r_1})^{A-1}h^B \equiv c_2(h^{r_1})^{A-1}h^B\pmod p $$

$$ \therefore (h^{r_1})^{A-1} \equiv c_{2'}c_2^{-1}h^{-B} \pmod p $$

可以设法求出 $h^{r_1}$ 的值,然后就可以通过 $c_2=mh^{r_1}$ 求解出 m 值

但由于 $A-1$ 和 $p-1$ 不互素,无法直接求得逆元,因此需要高次同余开根算法,这里使用了 AMM 算法进行求解,按照论文直接复现出来,得到 $h^{r_1}$ 的值

最后计算 $m\equiv c_2(h^{r_1})^{-1}\pmod p$ 得到flag

LCG 求解 ABq:

from Crypto.Util.number import *
def gcd(a,b): 
    if(b==0): 
        return a 
    else: 
        return gcd(b,a%b) 
s =  [543263588863771657634119, 628899245716105951093835, 78708024695487418261582, 598971435111109998816796, 789474285039501272453373]
t = []
for i in range(5):
    t.append(s[i]-s[i-1]) 
all_n = []
for i in range(3):
    all_n.append(gcd((t[i+1]*t[i-1]-t[i]*t[i]), (t[i+2]*t[i]-t[i+1]*t[i+1]))) 

MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1] #逆元计算
for n in all_n:
    n=abs(n)
    if n==1:
        continue
    a=(s[2]-s[1])*MMI((s[1]-s[0]),n)%n
    ani=MMI(a,n)
    b=(s[1]-a*s[0])%n
    print(a, b, n)

AMM 算法代码:

import random
import math
import libnum
import time
from Crypto.Util.number import bytes_to_long,long_to_bytes
p = 0
def GF(a):
    global p
    p = a
def g(a,b):
    global p
    return pow(a,b,p)
def AMM(x,e,p):
    GF(p)
    y = random.randint(1, p-1)
    while g(y, (p-1)//e) == 1:
        y = random.randint(1, p-1)
        print(y)
    #p-1 = e^t*s
    t = 1
    s = 0
    while p % e == 0:
        t += 1
        print(t)
    s = p // (e**t)
    # s|ralpha-1
    k = 1    
    while((s * k + 1) % e != 0):
        k += 1
    alpha = (s * k + 1) // e
    
    a = g(y, (e ** (t - 1) ) * s)
    b = g(x, e * alpha - 1)
    c = g(y, s)
    h = 1
    #
    for i in range(1, t-1):
        print('cur_i',i)
        d = g(b,e**(t-1-i))
        if d == 1:
            j = 0
        else:
            j = (-math.log(d,a) % e)
        b = b * (g(g(c, e), j))
        h = h * g(c, j)
        c = g(c,e)
    return (g(x,alpha * h)) % p

解题脚本:

#coding=utf-8
from gmpy2 import *
from libnum import *
from amm import AMM
A = 12742153496769814072597
B = 3035433788765894539799
q = 791763770658839585424113
# A = 107868759310796409744994
# B = 492302818740419286543885

p = 65211247300401312530078141569304950676358489059623557848188896752173856845051471066071652073612337629832155846984721797768267868868902023383604553319793550396610085424563231688918357710337401138108050205457200940158475922063279384491022916790549837379548978141370347556053597178221402425212594060342213485311
g = 27642593390439430783453736408814717946185190497201679721975757020767271070510268596627490205095779429964809833535285315202625851326460572368018875381603399143376574281200028337681552876140857556460885848491160812604549770668188783258592940823128376128198726254875984002214053523752696104568469730021811399216
h = 54585833166051670245656045196940486576634589000609010947618047461787934106392112227019662788387352615714332234871251868259282522817042504587428441746855906297390193418159792477477443129333707197013251839952389651332058368911829464978546505729530760951698134101053626585254469108630886768357270544236516534904

c1 = 60724920570148295800083597588524297283595971970237964464679084640302395172192639331196385150232229004030419122038089044697951208850497923486467859070476427472465291810423905736825272208842988090394035980454248119048131354993356125895595138979611664707727518852984351599604226889848831071126576874892808080133
c2 = 48616294792900599931167965577794374684760165574922600262773518630884983374432147726140430372696876107933565006549344582099592376234783044818320678499613925823621554608542446585829308488452057340023780821973913517239972817669309837103043456714481646128392677624092659929248296869048674230341175765084122344264
c1_ = 42875731538109170678735196002365281622531058597803022779529275736483962610547258618168523955709341579773947887175626960699426438456382655370090748369934296474999389316334717699127421889816721511602392591677377678759026657582648354688447456509292302633971842316239774410380221303269351351929586256938787054867
c2_ = 64829024929257668640929285124747107162970460545535885047576569803424908055130477684809317765011143527867645692710091307694839524199204611328374569742391489915929451079830143261799375621377093290249652912850024319433129432676683899459510155157108727860920017105870104383111111395351496171846620163716404148070

k = (p - 1) // q
assert k * q + 1 == p
# print(q, k)

hr1_A_1 = c2_ * invert(c2, p) * pow(h,-B, p) % p
# print(hr1_A_1)
d = xgcd(A - 1, p - 1)[0] % (p - 1)

cur_x = AMM(hr1_A_1, 7438, p)
# assert pow(cur_x, A - 1, p) == hr1_A_1

d = xgcd(A - 1, p - 1)[0] % (p - 1)
x_7438 = pow(hr1_A_1, d, p)

x_3719 = AMM(x_7438, 2, p)
x_3719_ = p - x_3719

x_3719s = [x_3719, x_3719_]

mol = (p - 1) // 3719

def check(mm):
	for i in mm:
		if i >= 128:
			return False
	return True

for eve_c in x_3719s:
	cur_x = AMM(eve_c, 3719, p)
	assert pow(cur_x, A - 1, p) == hr1_A_1
	for i in range(1, 1 + 6000):
		cur_use = cur_x * pow(i, mol, p)
		assert pow(cur_use, A - 1, p) == hr1_A_1
		cur_m = xgcd(cur_use, p)[0] * c2 % p
		cur_m = n2s(int(cur_m))
		if check(cur_m):
			print(cur_m)
			break
# b'flag{19e9f185e6a680324cedd6e6d9382743}'

Misc

badPDF

文件在运行之后在 %tmp% 目录下释放了三个文件,将 js 和 tmp 都提取出来

tmp的内容好像是在混淆,查了一下应该是使用 winrm.vbs 绕过应用白名单执行任意未签名代码(https://www.anquanke.com/post/id/151711),再一找发现是原题

找个 vbs 脚本改一下

for i= 1 to len("676d60667a64333665326564333665326564333665326536653265643336656564333665327c") step 2:
	flag = flag & chr(asc(chr("&h" & mid("676d60667a64333665326564333665326564333665326536653265643336656564333665327c",i,2)))xor 1):
	next:
msgbox(flag)

运行结果:

gogogo

先解puzzle,gaps不知道为啥跑不动,手动硬拼,口令是3e8f092d4d7b80ce338d6e238efb01

随后是个内存取证,在内存剪切板上又看到了上面的口令,确定了该口令只有30位而不是个md5

在内存的文件找到了csgo.zip,用口令解密拿到图片,但显示不出来,用010editor打开图片,发现png文件头重复,删掉一个,看到正经图片,但是缺少定位符

图片是个Aztec码,找了个二层的定位符,硬拼一下,在线网站直接解(https://products.aspose.app/barcode/recognize/aztec#/recognized)

flag: