You might be wondering what an examination of memory structures in Linux has to do with network security. I believe that you cannot twist a system to do what you want unless you know how it works. If we want to look at memory in Linux to discover useful artifacts (like credit card numbers, social security numbers, whatever), then we have to be familiar with how one could read the memory of an arbitrary process.
Remember the everything on a *nix system is represented by a file: the files on your hard drive, your printer, and even your memory. The keyword is represented. The kernel creates a number of virtual filesystems to abstract lower level parts of your system so that software that needs to interface with various parts of the system has a standard way to do so. These virtual filesystems allow the software to target a device or aspect of the system without worrying about changes in the underlying implementation. For example, /dev is used to interface with a given hardware device's driver as if it were a file. If a piece of software needs to interface with /dev/sda (the first SATA hard disk in the system), it does not need to worry about differences in SATA drivers across systems. It asks for /dev/sda, and the kernel worries about the specific implementation of the operation the software is trying to do.
That is great for devices, but what about memory? You can access system memory through the /proc filesystem. You might see this referred to as procfs.
user@localhost:~$ ls -al /proc
total 4
dr-xr-xr-x 94 root root 0 Sep 7 10:21 .
drwxr-xr-x 22 root root 4096 Sep 2 15:20 ..
dr-xr-xr-x 9 root root 0 Sep 7 10:21 1
dr-xr-xr-x 9 root root 0 Sep 7 10:21 10
dr-xr-xr-x 9 root root 0 Sep 7 10:21 11
dr-xr-xr-x 9 root root 0 Sep 7 10:21 12
dr-xr-xr-x 9 root root 0 Sep 7 10:21 13
dr-xr-xr-x 9 root root 0 Sep 7 10:21 133
dr-xr-xr-x 9 root root 0 Sep 7 10:21 134
dr-xr-xr-x 9 root root 0 Sep 7 10:21 135
dr-xr-xr-x 9 root root 0 Sep 7 10:21 138
dr-xr-xr-x 9 root root 0 Sep 7 10:21 139
dr-xr-xr-x 9 root root 0 Sep 7 10:21 14
dr-xr-xr-x 9 root root 0 Sep 7 10:21 146
dr-xr-xr-x 9 root root 0 Sep 7 10:21 147
dr-xr-xr-x 9 root root 0 Sep 7 10:21 149
dr-xr-xr-x 9 root root 0 Sep 7 10:21 15
dr-xr-xr-x 9 root root 0 Sep 7 10:21 150
dr-xr-xr-x 9 root root 0 Sep 7 10:21 163
dr-xr-xr-x 9 root root 0 Sep 7 10:21 164
dr-xr-xr-x 9 root root 0 Sep 7 10:21 17
dr-xr-xr-x 9 root root 0 Sep 7 10:21 18
dr-xr-xr-x 9 root root 0 Sep 7 10:21 19
dr-xr-xr-x 9 root root 0 Sep 7 10:21 2
dr-xr-xr-x 9 root root 0 Sep 7 10:21 20
dr-xr-xr-x 9 root root 0 Sep 7 10:21 208
dr-xr-xr-x 9 root root 0 Sep 7 10:21 21
dr-xr-xr-x 9 root root 0 Sep 7 10:21 22
dr-xr-xr-x 9 root root 0 Sep 7 10:21 229
dr-xr-xr-x 9 root root 0 Sep 7 10:21 23
dr-xr-xr-x 9 root root 0 Sep 7 10:21 24
dr-xr-xr-x 9 root root 0 Sep 7 10:21 240
dr-xr-xr-x 9 root root 0 Sep 7 10:21 25
dr-xr-xr-x 9 root root 0 Sep 12 06:35 2583
dr-xr-xr-x 9 root root 0 Sep 7 10:21 26
dr-xr-xr-x 9 root root 0 Sep 7 10:21 264
dr-xr-xr-x 9 root root 0 Sep 7 10:21 27
dr-xr-xr-x 9 root root 0 Sep 7 10:21 278
dr-xr-xr-x 9 root root 0 Sep 7 10:21 28
dr-xr-xr-x 9 root root 0 Sep 7 10:21 29
dr-xr-xr-x 9 root root 0 Sep 7 10:21 3
dr-xr-xr-x 9 root root 0 Sep 7 10:21 30
dr-xr-xr-x 9 root root 0 Sep 7 10:21 31
dr-xr-xr-x 9 root root 0 Sep 7 10:21 32
dr-xr-xr-x 9 root root 0 Sep 7 10:21 33
dr-xr-xr-x 9 root root 0 Sep 7 10:21 331
dr-xr-xr-x 9 systemd-timesync systemd-timesync 0 Sep 7 10:21 378
dr-xr-xr-x 9 root root 0 Sep 7 10:21 38
dr-xr-xr-x 9 root root 0 Sep 7 10:21 39
dr-xr-xr-x 9 root root 0 Sep 7 10:21 40
dr-xr-xr-x 9 root root 0 Sep 7 10:21 5
dr-xr-xr-x 9 root root 0 Sep 7 10:21 52
dr-xr-xr-x 9 root root 0 Sep 7 10:21 53
dr-xr-xr-x 9 root root 0 Sep 20 06:47 5312
dr-xr-xr-x 9 root root 0 Sep 20 06:47 5361
dr-xr-xr-x 9 root root 0 Sep 7 10:21 54
dr-xr-xr-x 9 root root 0 Sep 7 10:21 549
dr-xr-xr-x 9 root root 0 Sep 7 10:21 55
dr-xr-xr-x 9 daemon daemon 0 Sep 7 10:21 552
dr-xr-xr-x 9 root root 0 Sep 7 10:21 56
dr-xr-xr-x 9 root root 0 Sep 7 10:21 562
dr-xr-xr-x 9 root root 0 Sep 7 10:21 563
dr-xr-xr-x 9 root root 0 Sep 21 06:41 5650
dr-xr-xr-x 9 syslog syslog 0 Sep 7 10:21 567
dr-xr-xr-x 9 root root 0 Sep 7 10:21 57
dr-xr-xr-x 9 root root 0 Sep 7 10:21 570
dr-xr-xr-x 9 root root 0 Sep 7 10:21 581
dr-xr-xr-x 9 root root 0 Sep 21 17:47 5949
dr-xr-xr-x 9 root root 0 Sep 21 17:47 5979
dr-xr-xr-x 9 root root 0 Sep 21 17:47 5983
dr-xr-xr-x 9 root root 0 Sep 21 17:47 5984
dr-xr-xr-x 9 user user 0 Sep 21 17:47 5986
dr-xr-xr-x 9 user user 0 Sep 21 17:47 5987
dr-xr-xr-x 9 user user 0 Sep 21 17:47 6084
dr-xr-xr-x 9 user user 0 Sep 21 17:47 6088
dr-xr-xr-x 9 user user 0 Sep 21 17:48 6110
dr-xr-xr-x 9 root root 0 Sep 7 10:21 62
dr-xr-xr-x 9 messagebus messagebus 0 Sep 7 10:21 621
dr-xr-xr-x 9 root root 0 Sep 7 10:21 636
dr-xr-xr-x 9 root root 0 Sep 7 10:21 7
dr-xr-xr-x 9 root root 0 Sep 7 10:21 8
dr-xr-xr-x 9 root root 0 Sep 7 10:21 82
dr-xr-xr-x 9 root root 0 Sep 7 10:21 83
dr-xr-xr-x 9 root root 0 Sep 7 10:21 9
dr-xr-xr-x 2 root root 0 Sep 21 17:48 acpi
-r--r--r-- 1 root root 0 Sep 21 17:48 buddyinfo
dr-xr-xr-x 4 root root 0 Sep 21 17:48 bus
-r--r--r-- 1 root root 0 Sep 21 17:48 cgroups
-r--r--r-- 1 root root 0 Sep 21 17:48 cmdline
-r--r--r-- 1 root root 0 Sep 21 17:48 consoles
-r--r--r-- 1 root root 0 Sep 21 17:48 cpuinfo
-r--r--r-- 1 root root 0 Sep 21 17:48 crypto
-r--r--r-- 1 root root 0 Sep 21 17:48 devices
-r--r--r-- 1 root root 0 Sep 21 17:48 diskstats
-r--r--r-- 1 root root 0 Sep 21 17:48 dma
dr-xr-xr-x 2 root root 0 Sep 21 17:48 driver
-r--r--r-- 1 root root 0 Sep 21 17:48 execdomains
-r--r--r-- 1 root root 0 Sep 21 17:48 fb
-r--r--r-- 1 root root 0 Sep 21 17:48 filesystems
dr-xr-xr-x 5 root root 0 Sep 21 17:48 fs
-r--r--r-- 1 root root 0 Sep 21 17:48 interrupts
-r--r--r-- 1 root root 0 Sep 21 17:48 iomem
-r--r--r-- 1 root root 0 Sep 21 17:48 ioports
dr-xr-xr-x 55 root root 0 Sep 21 17:48 irq
-r--r--r-- 1 root root 0 Sep 21 17:48 kallsyms
-r-------- 1 root root 140737477877760 Sep 21 17:48 kcore
-r--r--r-- 1 root root 0 Sep 21 17:48 keys
-r--r--r-- 1 root root 0 Sep 21 17:48 key-users
-r-------- 1 root root 0 Sep 7 10:21 kmsg
-r-------- 1 root root 0 Sep 21 17:48 kpagecount
-r-------- 1 root root 0 Sep 21 17:48 kpageflags
-r--r--r-- 1 root root 0 Sep 21 17:48 loadavg
-r--r--r-- 1 root root 0 Sep 21 17:48 locks
-r--r--r-- 1 root root 0 Sep 21 17:48 mdstat
-r--r--r-- 1 root root 0 Sep 21 17:48 meminfo
-r--r--r-- 1 root root 0 Sep 21 17:48 misc
-r--r--r-- 1 root root 0 Sep 21 17:48 modules
lrwxrwxrwx 1 root root 11 Sep 21 17:48 mounts -> self/mounts
dr-xr-xr-x 3 root root 0 Sep 21 17:48 mpt
-rw-r--r-- 1 root root 0 Sep 21 17:48 mtrr
lrwxrwxrwx 1 root root 8 Sep 21 17:48 net -> self/net
-r--r--r-- 1 root root 0 Sep 21 17:48 pagetypeinfo
-r--r--r-- 1 root root 0 Sep 21 17:48 partitions
-r--r--r-- 1 root root 0 Sep 21 17:48 sched_debug
-r--r--r-- 1 root root 0 Sep 21 17:48 schedstat
dr-xr-xr-x 4 root root 0 Sep 21 17:48 scsi
lrwxrwxrwx 1 root root 0 Sep 7 10:21 self -> 6110
-r-------- 1 root root 0 Sep 21 17:48 slabinfo
-r--r--r-- 1 root root 0 Sep 21 17:48 softirqs
-r--r--r-- 1 root root 0 Sep 21 17:48 stat
-r--r--r-- 1 root root 0 Sep 7 10:21 swaps
dr-xr-xr-x 1 root root 0 Sep 7 10:21 sys
--w------- 1 root root 0 Sep 21 17:48 sysrq-trigger
dr-xr-xr-x 2 root root 0 Sep 21 17:48 sysvipc
lrwxrwxrwx 1 root root 0 Sep 7 10:21 thread-self -> 6110/task/6110
-r--r--r-- 1 root root 0 Sep 21 17:48 timer_list
-rw-r--r-- 1 root root 0 Sep 21 17:48 timer_stats
dr-xr-xr-x 4 root root 0 Sep 21 17:48 tty
-r--r--r-- 1 root root 0 Sep 21 17:48 uptime
-r--r--r-- 1 root root 0 Sep 21 17:48 version
-r--r--r-- 1 root root 0 Sep 21 17:48 version_signature
-r-------- 1 root root 0 Sep 21 17:48 vmallocinfo
-r--r--r-- 1 root root 0 Sep 21 17:48 vmstat
-r--r--r-- 1 root root 0 Sep 21 17:48 zoneinfo
There are a lot of entries in there, but let's talk about some of the highlights:
- /proc/<a number>: Each of the procfs entries that are just numbers each represent the process memory of that PID (process identifier). We will talk about this more in depth later.
- /proc/mounts: This is a list of the filesystems mounted on the system. The command 'mount' with no arguments is essentially the same as 'cat /proc/mounts'
- /proc/kcore: You might be looking at this one and wonder why it is "taking up" 140,737,477,877,760 bytes (roughly 128TiB). kcore is not taking up that much space. Rather, it is a representation of the total amount of virtual memory that the kernel can allocate. This system is a 64-bit system, so in theory, it could address 2^64 bytes of memory, but modern 64-bit x86 CPUs only use the lower 48 bits of a memory address (see the AMD docs on x86-64 here on page 168 of the PDF, 120 of the document). In practice, that gives us 2^47 bytes we can address. 2^47 bytes is roughly 128TiB. I was curious about this myself, so I looked it up.
- /proc/cpuinfo: cat /proc/cpuinfo will tell you all kinds of info about your CPU including cache size, model, speed, and capabilities (like SSE, AES-NI, and others).
user@localhost:~$ ps -aef | grep nano
user 6157 6088 0 18:20 pts/0 00:00:00 nano -w
user 6626 6207 0 18:31 pts/1 00:00:00 grep --color=auto nano
So our nano process has a PID of 6157 (the 6088 PID is the PID of the parent process - the process that our nano process spawned from). Let's see what is in the procfs for PID 6157:
user@localhost:~$ ls -al /proc/6157
total 0
dr-xr-xr-x 9 user user 0 Sep 21 18:21 .
dr-xr-xr-x 98 root root 0 Sep 7 10:21 ..
dr-xr-xr-x 2 user user 0 Sep 21 18:21 attr
-rw-r--r-- 1 user user 0 Sep 21 18:21 autogroup
-r-------- 1 user user 0 Sep 21 18:21 auxv
-r--r--r-- 1 user user 0 Sep 21 18:21 cgroup
--w------- 1 user user 0 Sep 21 18:21 clear_refs
-r--r--r-- 1 user user 0 Sep 21 18:21 cmdline
-rw-r--r-- 1 user user 0 Sep 21 18:21 comm
-rw-r--r-- 1 user user 0 Sep 21 18:21 coredump_filter
-r--r--r-- 1 user user 0 Sep 21 18:21 cpuset
lrwxrwxrwx 1 user user 0 Sep 21 18:21 cwd -> /home/user
-r-------- 1 user user 0 Sep 21 18:21 environ
lrwxrwxrwx 1 user user 0 Sep 21 18:21 exe -> /bin/nano
dr-x------ 2 user user 0 Sep 21 18:21 fd
dr-x------ 2 user user 0 Sep 21 18:21 fdinfo
-rw-r--r-- 1 user user 0 Sep 21 18:21 gid_map
-r-------- 1 user user 0 Sep 21 18:21 io
-r--r--r-- 1 user user 0 Sep 21 18:21 limits
-rw-r--r-- 1 user user 0 Sep 21 18:21 loginuid
dr-x------ 2 user user 0 Sep 21 18:21 map_files
-r--r--r-- 1 user user 0 Sep 21 18:21 maps
-rw------- 1 user user 0 Sep 21 18:21 mem
-r--r--r-- 1 user user 0 Sep 21 18:21 mountinfo
-r--r--r-- 1 user user 0 Sep 21 18:21 mounts
-r-------- 1 user user 0 Sep 21 18:21 mountstats
dr-xr-xr-x 6 user user 0 Sep 21 18:21 net
dr-x--x--x 2 user user 0 Sep 21 18:21 ns
-r--r--r-- 1 user user 0 Sep 21 18:21 numa_maps
-rw-r--r-- 1 user user 0 Sep 21 18:21 oom_adj
-r--r--r-- 1 user user 0 Sep 21 18:21 oom_score
-rw-r--r-- 1 user user 0 Sep 21 18:21 oom_score_adj
-r-------- 1 user user 0 Sep 21 18:21 pagemap
-r-------- 1 user user 0 Sep 21 18:21 personality
-rw-r--r-- 1 user user 0 Sep 21 18:21 projid_map
lrwxrwxrwx 1 user user 0 Sep 21 18:21 root -> /
-rw-r--r-- 1 user user 0 Sep 21 18:21 sched
-r--r--r-- 1 user user 0 Sep 21 18:21 schedstat
-r--r--r-- 1 user user 0 Sep 21 18:21 sessionid
-rw-r--r-- 1 user user 0 Sep 21 18:21 setgroups
-r--r--r-- 1 user user 0 Sep 21 18:21 smaps
-r-------- 1 user user 0 Sep 21 18:21 stack
-r--r--r-- 1 user user 0 Sep 21 18:21 stat
-r--r--r-- 1 user user 0 Sep 21 18:21 statm
-r--r--r-- 1 user user 0 Sep 21 18:21 status
-r-------- 1 user user 0 Sep 21 18:21 syscall
dr-xr-xr-x 3 user user 0 Sep 21 18:21 task
-r--r--r-- 1 user user 0 Sep 21 18:21 timers
-rw-r--r-- 1 user user 0 Sep 21 18:21 uid_map
-r--r--r-- 1 user user 0 Sep 21 18:21 wchan
There are lots of interesting things in here. For example, we can see a symlink to the current working directory (cwd), the executable itself (exe), and the command line arguments used to launch the program (cmdline). They are all zero bytes, because each of these "files" live in memory. If we cat out cmdline for example, we see nano-w which matches what we saw in the ps output. For examining the actual memory of the process, we will focus on mem and maps. If you want to see what each of these means, there is a good article here. Mem is the memory that this process is using, and maps describes the libraries and various memory sections used by the process. We cannot cat the memory out directly (there would be a lot of bytes that we might not care about), but let's take a look at the maps:
user@localhost:~$ cat /proc/6157/maps
00400000-0042e000 r-xp 00000000 fc:00 659332 /bin/nano
0062d000-0062e000 r--p 0002d000 fc:00 659332 /bin/nano
0062e000-0062f000 rw-p 0002e000 fc:00 659332 /bin/nano
0148e000-01580000 rw-p 00000000 00:00 0 [heap]
7f09babc3000-7f09bae8c000 r--p 00000000 fc:00 137021 /usr/lib/locale/locale-archive
7f09bae8c000-7f09bae8f000 r-xp 00000000 fc:00 786954 /lib/x86_64-linux-gnu/libdl-2.21.so
7f09bae8f000-7f09bb08e000 ---p 00003000 fc:00 786954 /lib/x86_64-linux-gnu/libdl-2.21.so
7f09bb08e000-7f09bb08f000 r--p 00002000 fc:00 786954 /lib/x86_64-linux-gnu/libdl-2.21.so
7f09bb08f000-7f09bb090000 rw-p 00003000 fc:00 786954 /lib/x86_64-linux-gnu/libdl-2.21.so
7f09bb090000-7f09bb250000 r-xp 00000000 fc:00 786942 /lib/x86_64-linux-gnu/libc-2.21.so
7f09bb250000-7f09bb450000 ---p 001c0000 fc:00 786942 /lib/x86_64-linux-gnu/libc-2.21.so
7f09bb450000-7f09bb454000 r--p 001c0000 fc:00 786942 /lib/x86_64-linux-gnu/libc-2.21.so
7f09bb454000-7f09bb456000 rw-p 001c4000 fc:00 786942 /lib/x86_64-linux-gnu/libc-2.21.so
7f09bb456000-7f09bb45a000 rw-p 00000000 00:00 0
7f09bb45a000-7f09bb47f000 r-xp 00000000 fc:00 787045 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f09bb47f000-7f09bb67e000 ---p 00025000 fc:00 787045 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f09bb67e000-7f09bb682000 r--p 00024000 fc:00 787045 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f09bb682000-7f09bb683000 rw-p 00028000 fc:00 787045 /lib/x86_64-linux-gnu/libtinfo.so.5.9
7f09bb683000-7f09bb6b6000 r-xp 00000000 fc:00 786991 /lib/x86_64-linux-gnu/libncursesw.so.5.9
7f09bb6b6000-7f09bb8b6000 ---p 00033000 fc:00 786991 /lib/x86_64-linux-gnu/libncursesw.so.5.9
7f09bb8b6000-7f09bb8b7000 r--p 00033000 fc:00 786991 /lib/x86_64-linux-gnu/libncursesw.so.5.9
7f09bb8b7000-7f09bb8b8000 rw-p 00034000 fc:00 786991 /lib/x86_64-linux-gnu/libncursesw.so.5.9
7f09bb8b8000-7f09bb8dc000 r-xp 00000000 fc:00 786918 /lib/x86_64-linux-gnu/ld-2.21.so
7f09bbac8000-7f09bbacf000 r--s 00000000 fc:00 133547 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
7f09bbacf000-7f09bbad3000 rw-p 00000000 00:00 0
7f09bbad9000-7f09bbadb000 rw-p 00000000 00:00 0
7f09bbadb000-7f09bbadc000 r--p 00023000 fc:00 786918 /lib/x86_64-linux-gnu/ld-2.21.so
7f09bbadc000-7f09bbadd000 rw-p 00024000 fc:00 786918 /lib/x86_64-linux-gnu/ld-2.21.so
7f09bbadd000-7f09bbade000 rw-p 00000000 00:00 0
7ffc68c28000-7ffc68c49000 rw-p 00000000 00:00 0 [stack]
7ffc68d66000-7ffc68d68000 r--p 00000000 00:00 0 [vvar]
7ffc68d68000-7ffc68d6a000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
As we expected, we can see the various libraries that nano loads (like libc and libncurses). We can also see where the stack and heap live. If we have typed anything into the nano session, we will see it in the heap. This is because nano cannot pre-allocate space for a free-form buffer (like the one needed to capture arbitrary text from the user), so it uses the heap to dynamically allocate memory.
Let's take a look at one of the lines and figure out what it means:
0148e000-01580000 rw-p 00000000 00:00 0 [heap]
The first section (0148e000-01580000) contains the start and end of the memory segment. The second column (rw-p in this case) contains the permissions on that segment of memory. These are similar to filesystem permissions in *nix. The permissions in this case mean the memory is readable, writable, and private (meaning that only this process can access it). The final permission is executable (x). Notice that in the maps above, writable and executable are not enabled together on any of the mapped regions. This is to help prevent attacks like buffer overflows. In short, buffer overflows take advantage of a vulnerability where an attack overwrites the return address of a function with the address to their shellcode. If that shellcode is in a space in memory that is marked as executable, the shellcode will be executed and the attacker gains control over the execution of the program. The third field is the offset which tell us the offset in the file (the rightmost column) that is loaded into memory. The fourth field is the device. It is broken into two parts: the major and minor device field, respectively. This is only applicable for files. The fifth column is the inode on the disk of the file. The final field is the path to the file loaded into memory or special regions (like heap and stack). More info is available in the man page for proc (man proc).
Now that we know how the memory in the process is laid out, we should be able to target specific artifacts in memory as long as we can generalize them. For example, we can generalize what a credit card number looks like (a 16 digit number in a specific format) or an SSN (9 numeric digits, in groups of 3 digits, 2 digits, and 4 digits).
Before we dive into that, we have to be aware of something. In some distros, protections in the kernel have been enabled by default that do not allow one process (call it A) to access the memory of another process (call it B). The only exception is if A called B (in other words, A has to be the parent of B). This allows for debugging, but prevents a process from being able to attach to any other process to read its memory. This is true even if A and B are owned by the same user. This is different than how things work in Windows. In Windows, a process running as a user can access the memory of another process running as that user. That is what allows the tool I linked to in the beginning of the post to work. This can be dangerous because if your browser gets exploited, it could read the memory of another process where you might have sensitive info.
You can check if your kernel is enforcing this rule by checking the contents of /proc/sys/kernel/yama/ptrace_scope. Ptrace is a system call on *nix systems that allows a process to control another (which is why it is useful for debugging). You can use cat to check this. If it is 1, then your kernel is enforcing this rule. There are two ways to get around this. Run the process that is examining memory as root, or disable the rule (set ptrace_scope to 0). We will run our process as root which would make this a bit harder to weaponize, because you would have to get root in order to make our script work. If the value of ptrace_scope is 2, that means that only users with administrative rights can invoke ptrace. If the value of ptrace_scope is 3, attaching to processes using ptrace is completely disabled. Get more info here.
For our purposes, we will be looking at dynamically allocated sections of memory (the heap and the stack). From my testing, it appears that you do not need to use ptrace to examine those sections of memory. You just need to be root when doing it (even for your own processes) and you need to know where to look. Luckily, the memory maps we discussed above do just that.
Next time we will see if we can find some interesting things in memory with what we talked about in this post. What are your thoughts so far? Please let me know. Thanks for reading!
No comments:
Post a Comment