Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to implement a web server with a printf () call

2025-04-14 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article will explain in detail how to use a printf () call to implement a web server. The content of the article is of high quality, so the editor shares it for you as a reference. I hope you will have some understanding of the relevant knowledge after reading this article.

A little buddy retweeted a joke about Jeff Dean that we all might know. Every time I read this list, this section pops up:

Jeff Dean once implemented a web server with a sentence printf (), while other engineers added thousands of lines of comments but still couldn't figure out exactly how it worked. And this program is now the home page of Google Search.

It's possible to use a printf call to implement a web server, but I haven't found anyone else to do it. So when I read this list this time, I decided to implement it. Here is its code, a pure single printf call, without any additional variables or macros (don't worry, I'll explain how this code works).

# include int main (int argc Char * argv []) {printf ("% * c%hn%*c%hn"\ xeb\ x3d\ x48\ x54\ x54\ x50\ X2f\ X31\ x2e\ x30\ x20\ x32"\ x30\ x30\ x0d\ x0a\ x43\ x6f\ x6e\ x65\ x79\ x70\ x65\ x3a\ x74\ x65\ x78\ x74\ x2f\ x68\ x74"\ x6d\ x6c\ x0d\ x0a\ x0d\ x0a \ x3c\ x31\ x3e\ x48\ x65 "\ x6c\ x6c\ x6f\ x20\ x57\ x6f\ x72\ x6c\ x64\ x21\ x3c\ x2f"\ x68\ x31\ x3e\ x4c\ X8d\ xbc\ xff\ xff\ X48\ x89"\ xe3\ X48\ X83\ xeb\ X10\ X31\ xc0\ x50\ x66\ xb8\ X1f"\ X90\ xc1\ xe0\ x10\ xb0\ X02\ X50\ x31\ xd2\ x31 Xf6\ xff ""\ xc6\ x89\ xf7\ xff\ xc7\ X31\ xc0\ xb0\ X29\ X0f\ X05\ x49 "\ X89\ xc2\ xd2\ xb2\ x10\ x48\ x89\ xde\ x89\ xc7\ x31"\ xc0\ xb0\ x31\ x0f\ x05\ x31\ xc0\ xb0\ x89\ xc6\ X4c "\ x89\ xd0\ x89\ xc7\ X31\ xc0 xb0\ X32\ X0f\ X05\ X31\ xd2" \ X4C\ X89\ xd0\ X89\ xc7\ X31\ xc0\ xb0\ X2b\ X0f ""\ X05\ X49\ X89\ xc4\ X48\ X31\ xd2\ xb2\ X3d\ X4c\ X89\ xee ""\ X4c\ X89\ xe7\ X31\ xc0\ xc0\ X0f\ X05\ xf6\ xff "\ xc6\ xff\ xc6\ X4c\ x89\ xe7\ x31\ xc0 xb0\ X30\ X0f\ X05"\ X4c\ X89\ xe7\ X31\ xc0\ X03\ X0F\ xeb\ xc3 " (unsigned long int) 0x4005c8 + 12) > 16) & 0xffff), 0, 0x00000000006007D8 + 2, ((unsigned long int) 0x4005c8 + 12) & 0xffff)-(unsigned long int) 0x4005c8 + 12) > 16) & 0xffff), 0, 0x00000000006007D8) }

This code can only be run on systems with a unique Linux AMD64 bit compiler (gcc version 4.8.2 (Debian 4.8.2-16)), with the following compilation commands:

Gcc-g web1.c-O webserver

Some people may guess that I cheated with a specially formatted string. This code may not work on your machine because I used hard coding for both addresses.

The following version is more user-friendly (easier to change), but you still have to change two values: FUNCTION_ADDR and DESTADDR, which I'll explain later:

# include # define FUNCTION_ADDR ((uint64_t) 0x4005c8 + 12) # define DESTADDR 0x00000000006007D8 # define a (FUNCTION_ADDR & 0xffff) # define b ((FUNCTION_ADDR > > 16) & 0xffff) int main (int argc Char * argv []) {printf ("% * c%hn%*c%hn"\ xeb\ x3d\ x48\ x54\ x54\ x50\ x2f\ X31\ x2e\ x30\ x20\ x32"\ x30\ x30\ x0d\ x0a\ x43\ x6f\ x6e\ x74\ x79\ x70\ x65\ x3a\ x74\ x65\ x78\ x74\ x2f\ x68\ x74" X0d\ x0a\ x0d\ X0a\ x3c\ x68\ x31\ x3e\ x48\ x65 "\ x6c\ x6c\ x6f\ x20\ x57\ x6f\ x72\ x6c\ x64\ x21\ x3c\ x2f"\ x68\ x31\ X3e\ x4c\ x2d\ xbc\ xff\ xff\ x48\ x89 ""\ xe3\ x48\ x83\ xeb\ X10\ X48\ xc0\ x50\ x66\ xb8\ X1f ""\ X90\ xc1\ xe0\ X10\ " Xb0\ x02\ x50\ xd2\ x31\ xf6\ xff "\ xc6\ x89\ xf7\ xff\ xc7\ X31\ xc0\ xb0\ x29\ x0f\ x05\ x49"\ x89\ xc2\ x31\ xd2\ xb2\ x10\ x48\ xde\ x89\ xc7\ x31"\ xc0\ xb0\ x31\ x05\ xc0\ xb0\ x05\ x89\ xc6\ x4c"\ x89\ xd0\ x89\ xc7\ X31\ xc0\ Xb0\ X32\ X0f\ X05\ X31\ xd2 "\ X31\ xf6\ X4c\ X89\ xd0\ X89\ xc7\ X31\ xc0\ xb0\ X2b\ x0f"\ X05\ X49\ X89\ xc4\ X48\ X31\ xd2\ xb2\ X3d\ X89\ xee"\ X4c\ X89\ xe7\ X31\ xc0\ xff\ xc0\ X0f\ X05\ X31\ xf6\ xff "\ xc6\ xff\ xc6\ X4c\ X89\ xe7\ X31\ xc0\ Xb0\ x30\ x0f\ x05 ""\ x4c\ x89\ xe7\ x31\ xc0\ xb0\ x03\ x0f\ x05\ xeb\ xc3 " B, 0, DESTADDR + 2, a murb, 0, DESTADDR) }

I'll explain how this code works through a series of short C codes. The first piece of code explains how to run another piece of code without using a function call. Take a look at the following simple code:

# include # include # define ADDR 0x00000000600720 void hello () {printf ("hello world\ n");} int main (int argc, char * argv []) {(* ((unsigned long int*) ADDR)) = (unsigned long int) hello;}

You can compile it, but it may not run on your system. You need to follow these steps:

1. Compile this code:

Gcc run-finalizer.c-o run-finalizer

two。 Check the address of fini_array

Objdump-h-j. Fini _ array run-finalizer

Then find the VMA:

Run-finalizer: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 18 .fini _ array 00000008 0000000060072000000000600720 00000720 2 CONTENTS, ALLOC, LOAD, DATA

You need a GCC to compile to find it, and older versions of GCC use different storage Terminator principles.

3. Change the value of ADDR in the code to the correct address.

4. Recompile the code

5. Run it

Now you will see the output "hello world" on your screen, but how does it actually work? :

According to Chapter 11 of Linux Standard Base Core Specification 3.1 (Linux Standard Foundation Core Specification 3.1 Chapter 11)

.fini _ array

This section holds an array of function pointers that contributes a terminating array to the executable or shareable object that contains this part.

In order for the hello function to be called instead of the default handler, we need to rewrite the array. If you try to compile the web server code, the value of ADDR is obtained in the same way (using objdump).

Well, now that we know how to execute a function by overwriting a certain address, we also need to know how to use printf to overwrite an address. You can find many tutorials on exploiting formatted string vulnerabilities, but I'll give you a brief explanation.

The printf function has a feature that uses the "% n" format to let us know how many characters are output.

# include int main () {int count; printf ("AB%n", & count); printf ("\ n% d characters printed\ n", count);}

You can see that the output is as follows:

AB 2 characters printed

Of course, we rewrite this address with the address of any counting pointer. But in order to overwrite the address with a large value, you need to output a large amount of text. Fortunately, there is another format string "% hn" that acts on short instead of int. This value can be overridden with a 4-byte value that we need to arrange 2 bytes at a time.

Try placing the a value we need (in this case, the pointer to the "hello" function) to fini_array with two printf calls:

# include # define FUNCTION_ADDR ((uint64_t) hello) # define DESTADDR 0x0000000000600948 void hello () {printf ("\ n\ nhello world\ n\ n");} int main (int argc, char * argv []) {short a = FUNCTION_ADDR & 0xfff; short b = (FUNCTION_ADDR > 16) & 0xfff; printf ("a = x b = x\ n", a, b); fflush (stdout) Uint64_t* p = (uint64_t*) DESTADDR; printf ("before: lx\ n", * p); fflush (stdout); printf ("% * c%hn", b, 0, DESTADDR + 2); fflush (stdout); printf ("after1: lx\ n", * p); fflush (stdout); printf ("% * c%hn", a, 0, DESTADDR); fflush (stdout); printf ("after2: lx\ n", * p); fflush (stdout); return 0 }

The imported lines are:

Short a = FUNCTION_ADDR & 0xfffff; short b = (FUNCTION_ADDR > > 16) & 0xfffff; printf ("% * c%hn", b, 0, DESTADDR + 2); printf ("% * c%hn", a, 0, DESTADDR)

Both an and b are only half the address of the function, and you can construct a string of length an and b to pass into printf, but I choose to use the format "% *", which can control the length of the output by parameters.

For example, this code:

Printf ("% * c", 10,'A')

Nine spaces will be output after A, so a total of 10 characters will be output.

If you want to use only one printf, you need to consider that the b-byte has already been printed, and we need to print another b-Mura byte (this counter is cumulative).

Printf ("% * c%hn%*c%hn", b, 0, DESTADDR + 2, bmura, 0, DESTADDR)

Currently we are calling this "hello" function, but we can actually call any function (or any address). I've written a shellcode that looks like a web server, but it just outputs "Hello world". The following is the fill data I wrote:

Unsigned char hello [] = "\ xeb\ x3d\ x48\ x54\ x50\ x2f\ x31\ x2e\ x30\ x20\ x32"\ x30\ x30\ x0d\ x0a\ x43\ x6f\ x6e\ x74\ x79\ x70\ x65\ x3a\ x74\ x78\ x74\ x2f\ x68\ x74"\ x6d\ x6c\ X0a\ x0d\ x0a\ x3c\ x68\ x48 X65 "\ x6c\ x6c\ x6f\ x20\ x57\ x6f\ x72\ x6c\ x64\ x21\ x3c\ x2f"\ x68\ x31\ X3e\ x4c\ x8d\ xbc\ xff\ xff\ x48\ x89 "\ xe3\ x48\ x83\ xeb\ x48\ x31\ xc0\ X50\ x66\ xb8\ X1f"\ x90\ xc1\ xe0\ X10\ xb0\ X02\ X50\ X31\ xd2\ X31\ xf6" "\ xc6\ x89\ xf7\ xff\ xc7\ X31\ xc0\ xb0\ x29\ x0f\ x05\ x49"\ x89\ xc2\ x31\ xd2\ xb2\ x10\ x48\ x89\ xde\ x89\ xc7\ x31"\ xc0\ xb0\ x0f\ x05\ x31\ xc0\ xb0\ X05\ xc6\ x4c"\ x89\ xd0\ xc7\ x31 xc0\ xb0\ X32\ x0f\ x05\ x31\ xd2" \ xf6\ X4c\ X89\ xd0\ X89\ xc7\ X31\ xc0\ X2b\ X0f ""\ X05\ X49\ X89\ xc4\ X31\ xd2\ xb2\ X3d\ X4c\ X89\ xee ""\ X4c\ X89\ xe7\ X31\ xff\ xc0\ X05\ X31\ xf6\ xff ""\ xc6\ xff\ xc6\ X4c\ X89\ xe7\ X31\ xc0\ xb0\ X30\ X0f\ X05 " \ x31\ xc0\ xb0\ x03\ x0f\ X05\ xeb\ xc3 "

If you remove the hello function and insert the fill data, this code will be called.

This code is actually a string, so you can add a "% * c%hn%*c%hn" format string to it. The string is not named yet, so we need to find its address after compilation, and to get this address, we need to compile the code and disassemble it:

Objdump-d webserver00000000004004fd: 4004fd: 55 push% rbp 4004fe: 48 89 e5 mov% rsp,%rbp 400501: 48 83 ec 20 sub $0x20 mov% RSP 400505: 89 7d fc mov% edi,-0x4 (% rbp) 400508: 48 89 75 f0 mov% rsi -0x10 (% rbp) 40050c: c7 04 24 d8 07 60 00 movl $0x6007d8, (% rsp) 400513: 41 b9 00 00 00 mov $0x0 mov% r9d 400519: 41 b8 94 05 00 00 mov $0x594 be% r8d 40051f: B9 da 07 60 00 mov $0x6007dajig% ECX 400524: ba 00 00 00 mov $0x010 10% edx 400529: be 40 00 00 mov $0x40 % esi 40052e: bf c8 05 40 00 mov $0x4005c8 mov% edi 400533: B8 00 00 00 mov $0x0 pen% eax 400538: E8 a3 fe ff ff callq 4003e0 40053d: c9 leaveq 40053e: c3 retq 40053f: 90 nop

In fact, all you need to care about is this business:

Mov $0x4005c8pi% EDI

This is the address we need:

# define FUNCTION_ADDR ((uint64_t) 0x4005c8 + 12)

+ 12 is necessary because our padding data starts after the 12-character "% * c%hn%*c%hn" string.

If you are curious about populating data, it is actually created by the following C code:

# include # include int main (int argc, char * argv []) {int sockfd = socket (AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; bzero ((char *) & serv_addr, sizeof (serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY Serv_addr.sin_port = htons (8080); bind (sockfd, (struct sockaddr *) & serv_addr, sizeof (serv_addr)); listen (sockfd, 5); while (1) {int cfd = accept (sockfd, 0,0); char * s = "HTTP/1.0 200\ r\ nContent-type:text/html\ r\ n\ nHello world!" If (fork () = = 0) {write (cfd, s, strlen (s)); shutdown (cfd, SHUT_RDWR); close (cfd);}} return 0;}

I did extra work (even if it wasn't necessary in this case) to remove all NUL characters from this populated data (because I couldn't find a single NUL character from the Shellcodes database on X86-64).

Jeff Dean once implemented a web server using a printf () call. Other engineers added thousands of lines of comments, but still didn't figure out how it worked. And this program is now the home page of Google Search.

This leaves the reader with an exercise where you can handle the load of web if you want to evaluate the Google search server.

The code for this part can be obtained here.

For those who think it's useless: it's really useless. I just happen to like this challenge, and it updates my memory and knowledge for the following topics: writing padding code (which has not been written for many years), AMD64 assembly (calling conventions, register protection, etc.), system calls, objdump,fini_array (the last time I checked, GCC still uses .dtors), printf formatting utilization, gdb techniques (such as writing memory blocks to files) There is also low-level socket programming (I have used boost in the past few years).

Update: Ubuntu adds a security feature that provides read-only relocation in the final ELF table area. To be able to run this example in ubuntu, add the following command line at compile time:

-Wl,-z,norelro

For example:

Gcc-Wl,-z,norelro test.c on how to use a printf () call to achieve a web server to share here, I hope the above content can be of some help to you, can learn more knowledge. If you think the article is good, you can share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report