# [NCtfU] pyc
#### artis24106
###### tags: `NCtfU` `Reverse` `python`
---
## What is .pyc?
----
### .pyc = python bytecode
![](https://i.imgur.com/sK7kDZt.png =800x)
----
### How to compile?
test.py
```python=
def hello_world():
print("Hello World =)")
hello_world()
```
```bash=
python3 -m compileall test.py -b
python3 test.pyc
Hello World =)
```
----
### pyc format
- Python 3.8.5
- [PEP 552](https://www.python.org/dev/peps/pep-0552/)
- 16 bytes header + marshaled code object
![](https://i.imgur.com/Xt2BbQR.png)
----
### magic number
- 4 bytes
- [magic 2 bytes](https://github.com/python/cpython/blob/5d1f32d33ba24d0aa87235ae40207bb57778388b/Lib/importlib/_bootstrap_external.py#L273) + `0d0a`
- ex:
- Python 3.8b4 -> `3413` -> `0d55`
- little endian
- `550d0d0a`
----
### example
test.pyc
![](https://i.imgur.com/2ZVgftW.png)
---
## Code object
----
### code object
- **byte-compiled** executable Python code
- or **bytecode**
```python=
# method1
def a():
print('b')
a.__code__
# method2
co = compile(data, 'test.py', 'exec')
# eval, exec, single
```
[`compile()` 的參數](https://www.programiz.com/python-programming/methods/built-in/compile)
----
### read-only attributes
- ex:
- co_name: code object name
- co_consts: constants
- co_code: bytecode instruction
- [more](https://docs.python.org/3.8/library/inspect.html)...
----
### example
```python=
def hello_world(a):
b = 456
print("Hello World " + str(a) + str(b))
co = hello_world.__code__
print('co_argcount'.ljust(11), co.co_argcount)
print('co_name'.ljust(11), co.co_name)
print('co_code'.ljust(11), co.co_code)
print('co_consts'.ljust(11), co.co_consts)
print('co_varnames', co.co_varnames)
print('co_names'.ljust(11), co.co_names)
```
----
### example cont.
```python=
def hello_world(a):
b = 456
print("Hello World " + str(a) + str(b))
'''
co_argcount 1
co_name hello_world
co_code b'd\x01}\x01t\x00d\x02t...
co_consts (None, 456, 'Hello World ')
co_varnames ('a', 'b')
co_names ('print', 'str')
'''
```
----
### bytecode
```python=
def hello_world():
print("Hello world")
co = hello_world.__code__
code = co.co_code
print(code)
# b't\x00d\x01\x83\x01\x01\x00d\x00S\x00'
```
----
### opcode
- [`Lib/opcode.py`](https://github.com/python/cpython/blob/3.8/Lib/opcode.py)
- (opcode \>= `HAVE_ARGUMENT`) -> have argument
![](https://i.imgur.com/eqAILY0.png)
----
### instruction
- 2 bytes each instruction
- opcode (1 byte) + oparg (1 byte)
- no argument -> oparg = 0 (default)
- ex: `b't\x00d\x01'`
![](https://i.imgur.com/g1XWdhC.png)
```=
LOAD_GLOBAL 0
LOAD_CONST 1
```
----
### instruction cont.
- https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions
- ![](https://i.imgur.com/BcCtb6E.png)
- ![](https://i.imgur.com/mK9QgYs.png)
```=
LOAD_GLOBAL 0 (co_names[0])
LOAD_CONST 1 (co_consts[1])
```
----
### dis
- python standard library
- Disassembler for Python bytecode
```python=
import dis
def hello_world():
print("Hello world")
dis.dis(hello_world.__code__)
```
```=
4 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello world')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
```
----
### disassemble pyc
```python=
import dis
import marshal
with open('./test.pyc', 'rb') as f:
code = marshal.loads(f.read()[16:])
dis.dis(code)
```
![](https://i.imgur.com/GMoGm5g.png)
----
### decompile pyc to py
- [uncompyle6](https://github.com/rocky/python-uncompyle6), [decompyle3](https://github.com/rocky/python-decompile3), ...
```bash=
uncompyle6 test.pyc > source.py
```
![](https://i.imgur.com/VOYjpxv.png)
---
## Anti decompile
----
### method
1. change co_name
2. invalid instruction
3. remap opcode
----
### method1: change co_name
- uncompyle6 use `co_name` as function name
- so...
![](https://i.imgur.com/XNijzcJ.png)
- `'my_func'` can be replaced!
----
### example
patch `test.pyc` to `test_patched.pyc`
![](https://i.imgur.com/2BvpOlm.png =400x)
----
### execute pyc
```bash=
python3 test.pyc
# my_func(87) is called
python3 test_patched.pyc
# my_func(87) is called
```
----
### decompile pyc and execute py file
```bash=
uncompyle6 test_patched.pyc > test_patched.py
python3 test_patched.py
```
![](https://i.imgur.com/YUFtYk2.png)
----
### view test_patched.py
![](https://i.imgur.com/JtEOHLD.png =700x)
----
### disassemble patched pyc
valid !
![](https://i.imgur.com/qk4NZzW.png =600x)
----
### method2: invalid instruction
- insert invalid instruction in `co_code`
```bash=
# assume len(co_consts) < 200
LOAD_CONST 200
```
- use JUMP to skip them
![](https://i.imgur.com/e0dCdPB.png)
----
### example
- insert invalid instruction at the beginning of `co_code`
![](https://i.imgur.com/5GfO50u.png =400x)
----
### execute pyc
```bash=
python3 test.pyc
# my_func(87) is called
python3 test_patched2.pyc
# my_func(87) is called
```
----
### decompile pyc
```bash=
uncompyle6 test_patched2.pyc
# IndexError: tuple index out of range
```
----
### disassemble pyc
![](https://i.imgur.com/NKz6MnE.png =450x)
----
### method3: remap opcode
- custom interpreter
----
### example
- download https://github.com/python/cpython/tree/3.8/Python
----
### opcodes we want to swap
```=
BINARY_ADD 23
BINARY_SUBTRACT 24
```
----
### change opcodes
- `Lib/opcode.py`
- `Include/opcode.py`
![](https://i.imgur.com/v19LutA.png)
----
### make && compile py
```bash=
make -j
./python -m compileall test.py
```
![](https://i.imgur.com/qpHf6PD.png)
----
### decompile with uncompyle6
```bash=
uncompyle6 __pycache__/test.cpython-38.pyc > test_source.py
```
![](https://i.imgur.com/9VQ4hht.png)
----
### disassemble pyc && diff
```bash=
vimdiff <(python3 dis_pyc.py ) <(./python dis_pyc.py )
```
![](https://i.imgur.com/Xa5a23Z.jpg)
---
## Little Demo
- 2020 JustCTF - REmap
- 2020 AIS3-EOF final - Cat Slayer
----
### 2020 JustCTF - REmap
- [challenge, source code](https://github.com/justcatthefish/justctf-2020/tree/master/challenges/re_REmap)
- file: `backup_decryptor.exe`
- ![](https://i.imgur.com/Vi3apiF.png)
- [pyinstxtractor](https://github.com/extremecoders-re/pyinstxtractor)
- `python38.dll` or check pyc header -> python3.8
- [real python3.8 opcode.py](https://github.com/python/cpython/blob/3.8/Lib/opcode.py)
----
### 2020 AIS3-EOF final - Cat Slayer
- [challenge, source code, write-up](https://github.com/splitline/My-CTF-Challenges/tree/master/ais3-eof/2020-final/cat-slayer)
- file: `game.pyc`, `cat_slayer.data.meow.flag`
---
## Thanks
<img src='https://i.imgur.com/MF5Huj7.png' style='width: 250px; border: none; background-color: transparent;'>
<style>
.hr {
width: 100%;
border: 2px solid black;
text-align: center;
padding-top: 7px;
padding-bottom: 7px;
background-color: #FFCE44;
margin-top: 45px;
margin-bottom: 15px;
font-weight: 600;
}
hr {
display: none;
}
</style>
{"metaMigratedAt":"2023-06-15T21:59:32.384Z","metaMigratedFrom":"YAML","title":"[NCtfU] pyc","breaks":true,"contributors":"[{\"id\":\"0b9aa881-9a57-4ba9-b469-e75c551389e8\",\"add\":9337,\"del\":2254}]"}