10

Is it possible to print my environment variable memory address ?

With gdb-peda i have a memory address looking like 0xbffffcd6 with searchmem and i know it's the right form. (0xbfff????) but gdb moved the stack with some other environment variable.

I would like with my python script to get this address and then do my trick and include my shellcode.

i tried (with Python):

print hex(id(os.environ["ENVVAR"]))
print memoryview(os.environ["ENVVAR"])

# output :
# 0xb7b205c0L
# <memory at 0xb7b4dd9c>

With Ruby :

puts (ENV['PATH'].object_id << 1).to_s(16)
# output :
# -4836c38c

If anyone have an idea, with python or ruby.

albttx
  • 3,444
  • 4
  • 23
  • 42
  • I'd be curious to hear more about the reason for needing/doing this. What is the trick? What is the shell code you want to include? Why does the shell code need the memory address of an environment variable. etc – Scott Swezey Jun 12 '16 at 02:48
  • @ScottS. Thanks for yout interest, it is for a simple BSS overflow exploit, i know i could use another solution, i found the way to get it in C, but it's much easier to access and work with my script with python. To get my environment variable address, concat it to my shellcode and do all my stuff. I start watching the code of `gdb-peda` for searching how it does. – albttx Jun 12 '16 at 13:08
  • @eki-al Could you send a link to the example in C? – VirtualVDX Jun 17 '16 at 14:37
  • http://pastebin.com/3DTJB9cY – albttx Jun 17 '16 at 14:55
  • @eki-al May be loading `libc` with `ctypes` and calling to C getenv from Python is an option. See docs on `ctypes` here: https://docs.python.org/2/library/ctypes.html – VirtualVDX Jun 17 '16 at 17:20

7 Answers7

4

The cpython built in function id() returns a unique id for any object, which is not exactly it's memory address but is as close as you can get to such.

For example, we have variable x. id(x) does not return the memory address of the variable x, rather it returns the memory address of the object that x points to.

There's a strict separation between 'variables' and 'memory objects'. In the standard implementation, python allocates a set of locals and a stack for the virtual machine to operate on. All local slots are disjoint, so if you load an object from local slot x onto the stack and modify that object, the "location" of the x slot doesn't change.

enter image description here http://docs.python.org/library/functions.html#id

davidcondrey
  • 34,416
  • 17
  • 114
  • 136
  • Thank you for this nice explanation, but i think it might be another way to access to memory address, i took a quick look at `peda` github, for the `searchmem` function this is how it's handle, i will test this weekend : https://github.com/longld/peda/blob/b7c7d7aeeba65a467fe982787b4f72e017774905/peda.py#L1889 – albttx Jun 16 '16 at 08:13
  • [CPython implementation detai](https://docs.python.org/3/library/functions.html#id)l: This is the address of the object in memory. – Nizam Mohamed Jun 18 '16 at 21:15
4

I suppose you could do that using the ctypes module to call the native getenv directly :

import ctypes

libc = ctypes.CDLL("libc.so.6")

getenv = libc.getenv
getenv.restype = ctypes.c_voidp

print('%08x' % getenv('PATH'))
mickael9
  • 456
  • 2
  • 12
3

This seems an impossible task at least in python. There are few things to take in consideration from this question:

  • ASLR would make this completely impossible
  • Every binary can have it's own overhead, different argv, so, the only reliable option is to execute the binary and trace it's memory until we found the environment variable we are looking for. Basically, even if we can find the environment address in the python process, it would be at a different position in the binary you are trying to exploit.

Best fit to answer this question is to use http://python3-pwntools.readthedocs.io/en/latest/elf.html which is taking a coredump file where it's easy to find the address.

laxa
  • 81
  • 1
  • 6
  • Thank you, nice answer, just discover ASLR, but if I don't have ASLR set, it's compile with the stack protector flag? – albttx Jun 18 '16 at 16:49
  • Not sure to understand your comment. ASLR is a system wide protection, it's not configured in the binary. You can do `cat /proc/sys/kernel/randomize_va_space` to see if it's enabled (value > 0). Otherwise, you have also the PIE protection that could shuffle the emplacement of the ENV in the memory. No other protection should make it be at other places. Most binaries though are using the NX protection and the stack is marked as not executable, so you won't be able to place a shellcode in the env in that case. – laxa Jun 18 '16 at 17:01
  • is ENV in the stack? – Nizam Mohamed Jun 18 '16 at 21:12
  • ENV is always in the stack on Linux. Stack layout is as follow, starting from highest address : [kernel reserved space (process can't see it)] [ENV] [arguments of program] [program stack]. – laxa Jun 19 '16 at 00:37
2

Please keep in mind that system environment variable is not an object you can access by its memory address. Each process, like Python or Ruby process running your script will receive its own copy of environment. Thats why results returned by Python and Ruby interpreters are so different.

If you would like to modify system environment variable you should use API provided by your programming language. Please see this or that post for Python solution.

Community
  • 1
  • 1
VirtualVDX
  • 2,231
  • 1
  • 13
  • 14
1

The getenv() function is inherently not reentrant because it returns a value pointing to static data.

In fact, for higher performance of getenv(), the implementation could also maintain a separate copy of the environment in a data structure that could be searched much more quickly (such as an indexed hash table, or a binary tree), and update both it and the linear list at environ when setenv() or unsetenv() is invoked.

So the address returned by getenv is not necessarily from the environment.

Process memory layout;


(source: duartes.org)


(source: cloudfront.net)

Memory map

import os

def mem_map():
    path_hex = hex(id(os.getenv('PATH'))).rstrip('L')
    path_address = int(path_hex, 16)
    for line in open('/proc/self/maps'):
        if 'stack' in line:
            line = line.split()
            first, second = line[0].split('-')
            first, second = int(first, 16), int(second, 16)
            #stack grows towards lower memory address
            start, end = max(first, second), min(first, second)
            print('stack:\n\tstart:\t0x{}\n\tend:\t0x{}\n\tsize:\t{}'.format(start, end, start - end))
            if path_address in range(end, start+1):
                print('\tgetenv("PATH") ({}) is in the stack'.format(path_hex))
            else:
                print('\tgetenv("PATH") ({}) is not in the stack'.format(path_hex))
            if path_address > start:
                print('\tgetenv("PATH") ({}) is above the stack'.format(path_hex))
            else:
                print('\tgetenv("PATH") ({}) is not above the stack'.format(path_hex))
            print('')
            continue
        if 'heap' in line:
            line = line.split()
            first, second = line[0].split('-')
            first, second  = int(first, 16), int(second, 16)
            #heap grows towards higher memory address
            start, end = min(first, second), max(first, second)
            print('heap:\n\tstart:\t0x{}\n\tend:\t0x{}\n\tsize:\t{}'.format(start, end, end - start))
            if path_address in range(start, end+1):
                print('\tgetenv("PATH") ({}) in the heap'.format(path_hex))
            else:
                print('\tgetenv("PATH") ({}) is not in the heap'.format(path_hex))
            print('')

Output;

heap:
        start:  0x170364928
        end:    0x170930176
        size:   565248
        getenv("PATH") (0xb74d2330) is not in the heap

stack:
        start:  0x0xbffa8000L
        end:    0x0xbff86000L
        size:   139264
        getenv("PATH") (0xb74d2330) is not in the stack
        getenv("PATH") (0xb74d2330) is not above the stack

Environment is above the stack. So its address should be higher than the stack. But the address id shows is not in the stack, not in the heap and not above the stack. Is it really an address? or my calculation is wrong!

Here's the code to check where an object lies in memory.

def where_in_mem(obj):
    maps = {}
    for line in open('/proc/self/maps'):
        line = line.split()
        start, end = line[0].split('-')

        key = line[-1] if line[-1] != '0' else 'anonymous'
        maps.setdefault(key, []).append((int(start, 16), int(end, 16)))

    for key, pair in maps.items():
        for start, end in pair:
            # stack starts at higher memory address and grows towards lower memory address
            if 'stack' in key:
                if start >= id(obj) >= end:
                    print('Object "{}" ({}) in the range {} - {}, mapped to {}'.format(obj, hex(id(obj)), hex(start), hex(end), key))
                    continue
            if start <= id(obj) <= end:
                print('Object "{}" ({}) in the range {} - {}, mapped to {}'.format(obj, hex(id(obj)), hex(start), hex(end), key))

where_in_mem(1)
where_in_mem(os.getenv('PATH'))

Output;

Object "1" (0xa17f8b0) in the range 0xa173000 - 0xa1fd000, mapped to [heap]
Object "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" (0xb74a1330L) in the range 0xb7414000L - 0xb74d6000L, mapped to anonymous

What's anonymous in the above output?

It is also possible to create an anonymous memory mapping that does not correspond to any files, being used instead for program data. In Linux, if you request a large block of memory via malloc(), the C library will create such an anonymous mapping instead of using heap memory. ‘Large’ means larger than MMAP_THRESHOLD bytes, 128 kB by default and adjustable via mallopt().

Anatomy of a Program in Memory

So the os.environ['PATH'] is in the malloced region.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Nizam Mohamed
  • 8,751
  • 24
  • 32
1

Thanks for @mickael9, I have writen a function to calculate address of an environment variable in a program:

def getEnvAddr(envName, ELFfile):
  import ctypes
  libc = ctypes.CDLL('libc.so.6')
  getenv = libc.getenv
  getenv.restype = ctypes.c_voidp

  ptr = getenv(envName)
  ptr += (len('/usr/bin/python') - len(ELFfile)) * 2
  return ptr

For example:

user@host:~$ ./getenvaddr.elf PATH /bin/ls
PATH will be at 0xbfffff22 in /bin/ls
user@host:~$ python getenvaddr.py PATH /bin/ls
PATH will be at 0xbfffff22 in /bin/ls
user@host:~$

Note: This function only works in Linux system.

mja
  • 1,273
  • 1
  • 20
  • 22
-1

In ruby it's possible - this post covers the general case: Accessing objects memory address in ruby..? "You can get the actual pointer value of an object by taking the object id, and doing a bitwise shift to the left"

puts (ENV['RAILS_ENV'].object_id << 1).to_s(16)
> 7f84598a8d58
Community
  • 1
  • 1
Fred Willmore
  • 4,386
  • 1
  • 27
  • 36