题目质量蛮不错的,学到了很多东西,RE是2道VM的题,一道花指令+ollvm混淆,也有两道Android题在Misc和Crypto中,最刺激的是距比赛结束还有两分钟时解出了RE3。

一、Misc

badPDF

拿到文件拖入010editor中,发现是4c 00开头,是win下快捷方式的标志,并且本身后缀为.link文件。

通过搜索找到一篇文章https://www.sohu.com/a/387683719_476857

得知是2020年一个病毒的弱化样本,利用LNK快捷方式伪装nCov-19疫情的恶意攻击。

大致流程是运行时会在AppData/Local/Temp目录下加载js、exe文件等用于下一次攻击。该附件的执行流程与所述病毒样本类似,关键点在于拿到WsmPty.xsl文件,查看js可知他写在了\AppData\Local\Temp\cscript.exe目录下,运行fakepdf,在xsl加载到目录下时用notepad打开,内容如下。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version='1.0'?>
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:user="placeholder"
version="1.0">
<output method="text"/>
<ms:script implements-prefix="user" language="VBScript">
<![CDATA[
rBOH7OLTCVxzkH=HrtvBsRh3gNUbe("676d60667a64333665326564333665326564333665326536653265643336656564333665327c"):execute(rBOH7OLTCVxzkH):function HrtvBsRh3gNUbe(bhhz6HalbOkrki):for rBOH7OLTCVxzkH=1 to len(bhhz6HalbOkrki)step 2:HrtvBsRh3gNUbe=HrtvBsRh3gNUbe&chr(asc(chr("&h"&mid(bhhz6HalbOkrki,rBOH7OLTCVxzkH,2)))xor 1):next:end function:
]]> </ms:script>
</stylesheet>

可见按照VBS脚本正常执行就能拿到flag串,加密很容易hex_to_str后与1异或即可。

1
2
3
4
a=bytes.fromhex('676d60667a64333665326564333665326564333665326536653265643336656564333665327c')
for i in range(len(a)):
print(chr(a[i]^1),end='')
#flag{e27d3de27d3de27d3d7d3de27dde27d3}

gogogo

考点是拼图、取证和Aztec

首先是puzzle,一共有256个分辨率为160x100,如果用gaps的话需要是正方形,所以先用convert强行转分辨率为160x160。

1
find ./ -name '*.png'  -exec convert -resize 160x160! {} {} \; 

之后montage指令将图片拼接

1
montage *png -tile 16x16 -geometry +0+0 out.png

之后直接gaps自动化拼即可 size参数选160

1
gaps --image=out.png --generations=20 --population=256 --size=160

image-20220123203055938

拿到passwd: 3e8f092d4d7b80ce338d6e238efb01

之后还有2.raw文件,因为我们有个passwd所以猜测有zip文件,直接filescan找zip

1
2
3
volatility -f 2.raw imageinfo #WinXPSP2x86
volatility -f 2.raw --profile=WinXPSP2x86 filescan | grep "zip"
volatility -f 2.raw --profile=WinXPSP2x86 dumpfiles -Q [csgo.zip偏移] -D .

能导出一个csgo.zip,并且能用puzzle得到的passwd解压,拿到二张图片(foremost分离csgo.png得到)

一个是阿兹特克的图片,一个是没有定位符的图片码,百度能确定是Aztec,那个枪名的谐音是提示,啊这。

网上生成一个标准的Aztec码,修复定位符即可,御用PDF修复,不会PS :)。

image-20220123204323061

二、Crypto

babyrsa

签到题,n能在线分解,正常rsa解密即可。

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import *
import gmpy2
n=13123058934861171416713230498081453101147538789122070079961388806126697916963123413431108069961369055630747412550900239402710827847917960870358653962948282381351741121884528399369764530446509936240262290248305226552117100584726616255292963971141510518678552679033220315246377746270515853987903184512948801397452104554589803725619076066339968999308910127885089547678968793196148780382182445270838659078189316664538631875879022325427220682805580410213245364855569367702919157881367085677283124732874621569379901272662162025780608669577546548333274766058755786449491277002349918598971841605936268030140638579388226573929
e=2199344405076718723439776106818391416986774637417452818162477025957976213477191723664184407417234793814926418366905751689789699138123658292718951547073938244835923378103264574262319868072792187129755570696127796856136279813658923777933069924139862221947627969330450735758091555899551587605175567882253565613163972396640663959048311077691045791516671857020379334217141651855658795614761069687029140601439597978203375244243343052687488606544856116827681065414187957956049947143017305483200122033343857370223678236469887421261592930549136708160041001438350227594265714800753072939126464647703962260358930477570798420877
c=1492164290534197296766878830710549288168716657792979479408332026408553210558539364503279432780006256047888761718878241924947937039103166564146378209168719163067531460700424309878383312837345239570897122826051628153030129647363574035072755426112229160684859510640271933580581310029921376842631120847546030843821787623965614564745724229763999106839802052036834811357341644073138100679508864747009014415530176077648226083725813290110828240582884113726976794751006967153951269748482024859714451264220728184903144004573228365893961477199925864862018084224563883101101842275596219857205470076943493098825250412323522013524
p=98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033
q=133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513
d=gmpy2.invert(e,(p-1)*(q-1))
print(long_to_bytes(pow(c,d,n)))
#hwctf{01d_Curs3_c4Me_Again}

crypto_Elgamal

1
2
3
4
assert ( A * s0 + B ) % q == s1
assert ( A * s1 + B ) % q == s2
assert ( A * s2 + B ) % q == s3
assert ( A * s3 + B ) % q == s4

第一段是LCG,未知参数是A、B、q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s0 = 543263588863771657634119
s1 = 628899245716105951093835
s2 = 78708024695487418261582
s3 = 598971435111109998816796
s4 = 789474285039501272453373
s=[s0,s1,s2,s3,s4]
t=[]
for i in range(len(s)-1):
t.append(s[i+1]-s[i])
print(t)

nn=[]
for i in range(1,3):
nn.append(abs(t[i+1]*t[i-1]-t[i]*t[i]))
print(nn)
q=gmpy2.gcd(nn[0],nn[1])
#791763770658839585424113

现在我们拿到(g, h, A, B, p, q),之后是ElGamal解密,可知他在生成r时也是线性同余,并且参数我们已知。

参考:2018 Code Blue lagalem

https://ctf-wiki.org/crypto/asymmetric/discrete-log/elgamal/#2018-code-blue-lagalem

1
2
3
4
5
6
tmp = gmpy2.powmod(c2, A, p) * gmpy2.powmod(h, B, p) * gmpy2.invert(c2_, p)
tmp = tmp % p
gg, x, y = gmpy2.gcdext(A - 1, p - 1)
print(gg)
m = gmpy2.powmod(tmp, x, p)
print(long_to_bytes(m))

如果是直接解出的q这里的gg则是7438,即我们求出的m 是 pow(m,7438,p)而7438是p-1的因数,所以rsa也不可解,可知在推导中如果我们取q`=k*q,那么A和B的值会变大,但是LCG的结果不会改变,而我们推导中用到了A和B的值来求t和gg,当我们改变q时,gg的值也在改变,故当gg足够小,为1时,则pow(tmp,x,p) = flag。

综上exp为:

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
import gmpy2
from Crypto.Util.number import *


s0 = 543263588863771657634119
s1 = 628899245716105951093835
s2 = 78708024695487418261582
s3 = 598971435111109998816796
s4 = 789474285039501272453373

s=[s0,s1,s2,s3,s4]
t=[]
for i in range(len(s)-1):
t.append(s[i+1]-s[i])
print(t)

nn=[]
for i in range(1,3):
nn.append(abs(t[i+1]*t[i-1]-t[i]*t[i]))
print(nn)
#[258157280800981693845036338952604472521614296785, 375487011827303788911792498742822292475328703777]
#q=gmpy2.gcd(nn[0],nn[1])
q=375487011827303788911792498742822292475328703777*3 #从nn的元素中取再数乘即可,但要保证求A时逆元存在。

A=gmpy2.invert(s1-s0,q)*(s2-s1) %q
B=(s1-A*s[0])%q


p = 65211247300401312530078141569304950676358489059623557848188896752173856845051471066071652073612337629832155846984721797768267868868902023383604553319793550396610085424563231688918357710337401138108050205457200940158475922063279384491022916790549837379548978141370347556053597178221402425212594060342213485311
g = 27642593390439430783453736408814717946185190497201679721975757020767271070510268596627490205095779429964809833535285315202625851326460572368018875381603399143376574281200028337681552876140857556460885848491160812604549770668188783258592940823128376128198726254875984002214053523752696104568469730021811399216
h = 54585833166051670245656045196940486576634589000609010947618047461787934106392112227019662788387352615714332234871251868259282522817042504587428441746855906297390193418159792477477443129333707197013251839952389651332058368911829464978546505729530760951698134101053626585254469108630886768357270544236516534904


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

tmp = gmpy2.powmod(c2, A, p) * gmpy2.powmod(h, B, p) * gmpy2.invert(c2_, p)
tmp = tmp % p
gg, x, y = gmpy2.gcdext(A - 1, p - 1)
print(gg)
m = gmpy2.powmod(tmp, x, p)
print(long_to_bytes(m))

#19e9f185e6a680324cedd6e6d9382743

三、REVERSE

EasyVM

修复花指令,patch call的第一个字节,之后观察控制流,尝试后在except异常处理块发现关键函数sub_4012F0

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
int sub_4012F0()
{
_DWORD *v0; // eax
int (__thiscall ***v1)(_DWORD, void *, void *, _DWORD, _BYTE *); // ebx
_BYTE *v2; // eax
char flag[256]; // [esp+8h] [ebp-100h] BYREF

v0 = (_DWORD *)sub_401889(0x24u);
if ( v0 )
{
*v0 = &off_40A0D0;
v0[1] = 0;
v0[2] = 0;
v0[3] = 0;
v0[4] = 0;
v0[5] = 0;
v0[6] = 0;
v0[7] = 0;
v0[8] = 0;
v1 = (int (__thiscall ***)(_DWORD, void *, void *, _DWORD, _BYTE *))v0;
}
else
{
v1 = 0;
}
scanf("%s", flag);
if ( strlen(flag) == 42 )
{
v2 = sub_4011E0(flag);
if ( (**v1)(v1, &unk_40B030, &unk_40B050, 0, v2) )
sub_4016F9((int)aCongratulation);
else
sub_4016F9((int)aUnfortunatelyI);
}
else
{
sub_4016F9((int)aVerificationFa);
}
system(Command);
return 0;
}

可知flag长度为42位,加密流程是sub_4011E0 和 v1指向的函数。

sub_4011E0是个魔改的base64,修改了表的值通过异或。

1
2
3
4
5
6
7
8
9
{
v6 += 4;
v8 = *((unsigned __int8 *)v7 - 1);
v7 += 3;
v4[v6 - 4] = aAbcdefghijklmn[v8 >> 2] ^ 0xA;
v4[v6 - 3] = aAbcdefghijklmn[(*((unsigned __int8 *)v7 - 3) >> 4) | (16 * (*(v7 - 4) & 3))] ^ 0xB;/
v4[v6 - 2] = aAbcdefghijklmn[(*((unsigned __int8 *)v7 - 2) >> 6) | (4 * (*(v7 - 3) & 0xF))] ^ 0xC;
v4[v6 - 1] = aAbcdefghijklmn[*(v7 - 2) & 0x3F] ^ 0xD;
}

v1指向的函数是虚拟机处理函数,传入的参数依次为opcode,密文和经过base64处理后的密文。

image-20220123205320141

结合动态调试可知是按字节处理第三个参数,当前直接与前一个处理后的字节异或后在异或0xee在和密文比较(第一个字节异或0异或0xee)

综上编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64
enc=[0,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]
m=[]
for i in range(56,0,-1):
m.append(enc[i]^enc[i-1]^0xee)
print(bytes(m)[::-1])

a=bytes(m)[::-1]
s=''
key=[0xa,0xb,0xc,0xd]
for i in range(len(a)):
s+=chr(a[i]^key[i%4])
print(base64.b64decode(s.encode()))
#flag{2586dc76-98d5-44e2-ad58-d06e6559d82a}

babyre

开局多种花指令,简单的跳转直接nop即可,有两种会影响反编译并且比较有趣。

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:00401838                 push    eax
.text:00401839 mov eax, 7Fh
.text:0040183E test eax, eax
.text:00401840 jz short loc_401847
.text:00401842 call near ptr loc_401847+1
.text:00401847
.text:00401847 loc_401847: ; CODE XREF: .text:00401840↑j
.text:00401847 ; .text:00401842↑p
.text:00401847 call fword ptr [eax+58h]
//nop一个字节后
.text:00401848 loc_401848: ; CODE XREF: .text:00401842↑p
.text:00401848 pop eax
.text:00401849 pop eax

第一种分析知eax被赋0x7f后jz跳转恒不成立,故通过call来跳转,会压入地址进栈,nop下面call的第一个字节后出现pop,其实是恢复eax值,所以从push eax 到pop eax全nop掉即可。

第二种更复杂一点,不过也有push和pop恢复现场,全nop掉即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00401A28                 push    eax
.text:00401A29 push ecx
.text:00401A2A call sub_4010A0
.text:00401A2F add eax, 15h
.text:00401A32 push eax
.text:00401A33 pop eax
.text:00401A34 mov ecx, 0FFFFFFFFh
.text:00401A39 xor eax, ecx
.text:00401A3B push eax
.text:00401A3C pop ecx
.text:00401A3D not ecx
.text:00401A3F push ecx
.text:00401A40 retn
.text:00401A40 ; ---------------------------------------------------------------------------
.text:00401A41 db 7Eh, 0FFh, 15h
.text:00401A44 ; ---------------------------------------------------------------------------
.text:00401A44 pop ecx
.text:00401A45 pop eax

简易版IPYTHON脚本手动去除,生成patch文件。

1
2
3
4
5
adr1=0x00401A28
adr2=0x00401A46
for ad in range(adr1,adr2):
PatchByte(ad,0x90)
print('yes')

之后查看控制流,发现ollvm混淆的特征,起初想找个自动化的脚本,可惜没找到!

https://www.52pojie.cn/thread-1488350-1-1.html

基本思路是,每个真实块的首地址下断点,运行并记录来还原,因为我们主要关注输入的变化所以,在所有输入的地方下断点,之后调试,多次尝试后执行如下。

第一块

1
2
3
//v31的初值为0
v31 = ~*(_DWORD *)&input_str[4 * v30] & v31 | ~v31 & *(_DWORD *)&input_str[4 * v30];
v32 = ~*(_DWORD *)&input_str[4 * v30]; // v30表示下标

该块为与或非来实现异或,v31依次与所有输入异或(4byte一组)。

第二块

1
2
*(_DWORD *)&input_str[4 * v29] = ~*(_DWORD *)&input_str[4 * v29] & v31 | ~v31 & *(_DWORD *)&input_str[4 * v29];
v32 = v29;//下标

该块为输入4byte一组依次与v31异或。

第三块

1
input_str[32] = (input_str[v28] & 0x98 | ~input_str[v28] & 0x67) ^ (input_str[32] & 0x98 | ~input_str[32] & 0x67);

把进过异或后的所有字节异或到一起填充为33位,用于接下来的base64运算。

第四块

1
2
3
4
5
6
v15 = v24 & 0xF4 | ~v24 & 0xF0C4020B;
input_str[v20] = v15 ^ (input_str[v20] & 0xF4 | ~input_str[v20] & 0xB);
input_str[v20 + 1] = ~v23 & input_str[v20 + 1] | ~input_str[v20 + 1] & v23;
LOBYTE(v15) = ~v22 & *(_BYTE *)(v20 + 10825322) | ~*(_BYTE *)(v20 + 10825322) & v22;
*(_BYTE *)(v20 + 10825322) = v15;
v32 = v15;

这一块比较复杂,结合动态调试,观察输入的变化,发现是3个一组与3个数异或,而异或的三个数是前一个数据base64加密时转化为的前3个下标。

在执行时这个异或是从当前开始,之后所有三个一组与该组转化的key异或,第一组不做处理。

第五块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
v24 = (int)input_str[v25] >> 2;         // a[0]>>2
v10 = ~(16 * ~(~input_str[v25] | 0xFC));// (a[0]&0x3 << 4)
v11 = ~((int)input_str[v25 + 1] >> 4); // a[1]>>4
v23 = ~(v11 | v10) | (((int)input_str[v25 + 1] >> 4) & 0x24 | v11 & 0xDB) ^ ((16 * ~(~input_str[v25] | 0xFC)) & 0x24 | v10 & 0xDB);
LOBYTE(v11) = ~(4 * (input_str[v25 + 1] & (input_str[v25 + 1] ^ 0xF0)));
v12 = ~((int)input_str[v25 + 2] >> 6);
v22 = ~(v12 | v11) | (((int)input_str[v25 + 2] >> 6) & 0x11 | v12 & 0xEE) ^ ((4* (input_str[v25 + 1] & (input_str[v25 + 1] ^ 0xF0))) & 0x11 | v11 & 0xEE);
v21 = input_str[v25 + 2] & (input_str[v25 + 2] ^ 0xC0);
v32 = 0x7F;
Str1[v26] = *(_BYTE *)sub_A22210(&off_A52E38, v24);
Str1[v26 + 1] = *(_BYTE *)sub_A22210(&off_A52E38, v23);
Str1[v26 + 2] = *(_BYTE *)sub_A22210(&off_A52E38, v22);
v13 = v26 + 3;
v26 += 4;
Str1[v13] = *(_BYTE *)sub_A22210(&off_A52E38, v21);
v20 = v25 + 3;
v19 = 0xC90DC21D;

base64加密,动调测试得知3byte变为4byte的每组6bit的顺序没有变,只不过用与或非实现起来比较难读,这一步和第四块是交替进行的,每进行一次第五块再进行第四块,base64的表需要动调获得。

通过&off_A52E38间接寻址获得。

image-20220124231204350

耐心调试,了解块的执行顺序后梳理解密逻辑,先是base64换表,之后逆第四块的异或,再之后需要拿到v31,假设我们的base解密后的为 c d 即 a^v31 b^v31 而 v31是由a^b得到所以 c^d == a^d 其中异或的v31抵消了,因此异或的4byte的key可由密文4个一组异或得到,之后再进行解密。

完整exp如下:

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
import base64
s='Fi9X/fxX6Q6JBfUfBM1V/y6V6PcPjMaQLl9IuttFuH68'
t1='QVEJAfHmUYjBac+u8Ph5n9Od16FrICL/X0GvtM4qk7T2z3wNSsyoebilxWKgZpRD'
t2='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
c=''
for i in range(len(s)):
c+=t2[t1.index(s[i])]
cc=list(base64.b64decode(c.encode())) #base换表

t=[]
for i in range(0,len(cc),3): #分成3个一组
t.append(cc[i:i+3])
print(t)
new=[t[0]]

key=[]
for i in range(1,len(t)):
tmp=[0,0,0]
a=base64.b64encode(bytes(t[i-1])).decode()
k=[]
for j in range(3):
k.append(t2.find(a[j]))
key.append(k)

#print(key) 还原base64所用key

for i in range(len(t)):
for j in range(i):
k=key[j]
for p in range(3):
t[i][p]^=k[p]
print(t) #你迭代的base

m=[]
for i in t:
m.extend(i)
#print(m) #base逆向后的明文
print(bytes(m))
mm=[0,0,0,0]
def xor(a,b):
for i in range(len(a)):
a[i]^=b[i]

for i in range(0,len(m)-1,4): #需要异或v31 而 v31是由 明文4个一组异或而来
xor(mm,m[i:i+4])
for i in range(len(m)):
print(chr(mm[i%4]^m[i]),end='')
#fce5e3dfc6db4f808ccaa6fcffecf583P

re1理解错了,之前也没有编写解释器的经验,后续会复现补上。

同时赛中花大量时间去搞Android了,可惜最终没能调动起so文件,可能是模拟器有点问题把,一直attach目标程序不上,但最终收获也是蛮大的。

这次写解密脚本失误有点大,差点re3就废了,看m师傅用z3解的,确实又快又准,自己还要好好加油哦img