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
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:
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.
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:
class Ports : public NakedCPUcode
{
public:
Ports()
{
DWORD pe, ps;
__asm
{
mov pe, offset end
mov ps, offset start
jmp end
start: mov ax, 0x28
mov es, ax
mov ds, ax
mov ax, 0x18
mov ss, ax
mov esp, 0x3fe
xor edi, edi
mov eax, 'OLEH'
stosd
xor esi, esi
mov ecx, 4
INT 0x21
...
_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:
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.
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].
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.
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:
References
- Intel Core 2 Duo Processor E8000 and
E7000 Series. Datasheet. June 2009. PDF
- Intel 64 and IA-32 Architectures Software
Developer's Manual, all volumes combined PDF
- Intel 4 Series Chipset Family. Datasheet PDF
- Intel I/O Controller Hub 10 (ICH10) Family.
Datasheet. PDF
- M Klasson. HexIt - The Hex Editor Here
- Intel I/O Controller Hub 8/9/10 and
82566/82567/82562V Software Developer's Manual. PDF
- MSDN, Beep function. Here
- 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
- A list of Ethertype values pointed by
Wikipedia. Here
- 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
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
000000FB EE out dx,al
000000FC B028 mov al,028
000000FE 42 inc edx
000000FF EE out dx,al
00000100 B004 mov al,04
00000102 EE out dx,al
00000103 B001 mov al,01
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
#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;
}