Skip to content

Commit

Permalink
Merge pull request #95 from behouba/memparse
Browse files Browse the repository at this point in the history
feat: add `memparse` sub-command
  • Loading branch information
rst0git authored Aug 9, 2023
2 parents 23dc8ac + d9e5742 commit bd9d1a9
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 26 deletions.
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Details on how to create checkpoints with the help of [CRIU][criu] can be found

## Usage

### `show` sub-command

To display an overview of a checkpoint archive you can just use
`checkpointctl show`:

Expand All @@ -41,6 +43,8 @@ $ checkpointctl show /var/lib/kubelet/checkpoints/checkpoint-counters_default-co
+-----------+------------------------------------+--------------+---------+--------------------------------+--------+------------+------------+
```

### `inspect` sub-command

To retrieve low-level information about a container checkpoint, use the `checkpointctl inspect` command:

```console
Expand All @@ -64,6 +68,91 @@ awesome_booth

For a complete list of flags supported, use `checkpointctl inspect --help`.

### `memparse` sub-command

To perform memory analysis of container checkpoints, you can use the `checkpointctl memparse` command.

```console
$ checkpointctl memparse /tmp/jira.tar.gz --pid=1 | less

Displaying memory pages content for Process ID 1 from checkpoint: /tmp/jira.tar.gz

Address Hexadecimal ASCII
-------------------------------------------------------------------------------------
00005633bb080000 f3 0f 1e fa 48 83 ec 08 48 8b 05 d1 4f 00 00 48 |....H...H...O..H|
00005633bb080010 85 c0 74 02 ff d0 48 83 c4 08 c3 00 00 00 00 00 |..t...H.........|
00005633bb080020 ff 35 b2 4e 00 00 f2 ff 25 b3 4e 00 00 0f 1f 00 |.5.N....%.N.....|
00005633bb080030 f3 0f 1e fa 68 00 00 00 00 f2 e9 e1 ff ff ff 90 |....h...........|
*
00005633bb0800a0 f3 0f 1e fa 68 07 00 00 00 f2 e9 71 ff ff ff 90 |....h......q....|
00005633bb0800b0 f3 0f 1e fa 68 08 00 00 00 f2 e9 61 ff ff ff 90 |....h......a....|
00005633bb0800c0 f3 0f 1e fa 68 09 00 00 00 f2 e9 51 ff ff ff 90 |....h......Q....|
00005633bb0800d0 f3 0f 1e fa 68 0a 00 00 00 f2 e9 41 ff ff ff 90 |....h......A....|
00005633bb0800e0 f3 0f 1e fa 68 0b 00 00 00 f2 e9 31 ff ff ff 90 |....h......1....|
```

Here's an example of memory analysis of a PostgreSQL container. In this case, we start a PostgreSQL container with a password set to 'mysecret'. Then, we create a checkpoint of the container and use the `memparse` to find the stored password.

```console
$ sudo podman run --name postgres -e POSTGRES_PASSWORD=mysecret -d postgres
$ sudo podman container checkpoint -l --export=/tmp/postgres.tar.gz
$ sudo checkpointctl memparse --pid 1 /tmp/postgres.tar.gz | grep -B 1 -A 1 mysecret
000055dd725c1e60 50 4f 53 54 47 52 45 53 5f 50 41 53 53 57 4f 52 |POSTGRES_PASSWOR|
000055dd725c1e70 44 3d 6d 79 73 65 63 72 65 74 00 00 00 00 00 00 |D=mysecret......|
000055dd725c1e80 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 |........1.......|
```

Here's another scenario, of memory analysis for a web application container. We start a vulnerable web application container, perform an arbitrary code execution attack, create a checkpoint for forensic analysis while leaving the container running, and finally analyze the checkpoint memory to identify the injected code.

```console
# Start vulnerable web application
$ sudo podman run --name dsvw -p 1234:8000 -d quay.io/rst0git/dsvw

# Perform arbitrary code execution attack: $(echo secret)
$ curl "http://localhost:1234/?domain=www.google.com%3B%20echo%20secret"
nslookup: can't resolve '(null)': Name does not resolve

Name: www.google.com
Address 1: 142.250.187.228 lhr25s34-in-f4.1e100.net
Address 2: 2a00:1450:4009:820::2004 lhr25s34-in-x04.1e100.net
secret
(reverse-i-search)`': ^C

# Create a checkpoint for forensic analysis and leave the container running
$ sudo podman container checkpoint --leave-running -l -e /tmp/dsvw.tar

# Analyse checkpoint memory to identify the attacker's injected code
$ sudo checkpointctl memparse --pid 1 /tmp/dsvw.tar | grep 'echo secret'
00007faac5711f60 6f 6d 3b 20 65 63 68 6f 20 73 65 63 72 65 74 00 |om; echo secret.|
```

For larger processes, it's recommended to write the contents of process memory pages to a file rather than standard output.

To get an overview of process memory sizes within the checkpoint, run `checkpointctl memparse` without arguments.

```console
$ sudo checkpointctl memparse /tmp/jira.tar.gz

Displaying processes memory sizes from /tmp/jira.tar.gz

+-----+--------------+-------------+
| PID | PROCESS NAME | MEMORY SIZE |
+-----+--------------+-------------+
| 1 | tini | 100.0 KiB |
+-----+--------------+-------------+
| 2 | java | 553.5 MiB |
+-----+--------------+-------------+
```

In this example, given the large size of the java process, it is better to write its output to a file.

```console
$ sudo checkpointctl memparse --pid=2 /tmp/jira.tar.gz --output=/tmp/java-memory-pages.txt
Writing memory pages content for process ID 2 from checkpoint: /tmp/jira.tar.gz to file: /tmp/java-memory-pages.txt...
```

Please note that writing large memory pages to a file can take several minutes.

## Installing from source code

1. Clone the repository.
Expand Down
87 changes: 76 additions & 11 deletions checkpointctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ import (
)

var (
name string
version string
format string
stats bool
mounts bool
pID uint32
psTree bool
psTreeCmd bool
psTreeEnv bool
files bool
showAll bool
name string
version string
format string
stats bool
mounts bool
outputFilePath string
pID uint32
psTree bool
psTreeCmd bool
psTreeEnv bool
files bool
showAll bool
)

func main() {
Expand All @@ -40,6 +41,9 @@ func main() {
inspectCommand := setupInspect()
rootCommand.AddCommand(inspectCommand)

memparseCommand := setupMemParse()
rootCommand.AddCommand(memparseCommand)

rootCommand.Version = version

if err := rootCommand.Execute(); err != nil {
Expand Down Expand Up @@ -263,3 +267,64 @@ func cleanupTasks(tasks []task) {
}
}
}

func setupMemParse() *cobra.Command {
cmd := &cobra.Command{
Use: "memparse",
Short: "Analyze container checkpoint memory",
RunE: memparse,
Args: cobra.MinimumNArgs(1),
}

flags := cmd.Flags()

flags.Uint32VarP(
&pID,
"pid",
"p",
0,
"Specify the PID of a process to analyze",
)
flags.StringVarP(
&outputFilePath,
"output",
"o",
"",
"Specify the output file to be written to",
)

return cmd
}

func memparse(cmd *cobra.Command, args []string) error {
requiredFiles := []string{
metadata.SpecDumpFile, metadata.ConfigDumpFile,
filepath.Join(metadata.CheckpointDirectory, "pstree.img"),
filepath.Join(metadata.CheckpointDirectory, "core-"),
}

if pID == 0 {
requiredFiles = append(
requiredFiles,
filepath.Join(metadata.CheckpointDirectory, "pagemap-"),
)
} else {
requiredFiles = append(
requiredFiles,
filepath.Join(metadata.CheckpointDirectory, fmt.Sprintf("pagemap-%d.img", pID)),
filepath.Join(metadata.CheckpointDirectory, fmt.Sprintf("mm-%d.img", pID)),
)
}

tasks, err := createTasks(args, requiredFiles)
if err != nil {
return err
}
defer cleanupTasks(tasks)

if pID != 0 {
return printProcessMemoryPages(tasks[0])
}

return showProcessMemorySizeTables(tasks)
}
Loading

0 comments on commit bd9d1a9

Please sign in to comment.