Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASM

A Platform for Unrestricted Low-level Hardware Programming and Experiments (NakedCPU)

4.90/5 (27 votes)
9 Aug 2012CPOL27 min read 44.9K   16  
This article presents an experimental platform consisting of two computers to conduct hardware programming and research, unrestricted by any Operating System.

NakedCPU platform

Figure 1. NakedCPU and master computers connected via serial interface. NakedCPU does not need a keyboard or a monitor.

Foreword

These days hardware is hidden beneath a thick blanket of Operating System's code. Yet, for me and many other folks, according to Google searches, a possibility to reach and experiment with hardware would be a very exciting opportunity. I have created an exploratory platform consisting of two Intel-based computers connected via RS232 interface. One computer, the Master, is running Windows and is used to control the second computer. The second one, the NakedCPU, runs essentially without any operating system, hence it is available to truly low-level experiments. My platform was featured on the cover of Circuit Cellar Magazine, issues 259, 260. Current article is an adaptation of the original publication, which is available on my web site.

Introduction

It seems that experimentation with a PC is limited to developing high-level code software with the aid of numerous libraries and technologies hiding the hardware beneath layers and layers of code. Rarely limited experimentation with PC hardware is possible, however one has to install drivers allowing some access to hardware, because OS naturally does not permit us to do any low-level activities. The sad part is that such drivers are mysterious themselves. It is safe to say that the hardware programming was well known to many computer professionals and enthusiasts in the 80-s. Later the people forgot about it, while the technology has tremendously leaped ahead. In this article, I try to bridge the gap in time and to revive the interest in hardware programming based on the state of the art technologies and concepts. There is a Russian saying: "Everything new is actually well-forgotten old".

This article is a result of my interest in the Intel CPU, chipset, I/O controller and other essential PC devices from the perspective of low-level hardware programming unobscured by an operating system and drivers. The motivation for this project was to reach out to people with inquisitive minds who would appreciate a possibility to directly experiment with the CPU, chipset and other hardware. Here I present NakedCPU: a facility providing full access to hardware and CPU without any restrictions imposed by the operating system. Importantly, the processor will not be obscured by Linux, DOS or Windows, and it will be operating in its most interesting and powerful regime - the protected mode. In the text, the users are referred as inquirers, because NakedCPU is made for researchers, i.e. devoted geeks, rather than regular users.

The other goal of this article is to provide the inquirers with a roadmap into navigating hardware documentation, which is confusing and difficult to find otherwise. I did not want to retell the documentation, because many computer concepts and technologies quickly become obsolete. With the roadmap, however, it will be easier to follow the newer technologies and documentation.

Interesting Facts

Let us think for a moment about one of the modern Intel CPU varieties, for instance Intel Core 2 Duo. Impressively, this processor is capable of consuming up to 75A of current [1]! Also, it is not a simple processor: its documentation consists of 5 volumes with the total page count of approx 4200 pages [2]. Intel CPU does not operate alone: it is interfacing a chipset, i.e. a Graphics and Memory Controller Hub (GMCH). The chipset on the other side is connected to an I/O Controller Hub (ICH). Interestingly, this arrangement is analogous to our nervous system with brain, brain stem and a spinal cord. GMCH and ICH are processors themselves, containing hundreds of configuration and control registers. The documentation on GMCH and ICH spans over 1400 pages [3, 4]. No wonder why operating systems hide actual hardware under a thick blanket of intermediate code!

Working Principle of the NakedCPU

NakedCPU is an experimental platform exposing the hardware internals of a PC. Experimentation with NakedCPU requires two computers (Figure 1). One is the master computer having Windows and Visual Studio software, whose job is interacting with us and the second computer. The second computer, i.e. the NakedCPU, is connected to the master via RS232 interface.The NakedCPU computer is booted up with a small startup code (provided here), which enables it to communicate via RS232 with the master. Upon startup, the NakedCPU is expecting two separate packages of bytes: one is a stream of Intel CPU opcodes to be executed (i.e. the executable) and the other one is the data to be processed. The executable can modify any part of memory, chipset registers, etc, and even overwrite the startup code. In other words, the freedom is yours.

Starting up NakedCPU

NakedCPU will not be alive without some sort of a startup code. At startup we have to accomplish two tasks: switch the CPU into the Protected Mode and begin listening on the serial port for two packets of bytes: executable and data. In order to supply NakedCPU with a startup code, the easiest way is to prepare a bootable floppy disk with our own code. Certainly, one can also put this code into a hard drive. The startup code (up to 512 bytes) is written in assembly language and must be stored in the sector 0, i.e. the Master Boot Record (MBR) of the disk. It is difficult to use assembly language compilers and linkers such as MASM, since they tailor the executable to a particular OS. There is, however, a binary editor HexIt [5], which among other things allows direct conversion of assembly commands into binary code. Using this editor, a binary file of the future MBR was created. The content of this file can be seen in Appendix. "Anatomy of MBR" provides detailed dissection of the content. A small utility "Firstsectwrite.exe", see Appendix, was written to transfer this file into sector 0 of the disk. Although the code of this utility is quite simple, it deserves some attention. A Windows API call

C++
CreateFile(TEXT("\\\\.\\A:")...)

opens raw communication with a disk, in this case a floppy drive A, to allow writes into the sector 0. It is important to note, that this call will be only successful under the administrator account. In this paper, a Dell Optiplex 760 computer was used to conduct the experiments. It had a floppy drive attached via USB and BIOS startup options allowed to boot up the computer from such a drive.

In this paper, a Dell Optiplex 760 computer was used to conduct the experiments. It had a floppy drive attached via USB and BIOS startup options allowed to boot up the computer from such a drive.

NakedOS

It may sound contradictive to the spirit of the article to be OS-free, however NakedCPU is booted up with a tiny (262 bytes long) 32-bit "operating system", NakedOS, which makes the NakedCPU capable of communicating with the outer world via serial port. In fact, we did not compromise our principles of truly free exploration, because NakedOS is absolutely transparent and its code completely presented in the Appendix. NakedOS defines several memory segments (Table 1), which are useful as initial environment for the inquirer's executable. Intel documentation [2] provides explanation for protected mode memory segments, Global Descriptor Table (GDT) and Interrupt Descriptor Table (IDT). In addition, NakedOS defines two software interrupts and a base vector for hardware interrupts. Note: the INT interrupts have nothing to do with DOS or BIOS; they are solely defined by our code.

Table 1. Memory segments and interrupts defined by NakedOS.
Segment Base Size Descriptors, type
Extended memory 0x100000 ~ 128Mb 0x28, data
Screen, character mode 0x0B8000 4Kb 0x20, data
Target executable 0x93B 64Kb 0x30, code 32;0x38, data
NakedOS 0x800 315 bytes 0x10, code 32
Stack 0x400 1024 bytes 0x18, stack 32
System data
IDT: 0x3FF-0x200
GDT: 0x1FF-0
0x0 1024 bytes 0x8, data

Interrupts Info
INT 0x20 Read a packet from serial port; destination ES:[EDI]; mandatory condition DS=ES. First 4 bytes of the packet indicate in bytes the length of the subsequent string. Upon return, ECX contains the number of bytes received.
INT 0x21 Send to serial port a string of ECX bytes, located at DS:[ESI].
IRQ0 Hardware interrupts base vector is 0x28.

Immediately after start, NakedOS is expecting two transactions: one for the executable code and another for data. Each transaction is a stream of bytes sent over the RS232 (see Figure 2). The first transaction is written into the memory segment "Target executable", while the second transaction goes into the "Extended memory" segment. After completion of the second transaction, NakedOS transfers control to the executable by a long jump:

ASM
jmp   00030:000000000

From that moment, in principle, any memory occupied by NakedOS can be overwritten by activities of the inquirer's executable. The hardware interrupts are normally masked when NakedOS is running; however the 8259 interrupt controller is set up (see additional file MBRListingNakedOS.doc in the download archive) to handle the interrupts if the inquirer decides to unmask them. Detailed instructions on programming the interrupt controller are provided in the documentation for the I/O Controller Hub (ICH) [4].

Figure 2. Format of NakedOS transactions. First 4 bytes indicate the length of the subsequent byte stream.

Image 2

NakedCPU Explorer and OtherExecutables

The important issue remains how to send an executable code to NakedCPU to conduct experiments. Recall the beginning of the article, where it says that two computers are involved. The master has a Visual C++ project, NakedCPU Explorer, which acts as a "shell" allowing inspection and modification of chipset registers and memory. The code defines a class having a constructor, which provides __asm{} brackets to be filled up by the inquirer with executable code:

C++
class Ports : public NakedCPUcode
{
    public:
    Ports()
    {
        DWORD pe, ps;
        __asm
        {
                mov pe, offset end      //save end and start of the code
                mov ps, offset start    //to be sent to the NakedCPU
                jmp end                 //master jumps over the NakedCPU code
        ///////////////////////////////////////////
        start:  mov ax, 0x28            //loading
                mov es, ax              //data
                mov ds, ax              //and stack
                mov ax, 0x18            //segment registers
                mov ss, ax              //initializing
                mov esp, 0x3fe          //stack pointer
                xor edi, edi
                mov eax, 'OLEH'         //NakedCPU Explorer says HELO
                stosd
                xor esi, esi
                mov ecx, 4
                INT 0x21
                ...                     //here goes rest of the code
                _emit 0xEA
                _emit 0x00
                _emit 0x00
                _emit 0x00
                _emit 0x00
                _emit 0x10
                _emit 0x00
        end:    nop
        }
        if(!PrepareCode(ps, pe)) delete this;
    }
};
Figure 3. A fragment of a constructor for a class derived from NakedCPUcode.

Since Microsoft Visual C++ is running on the master PC having an Intel CPU, the compiler will translate the assembly code into appropriate opcodes, which are naturally suitable for the NakedCPU! Specifically, this class is derived from another class, NakedCPUcode, which performs a preparatory work by extracting the opcodes produced from the code in the __asm{} brackets and making them available for sending over to the NakedCPU. Note, NakedCPU only receives the code between "start" and "end" labels. It is important to understand that the master computer will not execute the code in the __asm{} brackets, it simply jumps over it. The strange keyword "_emit" allows placing opcodes directly by their hexadecimal values - for some reason a long jump is not permitted when using Visual Studio compiler.

Any other executable code, besides the NakedCPU Explorer, can be prepared and sent to the NakedCPU computer. Actual sending of the NakedCPU Explorer code is accomplished the following way:

C++
Ports ncd;
SerialComm Com1;
if(!ncd.UploadEx(&Com1))
throw 1;

The project also defines a class SerialComm and a function SendNakedCPUdataRecvResponse to send and receive data. It is worthwhile to examine the straightforward code of the project to understand the details of communication with NakedCPU. Besides serving as an example, NakedCPU Explorer sends an executable to NakedCPU, which permits interactive examination and modification of various chipset and I/O controller registers. NakedCPU Explorer offers eight commands "write", "write32", "read", "read32", "pci", "memread", "memwrite" and "quit". First four commands will ask for a port address, i.e. an address in the CPU I/O space. With these commands, NakedCPU will write to and read from a GMCH or ICH register, one or four bytes. The fifth command will ask for Bus (decimal), Device (decimal), Function (decimal) and Register (hexadecimal) values. The values will be packed into the port 0xCF8 to open a "window" into the PCI configuration space, accessible via port 0xCFC. Details on addressing PCI devices are provided in the chipset documentation [3]. Memread and memwrite allow reading and writing double words from and to the memory respectively.

Note: NakedCPU Explorer does not use any hidden "helper" drivers or libraries. The code is small and entirely transparent for the inquirer's perusal.

Experiments

The following sections describe experiments with direct access to the hardware and CPU.

Making noise

Although it may sound trivial, making a PC speaker to produce sound involves understanding of timers and some low-level work. Ironically, there seems to be no way to make a speaker beep using Windows API on Vista and XP 64-bit version, because Microsoft decided that the speaker hardware is obsolete [7]. Certainly, in the past, the DOS programmers must have known how to do it, but now it seems to be forgotten. Reading ICH documentation [4] and conducting a few experiments resulted in the following protocol:

>write
Port address: 0x61
Value: 0x3
Speaker enabled. NakedCPU begins producing a continuous sound.
>write
Port address: 0x43
Value: 0xbe
>write
Port address: 0x42
Value: 0x10
>write
Port address: 0x42
Value: 0x1
Change the default frequency by writing a timer configuration word. Sound stops, because timer is expecting to receive a two-byte divisor. Write a new value 0x0110 in two transactions, after which NakedCPU will begin producing a high-pitch noise.
>write
Port address: 0x42
Value: 0x0
>write
Port address: 0x42
Value: 0x20
Write a larger divisor 0x2000 to lower the frequency. Writing the configuration word is not necessary.

Lighting an LED

Parallel port is becoming more and more obsolete, nevertheless it offers a possibility to read and send data over 8 lines. Strangely, ICH documentation does not say anything about programming a parallel port. Browsing Internet reveals that there is still some interest with regard to the parallel port and programming information is available. Connect an LED to the D2 port line via 470 Ohm resistor and follow the protocol below, which demonstrating writing to and reading from the parallel port.

>write
Port address: 0x77A
Value: 0x34
>write
Port address: 0x378
Value: 0x4
Parallel port turns into Extended Capabilities Port mode via modification of the Extended Control Register (0x77A). Line D2, goes HIGH and LED glows.
>write
Port address: 0x77A
Value: 0x34
>write
Port address: 0x37A
Value: 0x2c
Reading from LPT is also possible. By writing 0x2c into the Port Control Register (0x37A), we make parallel port read. The LED slightly glows indicating that pull up resistors are enabled in the reading mode.

The "first cry"

Which instruction the processor executes first after power on? Documentation [2] says that the processor reads its first instruction from the address 0xFFFFFFF0, i.e. 16 bytes below 4GB. Attempting to examine this address with a debugger is fruitless (tested, did not work). In order to reach this high address, which is in the range of high BIOS, a small executable for the NakedCPU was prepared. The executable defined a segment of memory addressing high BIOS and sent the content of the 16 bytes below 4GB back to the master computer. Quite expected, there was a short jump, approximately to 30KB below. The executable was modified to download the entire chunk of memory 30KB below 4GB up to the top. Especially curious inquirers are welcome to investigate the content, which was saved and available for download. As a general impression, one can see many accesses to PCI bus and calls for CPUID instruction. It certainly makes sense, because various devices have to be set up and BIOS is attempting to determine which processor is being used.

Network

Communication via network is accomplished using the Media Access Controller (MAC). Documentation is available at the Intel web pages. It is also helpful to read first three chapters of the IEEE 802.3-2008 standard to get an idea of the low-level network lingo as well as the packet format sent over the wires [8].

Once again we will use NakedCPU Explorer to investigate the internals of the Media Access Controller and conduct some experiments. MAC requires data structures in memory and configuration transactions via I/O address space. First, we must determine I/O address of MAC, which is called Base Address 2 (BAR2). The address is stored in the PCI configuration space at bus 0, device 25, function 0 (B0:D25:F0) register 0x18. By the way, there is a confusion in the documentation referring to the same register. ICH calls this particular register as MBARC [4], while MAC documentation [6] calls it BAR2. Conduct PCI transactions with NakedCPU Explorer as follows:

>pci
Enter Bus Device Function 0xRegister: 0 25 0 0x18
>read32
Port address: 0xcfc
ecc1
PCI transaction consists of two steps: define the location and read the content.

The number 0xECC1 means that the I/O address is actually 0xECC0 with the 0th bit hardcoded to 1 to indicate that the address is indeed in the I/O space as opposed to being memory-mapped [6]. The latter indication is important because all configuration and communication with MAC can also be done using memory-mapped registers, which is faster, however for our experiments it is sufficient to use the I/O space, because it is simpler and accomplishes same results as with memory-mapped operations.

In order to interact with MAC, the inquirer writes an address of a register within MAC into the BAR2 I/O address (0xECC0). After that, BAR2+0x4 (0xECC4) becomes a window to the value of that MAC register. It is important to mention that BAR2 and BAR2+0x4 accept only 32-bit double word read / write operations. MAC registers have plenty of bits to deal with and some bits are dependent on one another. It is very difficult understand the settings just by looking at the hexadecimal value of a register. An Excel worksheet InterpretRegister.xls (available for download) features a macro that greatly helps in this situation. Specifically, a table containing bit descriptions should be copy-pasted from the documentation PDF and a hexadecimal value of a register will be converted into binary 1s and 0s in appropriate cells right next to the description text, see Figure 4.

Image 3

Figure 4. A breakdown of the value 0x4100240 (yellow shading) into bits is shown as a fragment of the worksheet. Bits appear after textual descriptions of the individual fields.

Let us examine control CTRL(0x0) and status STATUS(0x8) registers; the address of the register is given in parentheses. After power on with network cable unplugged:

>write32
Port address: 0xecc0
Value: 0
>read32
Port address: 0xecc4
100240

This particular bit constellation determines among other things enabled automatic configuration for speed and full/half duplex.


>write32
Port address: 0xecc0
Value: 0x8
>read32
Port address: 0xecc4
80080600

These bits tell that there is no link established but initialization is completed.

After connecting the master computer with the NakedCPU, CTRL register stays the same as expected, while the status register changes to 0x80080683. The new value means full duplex communication, established link and 1Gbps speed. The master computer running Windows XP reported the same communication parameters, which indicates that the NakedCPU network interface was able to negotiate with the master computer's interface on the hardware level.

Receiving and interpreting network packets

In this section, we will experiment with reading network packets originated from the master computer. It was found that when master computer detected live NakedCPU via the network cable, Windows began generating DHCP requests. These requests are attempts to obtain an IP address and other high-level network settings, because Windows assumes (erroneously) that the NakedCPU is a router or a network server. Although Windows is mistaken, this is perfectly fine for our experiments, because we can catch these packets and examine them.

MAC uses direct memory access to store the received data. We have to create several descriptors, which will tell MAC where to write the data. Thus, two memory ranges are required: one for the descriptors and the other for packets. Referring to Table 1, one can see that there is an area of memory above the address 0x100000 available for inquirer's data. Bearing in mind that NakedCPU Explorer uses a tiny bit of that memory to store incoming commands, we can safely use addresses above 0x100500. It is sufficient to create two descriptors for the initial experiments. A descriptor is a data structure of four double words (16 bytes). The first two double words are 64-bit physical address of the location where the packet is to be stored. With the ability of the NakedCPU Explorer to write into memory locations, let us create the descriptors at the address 0x100500, pointing to two 512-byte long buffers located at the addresses 0x101000 and 0x101200. A collection of descriptors is called "queue". After MAC finishes with storing packets, it will update the descriptors to indicate received packet size, errors and several other parameters.

>memwrite
0xAddress above 0x100000: 0x100500
Num dwords: 8
0x101000
0
0
0
0x101200
0
0
0

The command "memwrite" asks for the address and the number of double words to be written. Note, writing below 0x100000 will cause general protection fault and reboot of the NakedCPU.

MAC has to know the location of the descriptors, the size of the receive buffers and the type of packets to receive. This information has to be stored in several MAC registers. RDBAL0(0x2800) and RDBAH0(0x2804) - low and high portions respectively of a 64-bit physical address of the base of the queue. RDLEN0(0x2808) - length of the memory buffer allocated for the queue. RDH0(0x2810) and RDT0(0x2818) - head and tail pointers respectively. RFCTL(0x5008) - receive filter control register. RXCSUM(0x5000) - receive checksum control register. Before setting up these registers, a bit 26 of the CTRL(0) register has to be set to 1, which will cause MAC reset. After setting up all registers, data reception is initiated by writing into RCTL(0x100) to set up the "enable" bit, the size or the receive buffers reception mode and type of the descriptors.

For the CTRL, RCTL, RFCTL and RXCSUM registers, the worksheet InterpretRegister.xls shows values (and bit states) that are going to be used in our experiments. The meaning of value for the RDLEN is somewhat confusing. According to the documentation, the length of the queue buffer must be a multiple of 128, which means at least eight descriptors (128 / 16) must be in the queue. We have only two descriptors, however. I have determined experimentally that it is not a problem to tell MAC that the queue buffer is larger than it needs to be, as long as the RDT0 register is pointing to the end of the actual queue. Hence, for our particular experiment we must set RDLEN = 0x80, RDT0 = 0x2.

Before receiving the packet by NakedCPU, the Master computer should not be sending DHCP packets. To suppress the DHCP packets originating from the Master computer, the following should be entered in the Windows command-line tool under the Administrator account:

ipconfig /release

Next, copy and paste columns of the Table 2 into the NakedCPU Explorer in the order I - V. Note that at the end of the 4th step, MAC is ready for enabling reception. That step ends up in reading from the status register, which should result in a value 0x80683. This value is similar to the one previously described (0x80080683, page 8) with the difference that the bit 31 is cleared, which indicates that the DMA clock cannot be lowered to ¼ of its value. The reason why MAC changed its "mind" concerning the DMA clock is not known, but this is not relevant for our experiments.

Table 2. Commands and values to be pasted into NakedCPU Explorer.
I II III IV V
write32
0xecc0
0
write32
0xecc4
0x4100240
write32
0xecc0
0x2800
write32
0xecc4
0x100500

write32
0xecc0
0x2804
write32
0xecc4
0
write32
0xecc0
0x2808
write32
0xecc4
0x80

write32
0xecc0
0x2810
write32
0xecc4
0
write32
0xecc0
0x2818
write32
0xecc4
0x2

write32
0xecc0
0x5008
write32
0xecc4
0x8000
write32
0xecc0
0x8
read32
0xecc4


write32
0xecc0
0x100
write32
0xecc4
0x402800A






To initiate the Master sending packets, type the following in the command-line window under the Administrator account:

ipconfig /renew

After reading from the network, MAC updates the two descriptors. With the memread command observe the new values by reading 8 double words beginning from the address 0x100500. You will see that the descriptor's address field is changed and two additional values appeared. Figure 5 shows the new values that my computer produced. Detailed information about the fields is provided in the MAC documentation [6]. Briefly, the lengths of the packets are 342 bytes (0x156); no errors occurred, the descriptors are indicated as "done" and the entire packet was able fit to the buffer (0x20073).

Descriptor 1 Descriptor 2
0
0
f8270fe8
f8270fe9
20073
20073
156
156
Figure 5. Descriptors updated after reading network packets.

MAC stored the actual two packets at the addresses 0x101000 and 0x101200 respectively; their contents are present in InterpretRegister.xls, "Packets" worksheet. Figure 6 shows the beginning of the stored data array and the order of transmission. The first six bytes marked red are the destination (broadcast) address, which are followed by the source addresses and the two-byte length / type field. The latter field is transmitted with most significant byte first [8], which makes it to be 0x0800. If the value of the length / type field is less or equal to 0x05DC, then it indicates the length of the packet, otherwise its type (Ethertype). Web pages at standards.ieee.org in theory provide specific Ethertype values, however it is virtually impossible to find the actual list of values. Luckily, Wikipedia points to an exact URL in the innards of the ieee.org site [9]. According to the standard, Internet Protocol (IP) is designated with the Ethertype 0x0800. Apparently, next step is to investigate the format of the Internet Protocol, which is described in RFC894, which points to RFC791 [10].

Image 4

Figure 6. Beginning of the DHCP packet received by NakedCPU.

The fields of the IP header are transmitted in a similar way as the previously described length / type field, i.e. most significant bit and the most significant byte first. In contrast to the intuitive notation, where bit 0 is the least significant bit, RFC791 provides the opposite, with bit 0 to be the most significant bit. Table 3 shows continuation of the received packet and the assignment of its specific values to the fields of the first 32-bits of the IP header. Important fields are IHL and Total Length. IHL indicates the number of 32-bit words in the header. In our case it is 5, which means that according to RFC791 Options and Padding fields are omitted. It is interesting to note that MAC reported receiving 342 bytes and the IP header indicated 328 (0x0148) bytes. The difference of 14 bytes makes perfect sense, because they make up to two bytes of the length / type field plus 2*6 bytes of destination and source hardware addresses.

All other fields of the IP header are easy to map following this example. Specifically, the field values are: Identification - 0x0fe8 (packet 1), 0x0fe9 (packet 2); Flags and Fragment Offset - 0; Time to Live - 0x80; protocol - 0x11; Header Checksum - 0x29be (packet 1), 0x29bd (packet 2); Source IP address - 0.0.0.0; Destination IP address - 255.255.255.255. It is understandable, that the master PC is asking for an IP address while doing the dynamic host configuration process, therefore the source IP address is all zeroes. For the destination, the address is all 255.255.255.255, which is a broadcast address, similar to the hardware broadcast address (6 bytes, all 255).

Table 3. First 32-bit word of the IP header. Top line - bit numbers, left column - fragment of the received packet. Underlined values of the packet are broken down into the fields.
Bit: 0 - 3 4-7 8-15 16-31
...
00450008
E80F4801
...
Version
0x4
Internet header length (IHL)
0x5
Type of Service
00
Total Length
0x0148

The value for the protocol field is 0x11 (17); according to RFC790 it refers to the User Datagram protocol (UDP) - the next level of data encapsulation - which is described in RFC768. According to that document, a UDP header contains four 16-bit words. In the order of transmission, these words are source port, destination port, length and checksum. The values received by NakedCPU in my experiment were source port - 0x0044, destination port - 0x0043, length - 0x0134, checksum - 0x5c1a (packet 1) and 0x581a (packet 2). The length of 308 (0x134) bytes makes sense, since IP header reported the total datagram length of 328 bytes minus 20 bytes occupied by the IP header. The source and destination port numbers should have been explained in RFC790, however it turned out that a long chain of other documents obsolete this one. At the end of the chain, it is suggested to look at the website of the Internet Assigned Numbers Authority (IANA), where, unfortunately, the information is not well organized. It was possible, however to find among the obsolete RFCs that port numbers 67 (0x43) and 68 (0x44) correspond to the Bootstrap Protocol, server and client ports respectively. The Bootstrap Protocol is described in RFC1542 that points out to the DHCP protocol, see RFC2131. A summary of all fields discussed in this section is provided in Figure 7.

Image 5

Figure 7. Summary of the data fields for Ethernet, IP and UDP protocols in the received packets.

Conclusions and Future Outlook

It is possible to directly experiment with Intel CPU and other PC hardware without any layers of unknown intermediate code that is intended to make our life "easier". As of yet, the most comprehensive documentation exists for the processor itself [2]. The other hardware is not well documented, which is why it takes a significant amount of efforts to gather pieces of information from the Internet and conduct experiments directly. The old books that dealt with hardware are DOS oriented and seriously obsolete. The new hardware is hidden behind layers of unknown code.

In theory, the NakedCPU platform enables developers to create task-specific applications using only needed components. For instance, if we are talking about a large-scale database, there is no need to support GUI, USB plug and play, audio cards, .NET and many other things. An additional bonus to the NakedCPU platform is immunity to viruses. Like in biology, where flexible viruses attack well-evolved organisms, the computer viruses attack well developed operating systems. With the NakedCPU, on the other hand, a particular task-specific solution can be very unique; hence the virus-creators will simply have not enough information to explore potential security holes.

Original Publication

The original publication is available on my web site. Click the images:

Part1 Part2

References

  1. Intel Core 2 Duo Processor E8000 and E7000 Series. Datasheet. June 2009. PDF
  2. Intel 64 and IA-32 Architectures Software Developer's Manual, all volumes combined PDF
  3. Intel 4 Series Chipset Family. Datasheet PDF
  4. Intel I/O Controller Hub 10 (ICH10) Family. Datasheet. PDF
  5. M Klasson. HexIt - The Hex Editor Here
  6. Intel I/O Controller Hub 8/9/10 and 82566/82567/82562V Software Developer's Manual. PDF
  7. MSDN, Beep function. Here
  8. IEEE Standard for Information technology - Telecommunications and information exchange between systems - Local and metropolitan area networks - Specific requirements. Part 3: Carrier Sense Multiple Access with Collision Detection (CSMA/CD) Access Method and Physical Layer Specifications. IEEE Std 802.3 -2008. Here
  9. A list of Ethertype values pointed by Wikipedia. Here
  10. All RFC documents are available at http://www.ietf.org/rfc/rfcZZZZ.txt, where ZZZZ is a four-position number of the document, prefixed with zeroes if necessary. Also here All RFCs

Appendix

Anatomy of MBR

Description MBR location
Determining current address while the processor is still in real mode after power on. BIOS has loaded the MBR somewhere into the memory and transferred control to our code. The current address is necessary to locate physical address of the pseudodescriptor, which is in turn defining a physical address and a limit for the Global Descriptor Table (GDT). 0x3E - 0x4D
LGDT instruction (Load GDT register) is loading pseudodescriptor, which is pointing to GDT. 0x52
GDT and Interrupt Descriptor Table (IDT) are copied into a new memory location, beginning from linear address 0x0. GDT and IDT are defining memory segments for the processor to operate in protected mode. 0x57 - 0x64
The MBR contains a very tiny 32-bit protected mode "operating system", let us name it NakedOS. 0x80 - 0x186
NakedOS is copied into a new memory location beginning with linear address 0x800. 0x65 - 0x71
Switching into protected mode is accomplished by adjusting the Machine Status Word using a LMSW instruction. 0x72 - 0x78
Transfer control to NakedOS. 0x7B
Set up 8259 interrupt controller 0xF5 - 0x105
Transfer control to the inquirer's executable 0x106

Critical data structures

Structure MBR location
Pseudo-descriptor IDT 0x194
Pseudo-descriptor GDT 0x1BA
Null descriptor 0x1C0

MBR Listing: NakedOS

ASM
Address   Opcode                        Mnemonic
00000000  EB3C                          jmp     short 00000003E
.............some remnants from FAT...............................
0000003E  FA                            cli
0000003F  33C0                          xor     ax,ax
00000041  8ED0                          mov     ss,ax
00000043  BC007C                        mov     sp,07C00
00000046  16                            push    ss
00000047  07                            pop     es
00000048  0E                            push    cs
00000049  1F                            pop     ds
0000004A  E80000                        call    00000004D
0000004D  89E5                          mov     bp,sp
0000004F  8B5E00                        mov     bx,[bp+000]
00000052  0F01976D01                    lgdt    q.[bx+0016D]
00000057  89DE                          mov     si,bx
00000059  81C67301                      add     si,0173
0000005D  B94000                        mov     cx,040
00000060  31FF                          xor     di,di
00000062  FC                            cld
00000063  F3                            repe
00000064  A4                            movsb
00000065  BF0008                        mov     di,0800
00000068  89DE                          mov     si,bx
0000006A  83C633                        add     si,033
0000006D  B93B01                        mov     cx,013B
00000070  F3                            repe
00000071  A4                            movsb
00000072  0F01E0                        smsw    ax
00000075  0D0100                        or      ax,01
00000078  0F01F0                        lmsw    ax
0000007B  EA00001000                    jmp     00010:00000
-----from here 32-bit code segment ------------------------------
00000080  66B81800                      mov     ax,018
00000084  8ED0                          mov     ss,ax
00000086  BCFE030000                    mov     esp,03FE
0000008B  66B80800                      mov     ax,08
0000008F  8EC0                          mov     es,ax
00000091  E492                          in      al,092
00000093  0C02                          or      al,02
00000095  E692                          out     092,al
00000097  66BAFB03                      mov     dx,03FB
0000009B  B083                          mov     al,083
0000009D  EE                            out     dx,al
0000009E  66BAF803                      mov     dx,03F8
000000A2  66B80600                      mov     ax,06
000000A6  66EF                          out     dx,ax
000000A8  66BAFB03                      mov     dx,03FB
000000AC  B003                          mov     al,03
000000AE  EE                            out     dx,al
000000AF  31C0                          xor     eax,eax
000000B1  BF00020000                    mov     edi,0200
000000B6  B900020000                    mov     ecx,0200
000000BB  F3                            repe
000000BC  AA                            stosb
000000BD  BF00030000                    mov     edi,0300
000000C2  BE14010000                    mov     esi,0114
000000C7  0E                            push    cs
000000C8  1F                            pop     ds
000000C9  0F011E                        lidt    q.[esi]
000000CC  83C606                        add     esi,06
000000CF  B904000000                    mov     ecx,04
000000D4  F3                            repe
000000D5  A5                            movsd
000000D6  66B83800                      mov     ax,038
000000DA  8EC0                          mov     es,ax
000000DC  8ED8                          mov     ds,ax
000000DE  31FF                          xor     edi,edi
000000E0  CD20                          int     020
000000E2  66B82800                      mov     ax,028
000000E6  8EC0                          mov     es,ax
000000E8  8ED8                          mov     ds,ax
000000EA  31FF                          xor     edi,edi
000000EC  CD20                          int     020
000000EE  6631C0                        xor     ax,ax
000000F1  8EC0                          mov     es,ax
000000F3  8ED8                          mov     ds,ax
000000F5  66BA2000                      mov     dx,020
000000F9  B011                          mov     al,011  //ICW1
000000FB  EE                            out     dx,al
000000FC  B028                          mov     al,028  //ICW2
000000FE  42                            inc     edx   //IRQ0 base addr 0x28
000000FF  EE                            out     dx,al
00000100  B004                          mov     al,04   //ICW3: Slave
00000102  EE                            out     dx,al   //connected to pin 2
00000103  B001                          mov     al,01   //ICW4
00000105  EE                            out     dx,al
00000106  EA000000003000                jmp     00030:000000000
.............random bytes......................................
00000140  66BAFD03                      mov     dx,03FD
00000144  EC                            in      al,dx
00000145  A801                          test    al,01
00000147  74FB                          jz      short 000000144
00000149  66BAF803                      mov     dx,03F8
0000014D  EC                            in      al,dx
0000014E  AA                            stosb
0000014F  50                            push    eax
00000150  66BAFD03                      mov     dx,03FD
00000154  EC                            in      al,dx
00000155  A820                          test    al,020
00000157  74FB                          jz      short 000000154
00000159  58                            pop     eax
0000015A  66BAF803                      mov     dx,03F8
0000015E  EE                            out     dx,al
0000015F  C3                            ret
00000160  57                            push    edi
00000161  B904000000                    mov     ecx,04
00000166  E8D5FFFFFF                    call    000000140
0000016B  E2F9                          loop    000000166
0000016D  5F                            pop     edi
0000016E  8B0F                          mov     ecx,[edi]
00000170  85C9                          test    ecx,ecx
00000172  7409                          jz      short 00000017D
00000174  51                            push    ecx
00000175  E8C6FFFFFF                    call    000000140
0000017A  E2F9                          loop    000000175
0000017C  59                            pop     ecx
0000017D  CF                            iretd
0000017E  AC                            lodsb
0000017F  E8CBFFFFFF                    call    00000014F
00000184  E2F8                          loop    00000017E
00000186  CF                            iretd
.............random bytes.........................................
00000194  FF01
00000196  0002
00000198  0000
0000019A  E000
0000019C  1000
0000019E  008E0000
000001A2  FE00
000001A4  1000
000001A6  008E0000
000001AA  0000
000001AC  0000
000001AE  0000
000001B0  0000
000001B2  0000
000001B4  0000
000001B6  0000
000001B8  0000
000001BA  FF01
000001BC  0000
000001BE  0000
000001C0  0000
000001C2  0000
000001C4  0000
000001C6  0000
000001C8  FF03
000001CA  0000
000001CC  00920000
000001D0  0A13
000001D2  0008
000001D4  009A4000
000001D8  FF03
000001DA  0004
000001DC  00924000
000001E0  FF0F
000001E2  00800B92
000001E6  0000
000001E8  007800
000001EB  0010
000001ED  92
000001EE  8000FF
000001F1  FF3B
000001F3  0900
000001F5  98
000001F6  40
000001F7  00FF
000001F9  FF3B
000001FB  0900
000001FD  92
000001FE  0000

Firstsectwrite

C++
#include "stdafx.h"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR destDisk[] = TEXT("\\\\.\\X:");
    cerr << "\n firstsectwrite.exe E: nakedos_x_y.bin";
    if (argc != 3)
    {
        cerr << "\nInput parameters error";
        return -1;
    }

    if(_tcslen(argv[1]) != 2)
    {
        cerr << "\nInput parameters error";
        return -1;
    }
    _tcscpy(&destDisk[4], argv[1]);

    HANDLE hD=CreateFile(destDisk,GENERIC_WRITE, FILE_SHARE_WRITE, NULL, 
           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, NULL);
    HANDLE hS=CreateFile(argv[2],GENERIC_READ, NULL, NULL, 
                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if((INVALID_HANDLE_VALUE == hS) || (INVALID_HANDLE_VALUE == hD))
    {
        cerr << "error";
        return -1;
    }
    BYTE buf[512];
    BYTE bufproof[512];
    DWORD dwCopied;
    ReadFile(hS, buf, 512, &dwCopied, NULL);
    WriteFile(hD, buf, 512, &dwCopied, NULL);
    CloseHandle(hD);
    CloseHandle(hS);

    hD=CreateFile(destDisk,GENERIC_READ, FILE_SHARE_WRITE, NULL, 
         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, NULL);

    if(INVALID_HANDLE_VALUE == hD)
    {
        cerr << "error";
        return -1;
    }
    ReadFile(hD, bufproof, 512, &dwCopied, NULL);
    if(!equal(buf, buf + 512, bufproof))
    {
        cerr << "write error";
        return -1;
    }
    return 0;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)