Showing posts with label CLI. Show all posts
Showing posts with label CLI. Show all posts

Friday, November 11, 2016

Word.

I hate the mouse, so it makes sense that I would especially hate copying and pasting from the command line. I usually just need one word of screen output to go about my business and do what I need to do, but this requires activating the control box (Alt+Space), activating the Edit menu, activating the Mark command and then dragging the cursor over the text of interest. Woe be to me if I screw up the text selection, because then I get to start all over again. And then there's when Windows doesn't respond consistently to the Alt+Space keystrokes I'm sending in order to activate the control box (yes, this happens; no, it's not human error).

I hate this so much that I wrote a pair of programs (line and word) to resolve it. Here are three examples of using them: getting all IP addresses, copying one of several paths to the clipboard, and isolating a filename in a paragraph of output.


If you invoke line or word without an argument, it will print the output with line numbers. An argument will isolate the lines or words you specify. Word accepts negative indices like Python. Line does not, because I didn't want to hog memory reading all of stdin into a buffer, though it wouldn't be hard to add, and it's not unreasonable since this is mostly for use with small buffers. Neither program accepts ranges, because I didn't have lots of time to spend.

You can get the code here: https://github.com/strictlymike/pipe-slice

Sunday, February 28, 2016

Snooping on Myself for a Change

Premise

When I started red teaming, I would often want to know the answers to impossible questions while writing my post-assessment report:
  • What was that command I used that I didn't think would be important to making my point, but now I realize it is?
  • The output indicated a Windows error message - which error was it?
  • Which IP address was leased to me when I did that?
Naturally a red teamer should always be taking copious screenshots, recording IP addresses frequently, and incrementally reporting in tandem with the engagement. But knowing what will be significant later is sometimes a challenge. It would be nice to have a backup.

In this post, I talk about researching and constructing a tool for logging 32-bit cmd.exe commands and output local to one's Windows attack VM. Then I discuss limitations and post source code. I've wanted to do this for a while, but I recently had a project that gave me an excuse to use Detours, so while I was at it, I decided to quickly knock out this project.

Tools

Microsoft Research produced a very useful tool for both investigating this problem and developing a solution, called Microsoft Detours. Detours is a library for hooking Win32 APIs, which allows one to instrument and extend binary applications. The non-commercial version can be downloaded for free, but only covers 32-bit architectures. This will suffice for our proof of concept. These techniques are older than 1999, so I leave it as an exercise for the student to extend this to 64-bit.

One of the Detours samples, named traceapi, hooks 1,401 Windows API functions, and can provide detailed insight into how an application works. Meanwhile, another Detours sample, named simple, shows an example of hooking and extending the Windows SleepEx API. This is all we need to get started.

Investigation

To use the traceapi sample, you must copy the Detours root to a writable directory, open a 32-bit Windows SDK Prompt, and invoke nmake from the samples directory. The bin.x86 Detours sub-directory will contain many files, including the following tools necessary to this exercise:
  • traceapi.dll (or trcapi32.dll - I seem to recall getting different results depending upon how I built this)
  • withdll.exe - for loading an application with a DLL in its address space
  • syelogd.exe - for logging trace output from Detours tools
When using traceapi, I expected to find patterns including CreateFile(CONIN$) / ReadFile, or some such. Then I anticipated hooking functions to record which handles corresponded to CONIN$, CONERR$, and CONOUT$ so that I could know which ReadFileW and WriteFileW activity corresponded to console input and output.

To begin the investigation, I launched syelogd.exe to log to a file, as follows:

C:\Users\someone\Detours\bin.x86> syelogd.exe trace_cmd.log

I then launched the 32-bit cmd.exe with traceapi.dll in its address space to begin tracing:

C:\Users\someone\Detours\bin.x86> withdll.exe /d:traceapi.dll C:\Windows\SysWOW64\cmd.exe

At this point, I typed a few commands like DIR and EXIT, and then terminated syelogd.exe.

Ah, beautiful trace output

What I found was happily contrary to my expectations...

Found the "Easy" button

The Windows command interpreter uses the ReadConsole and WriteConsole APIs for reading to and writing from the console. All the easier to hook, with no state tracking required.

Development and Features

The Detours sample named simple contains all you need to get started. Copy that folder, name it something else, update the Makefile, and get cooking. One bump I ran into and hacked up a fix for was that the 32-bit rc.exe crashed when executing it within my 32-bit Windows SDK Prompt. This is why my own Makefile (not uploaded) directly references the 64-bit rc.exe.

Logging

To customize the sample code to do my bidding, I followed the simple example's hooking pattern to first hook ReadConsoleW and WriteConsoleW. My initial logging hooks simply returned the value of the real functions just for testing purposes. I then tried printf("WriteConsole: %S", lpBuffer) to test whether I could output the data, and finally opened a file within the DllMain function.

Here is an example of the logging for a call to net.exe:
Logs for days
Why is there a separate log for each process? Because my DLL creates a new, unique log every time it is loaded. See the Limitations section below for discussion.

Why does net.exe create two logs? Because net.exe calls out to net1.exe to do its work.

IP Address Logging

I expected to have to use some sort of COM API, but instead there's this convenient GetAdaptersInfo API. Easy-peasy :)

Status Command

I wanted a way of knowing for sure that logging was enabled, without typing EXIT and having my parent prompt close on me. So, I implemented a secret command, REM status. Because my command begins with the REM command, it will be ignored by the command interpreter but intercepted by my hook on its way to the application.

BOOL WINAPI
LogReadConsoleW(
  HANDLE  hConsoleInput,
  LPVOID  lpBuffer,
  DWORD   nNumberOfCharsToRead,
  LPDWORD lpNumberOfCharsRead,
  PCONSOLE_READCONSOLE_CONTROL pInputControl
)
{
    BOOL ret = pReadConsoleW(
        hConsoleInput,
        lpBuffer,
        nNumberOfCharsToRead,
        lpNumberOfCharsRead,
        pInputControl
       );

    /* TODO: Replace this crap code with more accurate parsing */
    if (!wcsncmp((const wchar_t *)lpBuffer, CMD_QUERY_STATUS,
                wcslen(CMD_QUERY_STATUS)))
    {
        printf("Logging to %s\n", cmdlog_fname_expanded);
        LogInfoStampToFile(stdout);
    }

    LogToFile(cmdlog, "%S", lpBuffer);
    State = clsLabel;

    return ret;
}

The result is satisfactory. I type REM status, and if I'm hooked, I see a nice little confirmation that logging is enabled, including the log file name and some IP address information.

Yes, Neo, you are in the Matrix.

Limitations

Otherwise known as "problems I don't plan to solve until I get a copy of Detours Enterprise and some more time." The last 10% always takes 90% of the time, amirite?

Single-File Logging

This will probably entail using IPC, and some IPC mechanisms (like named pipes) cannot be initialized in DllMain because they could cause a deadlock by precipitating an attempt to acquire the loader lock (check out Microsoft's article on DLL best practices). This means writing "lazy initialization" code. I leave this as an exercise for the student.

Another scheme is for each child to share a log file with the parent process whose name is based on the PID of the top-most parent of the current process that has our DLL loaded in its address space. The APIs used to do this might entail a LoadLibrary or some APIs that violate DLL best practices, so again... Exercise left for the student.

Alternately, I could just open and close a single logfile with a single name, but then if I open several logged command prompts, one may fail to get access to the log as another is writing to it (say, if I'm running a command that outputs continuously while running another command in the other prompt). So, I didn't do that.

Cross-Architecture Process Creation

Because Detours Express is only for 32-bit, we have no 64-bit DLL. So here's what happens when you try to load a 64-bit process...

Nope.
Hooking 64-bit, while fun, is not something I have time for right now (I'm a father; give me a break). But my employer has a license for 64-bit, so perhaps I will extend this later. How? I anticipate that I will use the ImageHlp APIs or something to figure out whether the CreateProcess call is for a 32- or 64-bit process, and then load the appropriate DLL. Yay!

^M&^M's

If you open the output log files in something like gvim, you will see stray ^M's (character 0x0d) all over the place. Yuck. But a quick swipe of the %s/^M//g in gvim eliminates these, so I just can't bring myself to care.

Source Code

As is frequently the case with my spare-time experimentation, this code is proof of concept only. If it doesn't compile because I didn't close a pair of parentheses, rest assured that it's because I had to drop everything and rush over to my young daughter to prevent her from acquainting herself with an electric socket. Forgive me. But hopefully the thought process published here and the examples available in the code will help you think of new ways to solve problems.

The code is here: https://github.com/strictlymike/tools/tree/master/cmdlog

Tuesday, November 3, 2015

Poor Man's Persistence Monitoring

I've been interested in detecting badness on my computer since I started using computers to begin with. To that end, there is an interesting SysInternals tool called autoruns that can go through a large number of potential persistence points and indicate anomalies. The output of the graphical tool is daunting, but the command line tool, autorunsc (documented along with autoruns), provides an opportunity to automate the detection of changes in these potential persistence points, allowing you to identify new potential badness on your 'puter. This amounts to a basic level of configuration auditing, with a special focus on configuration settings that are relevant to malware persistence. Here's how to do it.

The command-line autorunsc tool allows you to audit all persistence entries that it knows about, print its output as a comma-separated variable (CSV) file, show file hashes, and verify digital signatures. Using this, you can capture and examine a system baseline configuration, and use differential analysis to identify new anomalies on a recurring basis. The autorunsc command-line options to do all this can be found by running autorunsc with the /? switch. Here is an excerpt of the output showing the relevant arguments:

     -a         Show all entries.
     -c         Print output as CSV.
     -f         Show file hashes.
     -v         Verify digital signatures.

Since autorunsc outputs to the console, you can use output redirection to save this to a file within a command script:

autorunsc.exe -acfv > output.csv

Then you can use the Windows FC.EXE utility (I suppose FC stands for "file compare") to compare this with a past capture. Then you can use the IF ERRORLEVEL command to determine whether fc detected a difference.

For this to be effective, you must first capture and audit a baseline. This means triaging the files and registry settings in the first set of output. Easy checks for suspicious binaries are to look for the "(Verified)" label and confirm that the binary is signed by an author you trust, or to look up hashes on VirusTotal. Registry settings like KnownDLLs should only point to DLLs that are part of Windows. Once your baseline configuration is verified, you can perform a much quicker differential analysis (with FC.EXE as mentioned above) to detect changes.

If a difference is detected, you can use a utility such as gvim, diff, or WinDiff, to examine the differences. Note that if you use gvim for Windows, you may need to use a utility such as Cygwin's iconv.exe to convert the UCS-2 output of autorunsc.exe into UTF-8. This is what I do, and it looks like this:


I have a script that does this and presents changes using gvim. I placed this in my Startup program group and also run it periodically if my computer has not been rebooted in a long while. The script compares both the output from autorunsc and the contents of HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify, because that key is occasionally written to, but appears to maintain the same contents each time. In practice, something almost always changes across reboots, but it is not very difficult to locate and audit each item.

One byproduct of using this script has been an increased awareness of the changes that are made when software updates are installed. For instance, after an iTunes for Windows update (don't ask), I found out that Apple registers a WinSock namespace for Bonjour, pointing to a digitally signed DLL produced by Apple. Another byproduct of this investigation over time is that it gives me a gradual tour of the persistence points in Windows.

In case you would find it useful as the basis for your own persistence checks, my script can be had here:

https://github.com/strictlymike/persistmon

Modify it to your liking. Happy un-hacking ;-)

Monday, February 2, 2015

Clip Snippet


Quickie: you run a command, then want its output in your Word document (because, for some reason, customers won't accept .TXT files as deliverables).  Try pressing the up arrow to queue the previous command to be repeated, and then piping it to CLIP.EXE:

ipconfig /all | clip

Check this little guy out (from CLIP /?):

CLIP

Description:
    Redirects output of command line tools to the Windows clipboard.
    This text output can then be pasted into other programs.

Parameter List:
    /?                  Displays this help message.

Examples:
    DIR | CLIP          Places a copy of the current directory
                        listing into the Windows clipboard.

    CLIP < README.TXT   Places a copy of the text from readme.txt
                        on to the Windows clipboard.

Yeahhhh.  It'll copy its input into the clipboard.  Not bad :-)

Tuesday, January 20, 2015

Windows man pages

If you're a UNIX or Linux person, you know Windows lacks man pages.  So man up and make some.

Create a directory and add it to your path.  Call it man, help, or whatever.

Every time you run into a command whose usage and arguments you need to know, run it with the /? switch and redirect its output into a file in that help directory.

FOR /? > %USERPROFILE%\help\for.txt

Now you've got a file in your path that says this:

Runs a specified command for each file in a set of files.

FOR %variable IN (set) DO command [command-parameters]

  %variable  Specifies a single letter replaceable parameter.
  (set)      Specifies a set of one or more files.  Wildcards may be used.
  command    Specifies the command to carry out for each file.
  command-parameters
             Specifies parameters or switches for the specified command.

To use the FOR command in a batch program, specify %%variable instead
...

Then, the next time you need help on the for command, hit Win+R, type for.txt, and press Enter.  The saved help will pop up immediately in Notepad, because you added the help directory (or whatever you called it) to your path.  You can also use it to stash past examples if you're running or scripting particularly complex commands.

Presto.  Man pages.  Now quit yer whining.

Sunday, January 18, 2015

This One Weird Trick the Visual Studio Team Doesn't Want You to Know!

I find it fascinating that most Windows boxes (these days, I can almost say all of them) have CLR compilers sitting around on their hard drives.  This would make it possible for people to experiment with C# and VB.NET without downloading squat...  But Microsoft forgot to do one thing: set up some file associations.  So this is a write-up on how to set them up in a quick and dirty fashion on any computer so that you can experiment with C# and VB.NET without having to download Visual Studio.

I don't like Visual Studio anyway, probably because I don't work on enterprise stuff.  I shamelessly admit that my codebase is a collection of cheap hacks, and my goal is to be able to crack open an editor, vomit out fifty or a thousand lines of code, execute my tests, and get on with my day.  I also get a smug sense of satisfaction and independence from eschewing an IDE, kind of like choosing to drive a manual transmission car.  (Why don't I use Linux, then?  Quiet, you.  It's because of software requirements.)

So, with no further ado, I present:

Visual Stu-DIY-o

For the impatient, here is the quick and dirty TL;DR:

assoc .cs=CSharpSourceFile
ftype CSharpSourceFile=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc.exe %1
reg add hkcr\CSharpSourceFile\Shell\edit\command /ve /d "%WINDIR%\System32\notepad.exe %1"

But your .NET compiler might not be located there, or your code warnings and errors may zip by and disappear, or your executable may not get built, or you may have wanted to program VB.NET, et cetera.  If you care about such things, then go through the exercise of locating your compiler, creating a friendly compiler script, and associating that with your source files.  Read on for more.

Find your .NET compiler of choice

You're really just confirming what version you have.  You're doing this because you want to be sure the files exist before you copy and paste the script below.  Fortunately, this is easy, because it's in your path:

  1. Hit Win+R (to get the run dialog)
  2. Type Microsoft.NET
  3. Leap of faith: press Enter
  4. Browse to the Framework\v4.0.30319 directory
  5. Note the absolute path to csc.exe (hint: hit ALT+D, hit End, finish off that directory name with a backslash, and start typing csc, and it will let you auto-complete the path in the address bar; copy this address for later).

A little note: I use the Framework (and not Framework64) directory because I want my MSIL to run in 32- and 64-bit environments.  For my normal projects, I use .NET version 4.0, and for software that must be deployed to mixed and potentially un-maintained platforms (if I told you why, I'd have to kill you), I target .NET 2.0.  Lastly, I use C#, but you don't have to.  At this point, if you want to locate vbc.exe instead (That's the VB.NET Compiler), go ahead and replace csc with vbc.  I won't judge you.  Just remember to actually replace what I put in the script below with the path to vbc.

At this point, we could set the file association directly with the compiler, as in the "TL;DR" section above, but if you double-click your .cs (or .vb) files, and your source code has warnings or errors, a mysterious black box will appear and disappear before you have a chance to review and fix them.  That's why you're going to bother with the next step:

Write a quickie script

Choose a place where this script will live.  Make a "util" or "bin" or "scripts" directory somewhere in your user profile if you don't already have one.  Open Notepad, copy and paste what is below (replace the path to csc if you are targeting a different .NET version, architecture, compiler, etc.), and save it as something.  I chose buildcs4.cmd.  In the Save As dialog, under "Save as type", don't forget to select "All Files (*.*)" so that it does not "helpfully" append the .TXT file extension for you, thus neutering your command script.  Here's my version:

@ECHO OFF
%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc.exe %*
PAUSE

The PAUSE command is there so you can see the warnings before the script closes.  You can't use an IF ERRORLEVEL command to pause for warnings, because the compiler only returns 1 or greater if there are errors, not if there are warnings, so that is why I include an unconditional PAUSE.  %* tells the command interpreter to pass along all the arguments from the command line.  This is how the filename you double-click will be passed to the C# compiler (or VB.NET compiler, if that's your predilection).

Finally, make the file association

Open an administrative command prompt and invoke these three incantations:

assoc .cs=CSharpSourceFile
ftype CSharpSourceFile=%USERPROFILE%\path\to\buildcs4.cmd %1
reg add hkcr\CSharpSourceFile\Shell\edit\command /ve /d "%WINDIR%\System32\notepad.exe %1"

These define a filetype, set a file association, and add an "Edit" context menu command, respectively.  For more background on all that, type ASSOC /?, FTYPE /?, and REG /? on the command-line.

Taking it for a Spin

If you want a test, here's a nice C# sample you can paste into notepad.  I think the traditional hello-world example is pretty boring, so here's something to spice up someone's life:

using System;
using System.Windows.Forms;

class mbox
{
    static public void Main()
    {
        MessageBox.Show(
            "Delete C:\\ - are you sure?",
            "Confirmation",
            MessageBoxButtons.OK,
            MessageBoxIcon.Warning
           );
    }
}

Now save that as hello.cs, and double-click it to compile.  Run the resulting hello.exe to see the result.



Right-click hello.cs and choose Edit when you want to mess about some more.  Aaaaaand, we're done.

Monday, December 29, 2014

How to Stop Bashing and Take CMD

A colleague of mine is a Linux hacker who took a job in Seattle and has been thrust into Windows.  On his team, PowerShell is not always available, so he's left saying "look dude, I know bash; what the heck do I do with this??"  If you're in a similar situation, this is a primer for you.

Below are a few bash-isms and Unix-isms, and their cmd.exe analogues, as well as some things that are purely from CMD.  To try them, open up cmd.exe (Start > Run: cmd.exe, or Windows+R: cmd.exe) and go to town.

One-Liners

I'll start with some one-liners.  The $ prompt indicates commands that can be used in bash on GNU/Linux and similar operating systems, and the > prompt represents the cmd.exe equivalent.  Omit the prompts ($ and >) when trying these commands.

Run something else
$ bash -c something else
> cmd /c something else
> cmd /k something else

In the latter command, cmd will stay resident and permit further commands.

Display program return value:
$ echo $?
> echo %ERRORLEVEL%

Some programs, when they are run, return a numeric status code.  Generally, 0 indicates success, and 1 or greater indicates an error.  Windows executables that display graphical windows (calc.exe and winword.exe are examples) return 0 immediately and unconditionally, and run "asynchronously" -- meaning, the command prompt receives the return value immediately and allows the user to type more commands.

Compare program return value:
$ if [ $? -eq 0 ]; then echo Success; fi;
> if %errorlevel% == 0 echo Success

Find program in path:
$ which which
> where where


Find file:
$ find / -type f -name whatever\*.txt
> dir /s \whatever*.txt
> dir /a/b/s \whatever*.txt

The latter command will show all files, even those having the hidden attribute (/a) and will provide bare output without any file sizes or other details (/b).

Find string in files:
$ grep -Ri needle *
> findstr /S /I needle *

Find string in program output:
$ ifconfig | grep 192
> ipconfig | findstr 192

Start service (e.g. mysql):
$ service mysql start
> sc start mysql
> net start mysql

The latter command (net.exe) can do many things including viewing and modifying local groups, authenticating to network shares and mapping them to drive letters, etc.  The other command (sc.exe) is strictly for starting and stopping services, viewing their configuration, and other tasks.

Restart web server:
$ apachectl -k restart > /dev/null 2>&1
> iisreset > nul 2>&1


Terminate by pid:
$ kill -s 9 916
> taskkill /f /PID 916


Terminate by name:
$ killall -s 9 kcalc
> taskkill /f /IM calc.exe


Shut down:
$ shutdown -h now
> shutdown /s /t 0


Reboot: 
$ shutdown -r now
$ reboot
> shutdown /r /t 0


Add user to group: 
$ useradd -G root mike
> net localgroup administrators /add mike


Set environment variable:
$ variable=hello
> set variable=hello


Display environment variable: 
$ echo $variable
> echo %variable%


Prompt for environment variable:
$ read -p "Type something: " variable
$ echo $variable
> set /p variable=Type something:

> echo %variable%

Pre-set variables such as username:
$ echo $USER
> echo %username%


Dump environment: 
$ setenv
> set


The SET command also accepts partial variable names, and will list all the variables and values whose names match that string.

Compare files:
$ cmp file1 file2
> fc file1 file2

You can check the errorlevel (the numeric error or success code returned by the program) to determine whether the files are the same.  Identical files result in a 0 errorlevel, and differing files result in a return value of 1 or greater.

Display file contents:
$ cat file
> type file

> more file

The latter command, more, can be used to display the contents of those notorious alternate data streams, and also serves as a pager (see next).

Display file contents with pager:
$ less file
$ cat file | less
> more file
> type file | more


Loops:
$ for ((n=2; n<=8; n+=2)); do echo $n; done
> for /l %n in (2, 2, 8) do echo %n


Operate on a set of arbitrary words: 
> for word in hello there; do echo $word; done
$ for %w in (hello there) do ( echo %w )


Echo the names of all text files in the current dir:
 > for file in *.txt; do echo $file; done
$ for %f in (*.txt) do echo %f


Echo the names of all text files recursively: 
$ for file in $(find . -name \*.txt); do echo $file; done
> for /f "usebackq" %f in (`dir /a/b/s *.txt`) do echo %f
> for /r %f in (*.txt) do echo %f


Parse IP addresses out of IP configuration:
 $ echo IP: $(ifconfig | grep 'inet addr' | awk -F: '{print $2}' | awk '{print $1}')
>for /f "usebackq delims=: tokens=1,2" %a in (`ipconfig ^| findstr /i IPv4`) do echo IP: %b

Scripts

Read file, find pattern, copy to another location

cprintf.sh:
#!/bin/bash

dstdir=~/cfiles;
rm -rf $dstdir;
mkdir $dstdir;

for file in $(find . -type f -name \*.c); do
    grep -i printf $file > /dev/null 2>&1;
    if [ $? -eq 0 ]; then
        cp $file $dstdir;
    fi;
done;


cprintf.cmd:
@echo off

set dstdir=%userprofile%\cfiles
if exist "%dstdir%" rmdir /s /q "%dstdir%"
mkdir "%dstdir%"

for /r %%f in (*.c) do (
    findstr /i printf %%f > nul 2>&1
    if not errorlevel 1 copy %%f "%dstdir%" > nul 2>&1
)


Because of the idiosynchrasies of the Windows command interpreter and native utilities, writing robust scripts for CMD is akin to any of the following activities:
  • Leveling and hanging a picture in an earthquake
  • Making a bed with a rabid dog in it
  • Asking a room full of four-year-olds to each draw a triangle
  • Wrestling with a snake, a crab, and an orangutan at the same time
  • Balancing a system of simultaneous equations by inspection while inebriated
Here are the idiosynchrasies that are relevant to the above script:

Double percent signs (e.g. %%f) are used to denote loop variables in .cmd and .bat files instead of single percent signs (e.g. %f) as on the command line.  Variable names must be a single character in length (e.g. %a on the command line, and %%a in a script file).

Also, evaluation of errorlevels can be done both by comparison with the %ERRORLEVEL% environment variable and using the IF [NOT] ERRORLEVEL construct.  The help for the if command (accessible by typing IF /?) states:

  ERRORLEVEL number Specifies a true condition if the last program run
                    returned an exit code equal to or greater than the number
                    specified.
And:

  NOT               Specifies that Windows should carry out
                    the command only if the condition is false.

Just so you've got that straight: don't ask the command interpreter this:

IF ERRORLEVEL 0 ECHO Oh yes, everything is fine <-- NO, IT IS NOT

There are some more turds in the punch bowl...

If you mean to set variables in a for loop and reflect on those values later in the script, you must first invoke SETLOCAL ENABLEDELAYEDEXPANSION.  To obtain the most up-to-date value of each variable, you must then use exclamation points, not percent signs, to access the data.  For example: !frick!.

If you use a pipe within a backtick expression in a FOR /F "usebackq" statement, escape the pipe with the caret symbol.  For example, `dir /a/b/s *.txt ^| findstr x`.

Windows scripts access arguments as %1, %2, etc.  Tilde modifiers such as %~n0 (equivalent to basename $1) are used to parse filenames, and can be found in the help for the FOR command. 

Environment variables inherently support substring selection and pattern replacement:

C:\Users\mykill>echo %username:kill=ke%
myke

C:\Users\mykill>echo %username:~0,3%
myk


I will probably update this article to include more info.  Suggestions are welcome.

For more loopy help:
for /?

The help from a few other commands can also be informative:
if /?
setlocal /?