Custom Attack Simulation Language (CASL) Network Associates, Inc. October, 1998 Contents 1 Introduction 3 1.1 What Is CASL? . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Why Is CASL? . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 How Does One Use CASL? . . . . . . . . . . . . . . . . . . . 5 1.4 A Word Of Warning . . . . . . . . . . . . . . . . . . . . . . . 5 2 Walkthrough 6 2.1 Brief Language Introduction . . . . . . . . . . . . . . . . . . . 6 2.1.1 Statements . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.3 Comments . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.4 Packets . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2 An Example Script . . . . . . . . . . . . . . . . . . . 9 2.2.1 Packet Output . . . . . . . . . . . . . . . . . . 9 2.2.2 Packet Input . . . . . . . . . . . . . . . . . . . 12 2.2.3 The Finished Script . . . . . . . . . . . . . . . 14 3 Structure of a CASL Program 16 3.1 Statements and Expressions . . . . . . . . . . . . . . . 16 3.1.1 Characters . . . . . . . . . . . . . . . . . . . 16 3.1.2 Integers . . . . . . . . . . . . . . . . . . . . . 17 3.1.3 Strings . . . . . . . . . . . . . . . . . . . . . . 17 3.1.4 Complex Types (Buffers and Lists) . . . . . . . . 17 3.2 Syntax . . . . . . . . . . . . . . . . . . . . . . . . . *17 3.2.1 Statements . . . . . . . . . . . . . . . . . . . . 17 3.2.2 Comments . . . . . . . . . . . . . . . . . . . . . 18 3.2.3 Variables . . . . . . . . . . . . . . . . . . . . 18 3.2.4 Assignments . . . . . . . . . . . . . . . . . . . 18 3.2.5 The "copy" Operator . . . . . . . . . . . . . . . 18 1 CONTENTS 2 3.2.6 Math . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.2.7 Comparison . . . . . . . . . . . . . . . . . . . . . . . . 19 3.2.8 Expression Syntax . . . . . . . . . . . . . . . . . . . . 19 3.2.9 Boolean Conditions . . . . . . . . . . . . . . . . . . . 20 3.2.10Control . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2.11Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2.12Conditionals . . . . . . . . . . . . . . . . . . . . . . . 23 3.2.13Subroutine Calls . . . . . . . . . . . . . . . . . . . . . 24 4 Lists 25 4.1 Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *25 4.2 Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3 List Operators . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.4 List Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *27 5 Packet Headers 28 5.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *28 5.2 Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . * *29 5.3 Field Reference . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.4 Special Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . * *30 5.5 Buffer Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *30 5.6 Structure Extraction . . . . . . . . . . . . . . . . . . . . . . . 31 6 Subroutines 32 6.1 Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 6.2 Argument Passing . . . . . . . . . . . . . . . . . . . . . . . . 33 6.3 Variable Argument Lists . . . . . . . . . . . . . . . . . . . . . 33 6.4 Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 6.5 Scope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *34 7 Network I/O 36 7.1 Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7.2 Fixups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *37 7.3 Input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * *37 7.4 Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . * * 37 8 Built-in Functions 38 Chapter 1 Introduction 1.1 What Is CASL? CASL is an exploration tool for network protocols. By this, we mean that it's intended to make it easier for people to experiment with and learn about the way their networks operate. Since networks work by exchanging packets of information, CASL focuses on allowing users to read and write packets directly to and from the network. CASL functions as a scripting language _ a high level programming language, like Perl, Python, or Tcl. Unlike general-purpose scripting lan- guages, CASL is designed specifically to make it easy to construct, read, and write raw network packets. CASL is intended primarily for security auditing applications; that is to say, CASL is intended to simulate attacks against hosts in order to see if tny, many security holes are conceptually simple and easily explained; "send two IP packet fragments, one of which overlaps the other" (this is the "teardrop" bug, which crashes Linux and Windows NT). Unfortunately, 3 CHAPTER 1. INTRODUCTION 4 actually sending two IP fragments that overlap each other can be extraor- dinarily tricky in the "C" programming language, and virtually impossible in high-level languages like Perl. CASL addresses this issue. By making it easy to write programs that deal with raw IP packets, users can easily simulate protocol-level bugs, al- lowing them to test their machines for vulnerability to them. Rather than waiting for vendors to provide test programs, or crackers to leave exploit code lying around, CASL gives users an edge in assessing and resolving security problems. Some security issues may not be "bugs", per se, but rather techniques used by attackers to gain information about or subvert the security of net- worked hosts. For instance, a popular trick used by hackers to almost un- detectably see what programs are running on a machine is the "stealth port scan": several TCP protocol tricks allow attackers to see if a connection can be made to a port, without actually opening a connection. Programs that actually do things like this tend to be long, complex, and OS-specific. Security professionals are forced to spend valuable time fishing through hacker exploit code to find poorly-written Linux programs that don't even compile. This time could be better spent quickly writing the equivalent in portable, simple CASL code, which will not only run on the machines they need to run on, but also work exactly how they need to work. Why not write these programs in "C"? While security tools will certainly run a bit faster if hand-coded in "C", the runtime speed benefits are probably not outweighed by the development speed costs. A "C" programmer needs to worry about memory allocation, portable network I/O, and a whole mess of other issues ranging from error handling to byte ordering. CASL programs don't worry about any of this; rather, they focus on what really matters in network security programs _ what's happening on the network. Why not write them in a language like Perl? While extensions to in- terpreters exist to write and read raw packets, they're not portable, and they only solve a small part of the problem. The major difficulty in writing raw network code is not the actual act of sending a packet across the net- work, but rather the complexity of building the packets themselves. CASL provides facilities specifically designed to make it easy to build packets for arbitrary protocols (not just IP, UDP, and TCP). CHAPTER 1. INTRODUCTION 5 1.3 How Does One Use CASL? The quickest way to understand CASL is to see an example of an actual CASL script. CASL is distributed with a number of well-commented exam- ple scripts that do many different things, but we'll provide what we think is a good example of how CASL works in the next section, and use it to walk through all the features needed to make immediate use of the tool. The rest of this document describes CASL in detail. There may be a few things you need to know before effectively using CASL, and which are outside the scope of this documentation. The most important of them is an understanding of IP networking; this manual assumes you know how the IP protocols work at a fairly low level, and it'd take an entire other manual to effectively introduce this topic. Appendix A recommends some good resources for this topic. While CASL can be used with no programming experience to forge arbi- trary packets, effective tool development requires at least a passing exposure to how programming works. Any experience with languages like Bourne shell script, Perl, Tcl, or even complicated DOS "batch" files, will likely suffice, and the manual tries to be fairly complete in describing the programming facilities offered in CASL. 1.4 A Word Of Warning CASL is an extremely powerful tool. It's designed to test real network security holes by directly manipulating networks. With this power comes a significant amount of risk; CASL can disrupt and even disable networks, and it can do this in ways that you may not expect; some sequences of packets that CASL can emit can cause packet storms that won't end until all the devices on the network are restarted, and others can directly crash machines. Be aware of this, and be sure to test scripts on non-critical "test" networks before deploying them on mission-critical networks. Chapter 2 Walkthrough The best way to introduce yourself to CASL is to see an actual working example. We present here a fairly involved script that does something gen- uinely useful _ execution of a simple TCP "stealth scan", to find listening ports on a remote machine. This is a good example because it demonstrates many aspects of CASL at once: packet creation, output, input, packet pars- ing, and user output. 2.1 Brief Language Introduction Before we dig into the example script, we'll need a basic overview of the CASL language. A quick explanation, on which we'll expand later in this section: 2.1.1 Statements CASL programs consist of statements operating on variables. A statement is anything that "does" something, from calculating the value of "2 + 2" to reading in a UDP packet. All statements end with a semicolon _ this allows the CASL interpreter to tell where statements end, allowing the language to be almost completely whitespace insensitive. A variable is a name attached to some piece of information, be it the number "2" to an ICMP header. CASL statements are built out of a limited number of basic operations. 2.1.2 Types Like most scripting languages, CASL variables lack explicit "type"; a CASL variable can be a string, an integer, or a packet, without ever telling the 6 CHAPTER 2. WALKTHROUGH 7 CASL interpreter beforehand which of these the variable was intended to be. On the other hand, there definitely are a limited number of "types" of variables: characters, numbers, strings, buffers, and lists. Characters, numbers, and strings are straightforward. o A "character" is one specific ASCII character (e.g., the letter 'c', the 'BEEP' character, etc.). Characters in CASL are represented in single quotes. o Numbers in CASL are integral _ there are no floating-point, decimal- pointed numbers in the CASL language. All CASL numbers are greater than or equal to zero. CASL integers are represented simply as numbers, without quoting; they can also be specified in hexidecimal by preceding them with "0x", as in the "C" language. o Strings in CASL, unlike "C" and like Perl, are not arrays of characters. A string is any sequence of characters enclosed in double quotes, such as "hello, world!". Strings can also contain control sequences, which are backslash-quoted codes that represent things like new-lines and tabs. These codes correspond to the "C" language equivalents (e.g., '\n' is a newline, '\t' is a tab). o A buffer holds an arbitrary collection of data, and is typically used to represent input packets and protocol headers. Packet definitions are laid over buffers to allow easy access to header fields; this mimics the way packet construction is typically accomplished in "C". o Lists are also collections of data; however, unlike buffers, each distinct data element stored in a list is a seperate entity, which can easily be added or removed from the list. Lists grow and shrink dynamically, as elements are added or removed from them. A packet that is to be output onto the network is represented in CASL as a list. A packet that has been input from the network is represented as a buffer. 2.1.3 Comments Comments in CASL, which are ignored by the interpreter, are either single- line or multi-line. A single-line comment begins with "//". A multi-line comment begins with "/*" and ends with "*/". CHAPTER 2. WALKTHROUGH 8 2.1.4 Packets CASL does not have any "built-in" packet types; rather, it provides a means to define any arbitrary packet type. Even simple IP and TCP headers must be defined using the CASL language. Fortunately, you're not required to do any of this basic work; CASL is distributed with scripted definitions of the basic IP protocols. To gain access to these definitions, the scripts that define them must be "included" into your script. This is done with the include directive, exact as in "C": #include "tcpip.casl" This line adds the contents of the file "tcpip.casl" to the current script. "tcpip.casl" contains header definitions for the basic IP protocols. Defini- tions lay out exactly how parts of packets look, so that they can be written correctly to the network. For instance, a UDP header definition might look something like this: define udp { udp_source : 16 bits; udp_destination : 16 bits; udp_length : 16 bits; udp_cksum : 16 bits; } The define keyword tells the CASL interpreter that this statement de- fines a new protocol definition, named "udp". The actual definition is (and must be) enclosed in curly braces. The contents of the definition are a series of field definitions. Each field definition consists of the name of the field, followed by the size of the value of that field. For instance, "udp_length" denotes a 16-bit field, named "udp_length", that is 32-bits into the UDP header (it's preceded by the source and destination ports). Protocol structures are instantiated in CASL scripts using the new op- erator. A variable is created to hold the protocol header, and the header is assigned to it: udpheader = new udp; This statement creates a new variable named "udpheader", and assigns to it a newly created UDP header structure. To access the individual fields of the new structure, reference the variable and field name seperated by a dot: CHAPTER 2. WALKTHROUGH 9 udpheader.udp_length = 10; This statement assigns the value 10 to the "udp_length" field of the variable "udpheader". It's a checked error to assign to a field of a variable that is not that type of structure; for instance, we'd get an error if we tried to do: udpheader.tcp_length = 10; . . . as "udpheader" doesn't contain a field called "tcp_length". It's worth noting that the value "10" will be represented as a 16 bit number, and in network-byte order; no processing need be done before assigning simple numbers to protocol fields. 2.2 An Example Script But we're digressing. Back to our example. As we mentioned, the first thing most scripts will do is include the definitions for basic IP packets ("packets.casl" defines "template" packets for common IP packet header defaults. More about that later.): #include "tcpip.casl" #include "packets.casl" As we stated previously, the example we're giving is of a "stealth scan- ner". The simplest way to accomplish a silent TCP port scan is do what's called a "half-open port scan"; this involves requesting a new connection by sending a TCP SYN packet, waiting for the SYN+ACK response to that packet, but not completing the connection with the final client ACK packet. In order to do this, we'll need to sequentially send SYN packets to our target host, and wait for TCP responses. The response will either be a SYN+ACK packet, indicating that something was actually listening and willing to accept a connection for that port, an RST packet, indicating that nothing was accepting a connection for that port, or nothing, possibly indicating that something was filtering out our connection attempt. 2.2.1 Packet Output The first thing we need to do, then, is to create our SYN packet. This involves creation of both a TCP and an IP packet header. We can do both of these easily by using the predefined TCP "template" packet headers and CHAPTER 2. WALKTHROUGH 10 filling in the values we need to change (the TCP source and destination ports, and the IP source and destination addresses). For example: OurSYN = copy SYN; OurSYN.tcp_source = 10; OurSYN.tcp_destination = 2049; . . . this creates a new TCP SYN header, and assigns a source port of "10" (some bogus random number) and a destination port of "2049" (the TCP NFS port). A TCP header by itself is worthless; the packet needs an IP header, to tell it which host the packet is destined for. We create an IP header for a TCP packet much the same way we created the TCP header: OurIP = copy TCPIP; OurIP.ip_source = 127.0.0.1; OurIP.ip_destination = 127.0.0.2; Here, we assume we're scanning "127.0.0.2", from "127.0.0.1" (these are obviously contrived examples). Since we're using the TCPIP template header, the other header values are already filled in for us. These two headers together create a complete TCP connection request. To write them to the network, we need to combine them; we do this using a list variable: PacketList = [ OurIP, OurSYN ]; This creates a new list called "PacketList", and includes in that list our IP and TCP headers, in that order. The opening bracket starts the list, the closing bracket ends it, and individual values are seperated by a comment. This is a simple example; lists can contain many more values, and even other lists. This is also just one way to build a list; another is to build it incrementally, using the list operators: PacketList = PacketList push OurSYN; PacketList = PacketList push OurIP; This adds the TCP and IP headers to the list seperately, "pushing" each element onto the list. The last element pushed into the list will be the first element written to the list; the push operator duplicates the behavior of a computer "stack". CHAPTER 2. WALKTHROUGH 11 Packets are output onto the network with the ip_output() function. The function takes as it's sole argument a list representing a packet to be written to the network, and: ip_output(PacketList); . . . writes our packet to the network. A stealth scanner wouldn't be too useful if it could only tell us about 1 port. A good start might be to try all the "reserved" ports (those between "1" and "1023"), where many of the standard network services listen. To do this, we'll need to loop through each of these port numbers. The easiest way to loop between two fixed numbers is to use a for statement: for(i = 1; i < 1023; i = i + 1) { // stuff here } for statements are defined by three parameters. The first usually tells where to start counting from, the second tells how long to count for (here, "as long as the count is below 1023"), and the third tells how far to step forward each step. The actual statements to be executed by the loop are enclosed in curly braces. The variable "i" we've created acts as the counter, and we can see what count we're at within the loop by accessing it. So, to send connection requests to each of the reserved ports, we might do: for(i = 1; i < 1023; i = i + 1) { OurSYN = copy SYN; OurSYN.tcp_source = 10; OurSYN.tcp_destination = i; OurIP = copy TCPIP; OurIP.tcp_source = 127.0.0.1; OurIP.tcp_destination = 127.0.0.2; OurPacket = [ OurIP, OurSYN ]; ip_output(OurPacket); } CHAPTER 2. WALKTHROUGH 12 2.2.2 Packet Input This piece of code will actually send connection requests to each reserved ports _ this is all there is to it _ but we're still missing a big piece of the puzzle; we're not reading the responses, so we'll never know if the host answered the connection request. We do this using the ip_input routine. ip_input() takes two arguments. The first is a timeout, specified in mil- liseconds. This tells the interpreter how long to wait before giving up. The second argument is a "tcpdump" filter that specifies which kinds of packets we want to read. "tcpdump" filters, which are based off the filtering language of the pop- ular network diagnostic tool, are described in-depth in Appendix B. They're given to ip_input() as either a string or a list; the easiest way to create one is to build it from a list. For instance, to read the response to one of our packets, we might do: OurFilter = [ "src host ", 127.0.0.2, " and tcp src port ", i ]; This corresponds to a filter string of: "src host 127.0.0.2 and tcp src port 103" . . . when "i", the counter in our loop, is equal to 103. ip_input() is the first function we see that returns a value. We obtain the return value of a function by assigning the call of the function to a variable; for instance: ReadPacket = ip_input(2000, OurFilter); . . . says, "wait at most 2 seconds to read any packet matching the filter we just created, and assign the packet we read to the variable ReadPacket". If ip_input() fails to read a packet, it'll return the value "0"; otherwise, it'll return a buffer variable containing the packet it just read. Every time we call ip_input(), we must check to make sure it successfully read a packet; we do this by comparing the returned value to "0": if(ReadPacket == 0) continue; Notice that the comparison operator is two "="'s, not one; this is to distinguish it from the assignment operator. continue tells us to move forward in the loop we're in. Note also that, since we're only executing one CHAPTER 2. WALKTHROUGH 13 statement in this conditional, we don't need to use the curly braces. If we needed to do more than one thing inside the condition, we'd need to enclose the statements in braces. If ip_input is successful, it returns a complete IP packet. We want to see if the packet is a TCP SYN+ACK or a TCP RST. There are a number of things we need to check to do this. The first is the size of the packet; it must be large enough to contain at least an IP and TCP header. We do this using the size() function: if(size(ReadPacket) < size(ip) + size(tcp)) continue; . . . which tells the interpreter to keep moving in the loop if the packet we just read is smaller in size than the sum of the sizes of a TCP and IP header (in other words, if the packet is too short). Note that we're looking at the size of "ip" and "tcp", not "TCPIP" and "SYN"; "ip" is the actual definition of an IP header, and "tcp" that of a TCP header. Both are defined in "tcpip.casl". If we're sure the packet is large enough, the next thing to do is to extract the headers from it. This is straightforward: ReadIP = extract ip from packet; ReadTCP = extract tcp from packet; . . . each header is extracted with the extract operator, which removes the specified header structure from the buffer and leaves the remaining bytes. Once we extract the headers from the packet, we can look at the indi- vidual fields of the TCP header. We want to make sure the SYN and ACK fields are set, and that RST isn't. If this is the case, the port is actually accepting connections; otherwise, it probably isn't. if(ReadTCP.tcp_ack != 1 || ReadTCP.tcp_syn != 1 || ReadTCP.tcp_rst == 1) continue; This is a complicated-looking conditional; the "||" symbol is a logical "or", so this reads: "if the ACK flag isn't set, or the SYN flag isn't set, or the RST flag IS set. . . ". "!=" means "not equal". You can combine any number of conditions using the "||" operator. If we pass all these conditions, the port is alive, and we want to tell the user that this is the case. We do this using the "print" function, which takes as it's argument a list or string to print: CHAPTER 2. WALKTHROUGH 14 print("Port", i, "Alive"); This prints "Port 1022 Alive", if the counter "i" equals "1022". 2.2.3 The Finished Script Our complete script now looks something like this: #include "tcpip.casl" #include "packets.casl" for(i = 1; i < 1023; i = i + 1) { OurSYN = SYN; OurSYN.tcp_source = 10; OurSYN.tcp_destination = i; OurIP = copy TCPIP; OurIP.tcp_source = 127.0.0.1; OurIP.tcp_destination = 127.0.0.2; OurPacket = [ OurIP, OurSYN ]; ip_output(OurPacket); OurFilter = [ "src host ", 127.0.0.2, " and tcp src port ", i ]; ReadPacket = ip_input(2000, OurFilter); if(ReadPacket == 0) continue; if(size(ReadPacket) < size(ip) + size(tcp)) continue; if(ReadTCP.tcp_ack != 1 || ReadTCP.tcp_syn != 1 || ReadTCP.tcp_rst == 1) continue; print("Port", i, "Alive"); } CHAPTER 2. WALKTHROUGH 15 Try feeding this script to CASL, replacing the IP addresses with your own source address and the address of a machine you want to scan. If CASL is installed correctly and the network is working, it'll work. . . and this is less than 40 lines of code! For an even more interesting example, replace your source address with that of a neighboring machine; if you share an ethernet with that host, you can forge the entire scan from that machine, and still see the reponses. This is actually a fairly complex example of how CASL works. In many instances, you simply want to forge a packet to see if it crashes a machine, or passes through a packet filter. These types of scripts usually take only a few lines, and don't involve any of the programming constructs (like for() loops and if() conditionals) we've walked through. For more examples of simple and complex CASL applications, see the collection of example scripts distributed with CASL. The rest of this manual documents the CASL language and environment in detail. There are many, many more features CASL offers, including subroutines, additional network commands, and options for packet specification, which are described later in the manual. Chapter 3 Structure of a CASL Program 3.1 Statements and Expressions CASL programs consist, essentially, of statements. These statements in turn are composed from control constructs and expressions. A control construct is a statement that affects the manner in which the flow of the program goes; these include loops (while and for) and conditionals (if). Expressions are sentances in CASL that evaluate to some value; any expression can be assigned to a variable. Unlike many programming languages, CASL allows statements to be executed in global scope. This means that a user can create a program without ever creating any "routines". There is no need (and no support) for the use of an entry-point main() function in CASL. Variables in CASL are dynamically typed. Since they don't have a de- clared type, they don't need to be declared prior to use; you can make up any name at any time, assign to it, and you'll have created a new variable. There are five different variable types: character, integer, string, buffer, and list. 3.1.1 Characters Characters are single ASCII characters, and are represented in CASL lan- guage in single quotes; `a', `c', and '\n' are all characters ('\n' is the newl* *ine character; "\" quotes a character, usually forming a control character like newline.). 16 CHAPTER 3. STRUCTURE OF A CASL PROGRAM 17 3.1.2 Integers Integers are integral values equal to or greater to zero (never negative). They're represented internally as 32-bit quantities. In CASL scripts, integers can be represented as simple numbers, or, preceded by "0x ", as hexidecimal quantities. 3.1.3 Strings Strings are any number of characters enclosed in double-quotes, such as "hello world!". In the "C" language, strings are arrays of characters; CASL treats strings as built-in types, and not as arrays (much like Perl). 3.1.4 Complex Types (Buffers and Lists) The two complex types are "buffer" and "list". Both of these types of variables can contain many pieces of information; buffers express this infor- mation as a contiguous sequence of bytes, and lists as a discrete series of variables. Buffers are used primarily to hold packet structures and input packets. The bulk of all CASL statements are expressions. There are many, many different kinds of expressions; they include subroutine calls, mathematical functions, list operations, and buffer extraction. Assignments are also ex- pressions, and assignments therefore can be assigned to variables. For the purposes of control statements, the value "0" is equivalent to "false", and any nonzero value is equivalent to "true". 3.2 Syntax 3.2.1 Statements Almost all CASL code consists of statements. All statements, excepting the control constructs, are terminated with a semicolon. They're all case sensitive, and, with few exceptions, are whitespace insensitive. You can indent and space a CASL program in any way you see fit. A single statement can stand on its own. A collection of statements can be grouped together and treated as a single statement by enclosing the group in curly braces. CHAPTER 3. STRUCTURE OF A CASL PROGRAM 18 3.2.2 Comments One part of CASL code that is not technically a statement is a comment. Comments are remarks left in the CASL source code intended for the readers of the program, to document the source. Comments are ignored completely by the interpreter. Comments can be single-line or multi-line. A single-line comment is started by the "// " sequence, and continues to the end of the line. A multi- line comment starts with "/* ", and doesn't end until the interpreter sees the closing "*/ " comment. 3.2.3 Variables The most basic element of a CASL script is a variable name. Variable names always start with a letter, and consist of zero or more trailing letters, numbers, or the underscore "_" character. "foo", "bar_baz", "i", and "z1" are all valid variable names. "1a" and "a@b" are not. 3.2.4 Assignments Variable names are not valid until they are assigned to. The CASL as- signment operator is "="; an assignment expression takes the value of the expression to the right of the "=" and assigns it to the variable on the left. The variable assigned to needn't exist beforehand. For instance, "i = c" assigns the value of the variable "c" to "i"; "c" must exist beforehand, "i" doesn't necessarily need to. 3.2.5 The "copy" Operator Assignments of list and buffer types occurs by reference _ that is to say, assigning one buffer to another variable name doesn't copy the buffer, but rather causes the new variable to point to the same buffer as the one being assigned from. There are times when this is desireable; however, there are also many scenarios in which this is not the optimal behavior. Template packet assign- ment is a good example; modifying the "new" variable should not affect the original template. In order to get around this, CASL provides the copy operator. The copy operator takes one operand _ a data element of any type supported by CASL _ and returns a copy of the item. Copy works for lists, buffers, and all the basic types supported in CASL. CHAPTER 3. STRUCTURE OF A CASL PROGRAM 19 3.2.6 Math Most of the standard mathematical operations are supported by CASL. This includes addition, subtraction, multiplication, and division, represented re- spectively by "+", "-", "*", and "/". Math is usually used only in assign- ment statements; for instance, to increment the variable "i" by one, use, "i = i + 1". 3.2.7 Comparison Typically, a program will need to examine the value of an expression to determine what to do in a given situation; for instance, a program may do something if an ip_input() call doesn't read a packet. Several operators exist to test the value of an expression: o x > y "x is greater than y" o x < y "x is less than y" o x >= y "x is greater than or equal to y" o x <= y "x is less than or equal to y" o x == y "x is exactly equal to y" o x != y "x is not equal to y" The basic comparisons "!=" and "==" (inequality and equality) apply to all of the CASL types. Two strings can be tested for equality using the equality comparison operator, and two buffers can be tested for inequality with the appropriate operator. Data varying in size will always compare false, as will lists with differing numbers of elements. 3.2.8 Expression Syntax An expression enclosed in parenthesis "(" ")" is treated and evaluated as a single expression. Parens are useful to disambiguate complicated expres- sions, where the interpreter can become confused as to which words belong to which expression. For example, to compare the value of an assignment, the assignment expression should be enclosed in parens, like: if((i = 1) == 1) print(i); CHAPTER 3. STRUCTURE OF A CASL PROGRAM 20 Expressions can be inverted for comparison with the "!" operator; an expression preceded with a "!" evaluates false if its value is nonzero. For instance, to do something if "i" is NOT "1", you can do: if(! (i == 1)) print(i); Negation with "!" is most useful when comparing something to zero. "!z" evaluates true if "z" is zero. An easy way to combine these rules to see if a packet is read from ip_input(): if(!(packet = ip_input(2000, filter)) print("didn't get a packet"); You don't need to explicitly compare an expression's value to "> 0" to see if its nonzero (for instance, "if(i > 0)"). . . if the expression evaluates non* *zero, it'll evaluate true, and if not, it'll evaluate false. For instance: if(i) print(i); else print("i is zero"); . . . prints the value of "i" if "i" is not zero. 3.2.9 Boolean Conditions Within the context of a boolean expression, two special operators are pro- vided, the AND and OR operators. AND, represented as "&&", joins a series of expressions together and returns 1 if all of them evaluate true. OR, represented as "||", returns 1 if ANY of them evaluate true. AND and OR operators in CASL evaluate left-to-right. Both boolean conditions "shortcircuit"; as soon as a condition arises that satisfies the expression conclusively (a subexpression of an AND statement evaluates false, or a subexpression of an OR statement evaluates true), the boolean expression ceases to evaluate and program flow continues. For example: 1 && 3 && 2 . . . evaluates true, because all of the subexpressions evaluate true. On the other hand: CHAPTER 3. STRUCTURE OF A CASL PROGRAM 21 1 && 0 && foo(); . . . not only does not evaluate true (the AND statement groups "0" into the expression, which evaluates false), but also never causes "foo()" to be called, because the expression is short-circuited after the second subexpres- sion. 3.2.10 Control Control statements affect the flow of control of a program. Control state- ments are either loops, which cause a piece of code to be executed zero or more times, or conditionals, which cause a piece of code to be executed only if the condition is satisified. Note that none of the control statements are terminated with a semi- colon; rather, control statements operate on other statements. 3.2.11 Loops While A while statement represents a loop that is not implicitly terminated. while loops execute their bodies until their conditional argument is satisfied. A while loop looks like this: while(conditional) statements Where conditional is an expression, and statements is a statement, or a group of statements enclosed in curly braces. For instance, while(i > 0) i = i - 1; . . . is an example of a while loop. For A for statement represents a loop that usually has implicit termination. for statements consist of three parts: an initializer, a conditional, and an iterator. The initializer is intended to set up a counter or some other place- holder variable for the loop. The conditional works the same way a while conditional works, terminating the loop when the condition evaluates false. CHAPTER 3. STRUCTURE OF A CASL PROGRAM 22 The iterator is intended to move the loop forward, typically advancing or decrementing a counter. Each part of a for statement is seperated by a semicolon. A very common error is to seperate these parts with commas. Watch out. An example of a for loop is: for(i = 0; i < 10; i = i + 1) print(i); . . . this example executes the code print(i) ten times, starting with "i" equal to zero (outputting "0"), and executing the last statement with "i" equal to "9", and terminating when "i" evaluates to "10". for(;;) is a legal statement representing an infinite loop. Loop Control While in the body of a loop, control can further be affected by either the loop terminator or the loop continuer statements. Loops can be immediately terminated by executing the break statement, and can be continued to the next iteration with the continue statement. For instance, for(i = 0; 1; i = i + 1) { if(i != 4) continue; if(i == 4) break; } . . . sets up an infinite loop (note that the conditional, which is the seco* *nd part of for's argument, will never evaluate false), but terminates the loop explicitly if the counter ever reaches the value "4" The continue statement is wholly redundant here, and meant for illustration; if the counter is any value other than "4", the loop continues to move forward. Loop control statements are only valid within loops. Its illegal to attempt to execute a break or continue when you're not in a loop. Remember, if conditionals aren't loops, and remember that the control statement affects the closest loop, so: for(;;) while(1) CHAPTER 3. STRUCTURE OF A CASL PROGRAM 23 if(c == 1) break; . . . the continue here affects the while, not the for, and is valid because it's executed while at least one loop is in effect. On the other hand, if(1) break; . . . is NOT valid, because no loop is present. 3.2.12 Conditionals The CASL conditional statement is if. if executes its body of statements if the conditional argument evaluates true; for instance: if(i == 1) { print(i); print("done"); } . . . executes the code in the body of the conditional if "i" is equal to "1* *". Code can also be executed if a loop evaluates false; this is done with an else extension. The body of the else is executed if the condition of the if is false. For instance, if(0) print("foo"); else print("bar"); . . . prints the string "bar" (the "0" conditional always evaluates false). if/else statements can be chained indefinitely, with else if. For instance: if(i == 1) print(``foo''); else if(i == 2) print(``bar''); else if(i < 4) print(``baz''); else print(``quux''); . . . prints "foo" if "i" is 1, "bar" if "i" is 2, "baz" if "i" is 3, and "q* *uux" if "i" is any other value. CHAPTER 3. STRUCTURE OF A CASL PROGRAM 24 3.2.13 Subroutine Calls A subroutine call diverts control to the code in the named subroutine. It passes arguments to that subroutine, to affect how that subroutine executes. When the routine finishes, it returns a value, which can be obtained by assigning the subroutine call expression to a variable. The syntax for a call is "function(argument0, argument1, argumentN )", where "function" is the name of the function (e.g., "ip_input"), and argu- mentX is the argument at position X. For example, if "foo" is a function that takes as an argument a value, and has as a return value that same value plus one, the following statement: - i = 1; i = foo(i); print(i); " . . . prints the value "2". Chapter 4 Lists One of the most important constructs in the CASL language are lists. Lists represent collections of data, composed of individual variables, which can grow or shrink dynamically. Lists are used to represent complicated strings, packets, and many other things in CASL programs. They can also be used as data structures for CASL programs, like stacks and queues. 4.1 Creation Lists are initialized in one of two ways. The first, and most common, is to explicitly create a list using the list composition operators "[" and "]". The square brackets enclose a comma-seperate list of elements, and this expression as a whole defines a new list. For instance, [ foo, bar, baz, 1 ] . . . is an expression defining a list containing the variables "foo", "bar", and "baz", as well as the number "1". The other way to create a new list is to use a list operator to explicitly assign an element to it. This is done by assigning the name of the list to an expression with a list operator operating on that name and inserting a new element; for instance: list = list push foo; . . . is an expression that creates a new list, called "list", containing so* *lely the element "foo". 25 CHAPTER 4. LISTS 26 4.2 Recursion Lists can contain any variable, including other lists. Lists can nest ind* *efi- nitely. Routines that act on lists expand all elements from all lists in * *the order it encounters them, so: [ "foo ", "bar ", [ "baz ", "quux " ], "zarkle" ]; . . . defines a string list that will evaluate to: "foo bar baz quux zarkle". When stepping through a list with list operators, an element of a list that is itself a list will be returned as the entire list, not the first e* *lement of the list. So, the same string list above processed with this statement: - list = [ "foo ", "bar ", [ "baz ", "quux " ], "zarkle" ]; x = pop list; y = pop list; z = pop list; print(z); " prints the string "baz quux ", because the value of "z" is equal to the third element of the list "list", which is itself a list containing two va* *lues. 4.3 List Operators There are four list operators. Two of them add elements to the list, and t* *wo of them subtract elements from a list. The list operators are: prepend add an element to the head of the list append add an element to the tail of the list head take an element from the head of the list tail take an element from the tail of the list head and tail operate on a list, evaluating to the element removed from the list. An example of a head or tail statement is: CHAPTER 4. LISTS 27 - list = [ foo, bar, baz ]; x = head list; print(x); " . . . which prints the value of "foo", the first item (the "head") of the li* *st. prepend and append operate on a list and an element to add to that list. If the list referred to doesn't already exist, it is created. An example of an append or prepend statement is: - list = [ foo, bar ]; list = list append baz; print(list); // list is now [foo, bar, baz] " . . . which prints the values of "foo", "bar", and "baz". The commonly used computer "stack" terms, push and pop, are aliases for prepend and head, respectively. 4.4 List Control CASL also provides a control structure for iterating through lists. It's fore- ach, and it's function is to step through each element of a list. A foreach statement has two parts; a "binding name" and the list to operate on. For each element of the list, the "binding name" is set to refer to that element. An example of a foreach statement is: - list = [ foo, bar, baz ]; foreach element [ list ] - print(element); " " . . . which would print, in order, the values of "foo", "bar", and "baz". The looping control statements continue and break function as expected in list loops. Note that list expansion within foreach is recursive; a list containing other lists will be expanded to all enlisted data elements. Chapter 5 Packet Headers An extremely common operation of a CASL script is to create a packet consisting of a series of protocol headers, each of which has a fixed format. Fixed-format protocol headers can be defined in CASL with the protocol structure construct, which lays out bit-by-bit the order and contents of a protocol structure. 5.1 Definition Protocol structures are defined by define statements. A define statement creates a new structure with a specified name, which consists of a curly-brace enclosed definition. The definition is in turn composed of field specifiers which dictate the name, length, and order of the protocol fields. A basic protocol structure definition is: define foo - // contents here " . . . which creates a new structure named "foo". "foo" is currently mean- ingless, because it hasn't defined any fields. A more concrete example would be: define ip - ip_version : 4 bits; ip_headerlen : 4 bits; ip_tos : 8 bits; ip_length : 16 bits; 28 CHAPTER 5. PACKET HEADERS 29 ip_id : 16 bits; ip_df : 1 bit; ip_mf : 1 bit; ip_offset : 14 bits; ip_ttl : 8 bits; ip_protocol : 8 bits; ip_cksum : 16 bits; ip_source : 32 bits; ip_destination : 32 bits; " . . . which defines an IPv4 header. Each specifier enclosed in the braces denotes a field of the structure. Each consists of a name, a colon, and a size. The name can be any valid variable name. The size can be specified in terms of any number of bits, bytes, "words", and "dwords". "words" are 16 bit quantities, "dwords" are 32 bit quantities. Protocol structure definitions can mix any combination of sizes specified in bytes, bits, word, or dwords. 5.2 Instantiation A new instance of a protocol structure is created by assigning it's name to a variable with the new operator. This creates a buffer large enough to hold that structure, with all fields of the structure set to "0". Assignment of a buffer to another variable copies a reference to the buffer, so in the statement: - x = new ip; y = x; z = y; " . . . "x", "y", and "z" all refer to one copy of the "ip" structures. In order to create a copy of a structure, use the copy operator, which creates a logical copy of it's operand. For instance, in: - x = new ip; y = copy x; " CHAPTER 5. PACKET HEADERS 30 . . . x and y are both independant copies of an IP header. 5.3 Field Reference Individual fields of a structure are referenced with the field referenc* *e operator ".". For instance, if "x" is an "ip" structure, "x.ip_ttl" refers to th* *e "ip_ttl" field of "x". Any number can be assigned to a protocol structure field. Numbers wi* *ll be packed in Internet byte order into the field, and will only use as m* *any bits as the field is large. It's an unchecked error to try to stuff a v* *alue that is too large for a field's size; if "foo" is a field that is 1 bit wide* *, "x.foo = 4" results in undefined behavior. 5.4 Special Fields Every buffer variable has 4 special fields that reference arbitrary loc* *ations within the buffer. These fields are "bits", "bytes", "words", and "dwor* *ds", and they're specified with ranges corresponding to how many of these un* *its are being referred to. The syntax of a direct memory reference of a structure follows these examples: z.bits[x .. y]bits "x" through "y" of the buffer "z" z.bytes[x .. ]bytes "x" through the end of buffer "z" z.word[x]word "x" of buffer "z" These expressions all evaluate to integer numbers, and can also be a* *s- signed to. For instance, z.bit[10] = 1; . . . is a valid CASL expression that sets bit "11" (counting from 0* *) of the buffer "z" to 1. 5.5 Buffer Size Since buffers can represent an arbitrary amount of data, their size mus* *t be obtained explicitly using the size function. size evaluates to the siz* *e, in bytes, of it's argument. The statement: CHAPTER 5. PACKET HEADERS 31 - x = new ip; print(size(x)); " . . . prints "20", the size in bytes of an IP header. 5.6 Structure Extraction A buffer can contain several structures. Each individual structure can be obtained by extracting data from the buffer with the extract operator. Extract is specified either as: foo = extract bar from baz; . . . which extracts a "bar" structure from the buffer "baz", leaving the remaining bytes in "baz", or as: foo = extract z bytes from baz; . . . which extracts "z" bytes from "baz", leaving the remaining bytes. Chapter 6 Subroutines One of the most powerful features of any programming language is the ability to divide a task into small, reusable components. Small software components are usually expressed as "functions"; a function takes a number of arguments and produces a result depending on those inputs. The proper use of functions not only makes code far more readable, but also allows significant amounts of code to be re-used from project to project. 6.1 Declaration Subroutines are defined with the proc keyword. A subroutine takes a fixed number of arguments, and optionally returns a value. Subroutines can be defined anywhere and do not require prototypes, as they do in the "C" language. To declare a new subroutine, use the proc keyword as in this example: proc foo(arg1, arg2, argN) { // statements } where "foo" is the name of the new function, "argX" specifies the name of the argument at place "X", and the body of the function appears between the braces. Within the body of the function, the variables named "argX" are replaced by the value of the arguments passed at place "X". So, for instance, to declare a function called "foo" that takes an argument named "x" and adds "1" to it, do: proc foo(x) { 32 CHAPTER 6. SUBROUTINES 33 x = x + 1; print(x); " 6.2 Argument Passing An argument specified in a function's declaration is called a "formal argu- ment". The name of this argument is available to all the statements executed in the body of this function. An argument passed to a function in a sub- routine call is called a "calling argument", and it's value is made available through the name of the corresponding formal argument. Argument passing in CASL is, with one exception, "by value". This means that the formal argument is bound to the VALUE of the calling argument, not the actual calling argument. For example, in the above- mentioned procedure "foo", the addition of "1" to the argument "x" is never seen by the caller of "foo" _ it affects only the variable "x" within the function "foo". The sole exception to this is structure and list passing; references to lists and structures are passed, and changes to them affect the variables on the caller side as well as within the body of the subroutine. This is intended to make it easy to write routines that set fields within structure headers, or change the order of packet lists. 6.3 Variable Argument Lists In many cases, it is desireable to create procedures that take a variable number of arguments. CASL directly supports this functionality, using the list type. A variable-argument function is defined as one that can take more calling arguments than formal arguments. In this case, the final formal argument becomes a list of all the extra calling arguments. For example: proc foo(x) { ... } foo(i, j, k); . . . defines a function called "foo", which can take a variable number of CHAPTER 6. SUBROUTINES 34 arguments. The function call to foo() specifies three arguments, while the definition specifies one, so "x" becomes a list containing i, j, and k. 6.4 Return Values Subroutines end either when the curly brace is reached, or when control reaches a return statement. A return statement ends the execution of a subroutine, and causes the subroutine call to evaluate to the value specified as return's argument. For instance, to make "foo" return the value it calculated, change the above function to: proc foo(x) { x = x + 1; return(x); } A call to "foo" will evaluate to the argument passed to foo, plus "1". Any variable can be returned through the return statement. Multiple values are returned from a function using list variable returns. 6.5 Scope An important issue in programming languages with variables and routines is that of variable "scope". The scope is the space within which a variable is valid. When a program is executing within a subroutine, any variables it defines are accessible only within that execution of that subroutine; the caller of the subroutine cannot access variables defined in the subroutine. Code that is not executing within a subroutine is in "global" scope. Variables defined in global scope are accessible ANYWHERE, even within subroutines. For example: i = 1; // global foo(i); proc foo(x) { x = x + 1; // local, "x" can only be accessed within "foo" y = i; // "y" is local and can only be accessed within // "foo", but "i" is global and can be accessed // anywhere. CHAPTER 6. SUBROUTINES 35 return(x); } Chapter 7 Network I/O CASL supports two basic primitives for network I/O: IP output and IP input. IP output writes a complete IP packet, including the IP header, to the network; likewise, IP input reads a complete packet, starting with the IP header, from the wire. 7.1 Output IP output in CASL is accomplished via the ip_output() routine. ip_output takes as an argument a list of data elements that are expected to comprise an IP packet; a single buffer variable can also be passed to ip_output for writing as well. Successfully sending a well-formed IP packet involves some tricky issues. Two of these are checksum and length calculation. The IP and transport headers require knowledge of the length of the entire packet, the lengths of the individual headers, and the calculation of a checksum over some of these headers and some of the data. Both of these issues can be resolved by writing CASL code to com- pute checksums and lengths. However, this code can potentially be cumber- some and error-prone. Rather than requiring the implementation of CASL- scripted checksum and length calculation, the CASL interpreter provides a few shortcuts to solve these issues transparently. For the basic IP protocols (IP, TCP, UDP, and ICMP), the CASL in- terpreter will automatically calculate checksum fields, packet lengths, and header lengths. The appropriate values will be filled in before the packet is written to the wire; the computed values will NOT affect the passed-in data, only the packet written to the wire. 36 CHAPTER 7. NETWORK I/O 37 In order to allow for arbitrary packets (possibly with intentionally bad header values) to be sent, CASL will not touch header fields it thinks have explicitly been filled in. For the basic IP protocols, this means that CASL will not fill in values for fields that already have nonzero values. 7.2 Fixups In many cases, it may be important to fill in the variable header fields of an IP datagram without outputing it to the network. This is a common re- quirement of IP fragmentation code, for example. CASL supports this with the ip_fixup() procedure. ip_fixup() takes the same arguments as ip_output(), but, instead of outputting the packet to the network, it returns a new packet, which is a copy of the input with the appropriate header fields filled in. 7.3 Input Packet input in CASL is done using the ip_input() routine. Ip_input takes as arguments a timeout value, specified in milliseconds, and a "tcpdump" filter. The timeout specifies how long to wait for a packet before giving up, and the filter defines which packets to read. If the millisecond timer runs out before a packet is read, ip_input returns the integer value "0". If a packet is read succesfully within the alloted time, it is returned, minus the link-layer (Ethernet) header, as a buffer. The size of the buffer can be queried with "size()" to determine the length of the inputted packet. 7.4 Filtering CASL also allows the explicit setting of global filters that affect all reads by using the "ip_filter()" routine. Ip_filter takes as an argument a "tcpdump" filter, through which all packets read by CASL must successfully pass before being returned via ip_input. On some computer architectures, notably 4.4BSD, "ip_filter()" also sets kernel packet filters. The enabling of a kernel packet filter prevents the CASL interpreter from reading packets the programmer does not intend to have read; this can be a major performance benefit, as it prevents the CASL interpreter from needing to explicitly filter out spurious packets. Chapter 8 Built-in Functions In addition to the language and network I/O functionality supported im- plicitly by CASL, the interpeter includes a variety of built-in functio* *ns to support functions that can't easily be performed directly by the langua* *ge. What follows is a brief overview of the built-in functions provided * *with CASL. print()The print() function takes a list of data elements to write to sta* *ndard output. It writes each of these elements, seperated by a space, to standard output, followed by a newline. checksum() The checksum() function takes a list of data elements to perform an Internet checksum on. It returns an integer representing the check* *sum of these elements. timer_start()The timer_start() function starts a stopwatch timer in the CASL in* *ter- preter. It returns a descriptor number, which can be used to retri* *eve the amount of time that has elapsed since the timer started. timer_stop()The timer_stop() function takes a descriptor number as an argument, stops the stopwatch timer associated with the descriptor, and retu* *rns the number of milliseconds that have elapsed since the timer was started. tobuf() The tobuf() function takes a list as an argument, and returns a bu* *ffer containing the ordered contents of that list. atoi()The atoi() function takes a string as an argument, and returns the integer represented by that string. 38 CHAPTER 8. BUILT-IN FUNCTIONS 39 wait() The wait() function takes an integer as an argument, representing * *the number of seconds for the interpreter to wait before continuing. getip()The getip() function takes a string as an argument, and returns a number representing the IP address contained in that string. putip() The putip() function takes a binary IP address as an argument, and returns a string representing that IP address. open() The open() function takes a filename as an argument, and returns a descriptor number that can be used to manipulate that file. If the* * file does not exist, it will be created; if it does, it will be appende* *d to. If the file cannot be opened, "0" is returned. close()The close() function takes a descriptor number as an argument, and closes the associated file, flushing any pending output and preven* *ting further manipulation of the file. read() The read() function takes as arguments a descriptor number and a count of bytes to read. It reads at most the specified number of b* *ytes from the file, and returns a buffer containing those bytes. The nu* *mber of bytes actually read by the file can be queried with the "size()" command; if no data was read, "0" will be returned. write()The write() function takes as arguments a descriptor and a data el* *e- ment (which can be a list or a buffer, or any of the basic types) * *to write to the file matching that descriptor. The number of bytes written * *to the file is returned. fgets()The fgets() function takes as arguments a descriptor and a number representing the maximum number of characters to read from a file. It then reads at most that many characters, stopping when a line terminator (the newline character) is found. It returns the data r* *ead, or "0" if nothing was read. rewind() The rewind() function repositions the offset into the descriptor g* *iven as an argument, so that it points to the beginning of the file. T* *his allows the same data to be read from the same file descriptor twic* *e. fastforward()The fastforward() function repositions the offset into the descrip* *tor given as an argument, so that it points to the end of the file. T* *his allows recovery from rewind(), for further writing. CHAPTER 8. BUILT-IN FUNCTIONS 40 remove() The remove() function deletes the specified file from the system, re- turning "1" if successful.hose hosts are vulnerable to those attacks. CASL is particularly oriented towards low-level network attacks which require packet forgery. All told, CASL provides an extremely flexible and general way to ma- nipulate networks. Its presentation as a programming language allows it to accomplish a virtually limitless number of tasks, and its protocol spoofing capabilities provide a means to do things that require hundreds of lines of "C" code in less than 10 lines of CASL code. 1.2 Why Is CASL?