成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用一個printf()調用實現一個web服務器

開發 前端
使用一句printf調用來實現一個web服務器是很有可能的,但是我還沒發現其他人做到。所以這次我讀到這個列表時,我決定實現它。這里是它的代碼,一個純粹單一的printf調用,沒有任何附加的變量或者宏(不用擔心,我將會解釋這段代碼是如何工作的)。

一個小伙伴轉發了一個可能我們都知道的Jeff Dean笑話。每次我讀到這個列表的時候,這一部分就會跳出來:

 Jeff Dean有次用一句printf()實現了一個web服務器,而其他工程師添加了數千行注釋但是仍然不能完全弄清楚它是如何工作的。而這個程序正是如今的Google Search首頁。

使用一句printf調用來實現一個web服務器是很有可能的,但是我還沒發現其他人做到。所以這次我讀到這個列表時,我決定實現它。這里是它的代碼,一個純粹單一的printf調用,沒有任何附加的變量或者宏(不用擔心,我將會解釋這段代碼是如何工作的)。

  1. #include <stdio.h>  
  2.    
  3. int main(int argc, char *argv[])  
  4. {  
  5.  printf("%*c%hn%*c%hn" 
  6.   "\xeb\x3d\x48\x54\x54\x50\x2f\x31\x2e\x30\x20\x32" 
  7.  "\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d" 
  8.   "\x74\x79\x70\x65\x3a\x74\x65\x78\x74\x2f\x68\x74" 
  9.  "\x6d\x6c\x0d\x0a\x0d\x0a\x3c\x68\x31\x3e\x48\x65" 
  10.   "\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x3c\x2f" 
  11.   "\x68\x31\x3e\x4c\x8d\x2d\xbc\xff\xff\xff\x48\x89" 
  12.  "\xe3\x48\x83\xeb\x10\x48\x31\xc0\x50\x66\xb8\x1f" 
  13.   "\x90\xc1\xe0\x10\xb0\x02\x50\x31\xd2\x31\xf6\xff" 
  14.  "\xc6\x89\xf7\xff\xc7\x31\xc0\xb0\x29\x0f\x05\x49" 
  15.  "\x89\xc2\x31\xd2\xb2\x10\x48\x89\xde\x89\xc7\x31" 
  16.   "\xc0\xb0\x31\x0f\x05\x31\xc0\xb0\x05\x89\xc6\x4c" 
  17.  "\x89\xd0\x89\xc7\x31\xc0\xb0\x32\x0f\x05\x31\xd2" 
  18.   "\x31\xf6\x4c\x89\xd0\x89\xc7\x31\xc0\xb0\x2b\x0f" 
  19.  "\x05\x49\x89\xc4\x48\x31\xd2\xb2\x3d\x4c\x89\xee" 
  20.  "\x4c\x89\xe7\x31\xc0\xff\xc0\x0f\x05\x31\xf6\xff" 
  21.  "\xc6\xff\xc6\x4c\x89\xe7\x31\xc0\xb0\x30\x0f\x05" 
  22.   "\x4c\x89\xe7\x31\xc0\xb0\x03\x0f\x05\xeb\xc3",  
  23.  ((((unsigned long int)0x4005c8 + 12) >> 16) & 0xffff),   
  24.  00x00000000006007D8 + 2,   
  25.   (((unsigned long int)0x4005c8 + 12) & 0xffff)-  
  26.   ((((unsigned long int)0x4005c8 + 12) >> 16) & 0xffff),   
  27.   00x00000000006007D8 );  

這段代碼只能在獨有Linux AMD64位編譯器(gcc版本是4.8.2Debian 4.8.2-16))的系統上運行,編譯命令如下:

  1. gcc -g web1.c -O webserver 

可能有些人會這樣猜測:我用一個特殊格式的字符串來作弊。這段代碼可能不能在你的機器上運行,因為我對兩個地址使用了硬編碼。

下面這個版本是更加用戶友好化的(更容易改變),但是你仍舊要改變兩個值:FUNCTION_ADDRDESTADDR,稍后我會解釋:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdint.h>  
  4.    
  5. #define FUNCTION_ADDR ((uint64_t)0x4005c8 + 12)  
  6. #define DESTADDR 0x00000000006007D8 
  7. #define a (FUNCTION_ADDR & 0xffff)  
  8. #define b ((FUNCTION_ADDR >> 16) & 0xffff)  
  9.    
  10. int main(int argc, char *argv[])  
  11. {  
  12.    printf("%*c%hn%*c%hn" 
  13.     "\xeb\x3d\x48\x54\x54\x50\x2f\x31\x2e\x30\x20\x32" 
  14.     "\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d" 
  15.     "\x74\x79\x70\x65\x3a\x74\x65\x78\x74\x2f\x68\x74" 
  16.     "\x6d\x6c\x0d\x0a\x0d\x0a\x3c\x68\x31\x3e\x48\x65" 
  17.     "\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x3c\x2f" 
  18.     "\x68\x31\x3e\x4c\x8d\x2d\xbc\xff\xff\xff\x48\x89" 
  19.     "\xe3\x48\x83\xeb\x10\x48\x31\xc0\x50\x66\xb8\x1f" 
  20.     "\x90\xc1\xe0\x10\xb0\x02\x50\x31\xd2\x31\xf6\xff" 
  21.     "\xc6\x89\xf7\xff\xc7\x31\xc0\xb0\x29\x0f\x05\x49" 
  22.     "\x89\xc2\x31\xd2\xb2\x10\x48\x89\xde\x89\xc7\x31" 
  23.     "\xc0\xb0\x31\x0f\x05\x31\xc0\xb0\x05\x89\xc6\x4c" 
  24.     "\x89\xd0\x89\xc7\x31\xc0\xb0\x32\x0f\x05\x31\xd2" 
  25.     "\x31\xf6\x4c\x89\xd0\x89\xc7\x31\xc0\xb0\x2b\x0f" 
  26.     "\x05\x49\x89\xc4\x48\x31\xd2\xb2\x3d\x4c\x89\xee" 
  27.     "\x4c\x89\xe7\x31\xc0\xff\xc0\x0f\x05\x31\xf6\xff" 
  28.     "\xc6\xff\xc6\x4c\x89\xe7\x31\xc0\xb0\x30\x0f\x05" 
  29.     "\x4c\x89\xe7\x31\xc0\xb0\x03\x0f\x05\xeb\xc3" 
  30. , b, 0, DESTADDR + 2, a-b, 0, DESTADDR );  

我將解釋這段代碼如何通過一系列簡短的C編碼來工作。***段代碼將解釋如何不使用函數調用,就能運行另一段代碼。看看下面這段簡單的代碼:

  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3.    
  4. #define ADDR 0x00000000600720 
  5.    
  6. void hello()  
  7. {  
  8.     printf("hello world\n");  
  9. }  
  10.    
  11. int main(int argc, char *argv[])  
  12. {  
  13.     (*((unsigned long int*)ADDR))= (unsigned long    int)hello;  
  14.  } 

你可以編譯它,但是它可能不能在你的系統上運行,你需要按如下步驟來做:

1.編譯這段代碼:

  1. gcc run-finalizer.c -o run-finalizer 

2.檢查fini_array的地址

  1. objdump -h -j .fini_array run-finalizer 

然后從中找到VMA

  1. run-finalizer:     file format elf64-x86-64 
  2. Sections:  
  3. Idx Name          Size      VMA               LMA               File off  Algn  
  4.  18 .fini_array   00000008  00000000006007200000000000600720  00000720  2**3 
  5.       CONTENTS, ALLOC, LOAD, DATA 

你需要一個***版的GCC來編譯才能發現它,舊版本的GCC使用不同的存儲終結器原理。

3. 改變代碼中ADDR的值為正確的地址。

4.重新編譯代碼

5.運行它

#p#

現在你就會看到你的屏幕上輸出“hello world”,而它實際上是如何運行的呢?:

依據Chapter 11 of Linux Standard Base Core Specification 3.1(譯注:Linux標準基礎核心規范3.111章)

.fini_array

這部分保存了一個函數指針數組,它貢獻出一個終止數組給這個可執行的或可共享的、包含這個部分的對象。

為了讓hello函數被調用而不是調用默認的處理函數,我們要重寫這個數組。如果嘗試編譯這個web服務器代碼,ADDR的值以同樣的方式獲取(使用objdump)。

好了,現在我們清楚了如何通過覆蓋一個確定的地址來執行一個函數,還需要知道如何使用printf來覆蓋一個地址。可以找到很多關于利用格式化字符串漏洞的教程,但是我將給出一個簡短的解釋。

printf函數有這樣一個特性,使用“%n”格式可以讓我們知道有多少個字符輸出。

  1. #include <stdio.h>  
  2.    
  3. int main(){  
  4.     int count;  
  5.     printf("AB%n", &count);  
  6.     printf("\n%d characters printed\n", count);  

可以看到輸出如下:

  1. AB  
  2. 2 characters printed 

當然我們用任何計數指針的地址來重寫這個地址。但是為了用一個大數值來覆蓋地址,需要輸出大量的文本。幸運的是,有另外一個格式化字符串“%hn”作用于short而不是int。每次可以用2個字節排列成一個我們需要的4字節值來覆蓋這個值。

試著用兩個printf調用放置我們需要的a¡值(在這個例子中是指“hello”函數的指針)到fini_array

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdint.h>  
  4.    
  5. #define FUNCTION_ADDR ((uint64_t)hello)  
  6. #define DESTADDR 0x0000000000600948 
  7.    
  8. void hello()  
  9. {  
  10.     printf("\n\n\n\nhello world\n\n");  
  11. }  
  12.    
  13. int main(int argc, char *argv[])  
  14.  {  
  15.  short a= FUNCTION_ADDR & 0xffff;  
  16.  short b = (FUNCTION_ADDR >> 16) & 0xffff;  
  17.  printf("a = %04x b = %04x\n", a, b);fflush(stdout);  
  18.    
  19.  uint64_t *p = (uint64_t*)DESTADDR;  
  20.  printf("before: %08lx\n", *p); fflush(stdout);  
  21.  printf("%*c%hn", b, 0, DESTADDR + 2 );fflush(stdout);  
  22.  printf("after1: %08lx\n", *p); fflush(stdout);  
  23.  printf("%*c%hn", a, 0, DESTADDR);fflush(stdout);  
  24.  printf("after2: %08lx\n", *p); fflush(stdout);  
  25.  return 0;  

導入的行是:

  1. short a= FUNCTION_ADDR & 0xffff;  
  2. short b = (FUNCTION_ADDR >> 16) & 0xffff;  
  3. printf("%*c%hn", b, 0, DESTADDR + 2 );  
  4. printf("%*c%hn", a, 0, DESTADDR); 

ab都只是函數地址的一半,可以構造一個ab長度的字符串傳入printf但是我選擇使用“%*”這個格式,它可以通過參數來控制輸出的長度。

例如這段代碼:

  1. printf("%*c"10'A'); 

將會在A后面輸出9個空格,所以一共輸出10字符。

如果只想用一個printf,就需要考慮到b字節已經被打印,而我們又需要打印另一個b-a字節(這個計數器是累加的)。

  1. printf("%*c%hn%*c%hn", b, 0, DESTADDR + 2, b-a, 0, DESTADDR ); 

目前我們是調用這個“hello”函數,但是其實我們是可以調用任何函數的(或者任何地址)。我寫過一個就像web服務器的shellcode(譯注:填充數據),但是它只是輸出“Hello world”。以下是我寫的填充數據:

  1. unsigned char hello[] =   
  2.     "\xeb\x3d\x48\x54\x54\x50\x2f\x31\x2e\x30\x20\x32" 
  3.     "\x30\x30\x0d\x0a\x43\x6f\x6e\x74\x65\x6e\x74\x2d" 
  4.     "\x74\x79\x70\x65\x3a\x74\x65\x78\x74\x2f\x68\x74" 
  5.     "\x6d\x6c\x0d\x0a\x0d\x0a\x3c\x68\x31\x3e\x48\x65" 
  6.     "\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21\x3c\x2f" 
  7.     "\x68\x31\x3e\x4c\x8d\x2d\xbc\xff\xff\xff\x48\x89" 
  8.     "\xe3\x48\x83\xeb\x10\x48\x31\xc0\x50\x66\xb8\x1f" 
  9.     "\x90\xc1\xe0\x10\xb0\x02\x50\x31\xd2\x31\xf6\xff" 
  10.     "\xc6\x89\xf7\xff\xc7\x31\xc0\xb0\x29\x0f\x05\x49" 
  11.     "\x89\xc2\x31\xd2\xb2\x10\x48\x89\xde\x89\xc7\x31" 
  12.     "\xc0\xb0\x31\x0f\x05\x31\xc0\xb0\x05\x89\xc6\x4c" 
  13.     "\x89\xd0\x89\xc7\x31\xc0\xb0\x32\x0f\x05\x31\xd2" 
  14.     "\x31\xf6\x4c\x89\xd0\x89\xc7\x31\xc0\xb0\x2b\x0f" 
  15.     "\x05\x49\x89\xc4\x48\x31\xd2\xb2\x3d\x4c\x89\xee" 
  16.     "\x4c\x89\xe7\x31\xc0\xff\xc0\x0f\x05\x31\xf6\xff" 
  17.     "\xc6\xff\xc6\x4c\x89\xe7\x31\xc0\xb0\x30\x0f\x05" 
  18.     "\x4c\x89\xe7\x31\xc0\xb0\x03\x0f\x05\xeb\xc3"

如果移除hello函數然后插入這個填充數據,這段代碼將會被調用。

#p#

這段代碼其實就是一個字符串,所以可以給它添加“%*c%hn%*c%hn”格式化字符串。這個字符串還未命名,所以需要在編譯后找到它的地址,而為了獲得這個地址,我們需要編譯這段代碼,然后反匯編它:

  1. objdump -d webserver 
  1. 00000000004004fd <main>:  
  2.   4004fd:   55                      push   %rbp  
  3.   4004fe:   48 89 e5                mov    %rsp,%rbp  
  4.   400501:   48 83 ec 20             sub    $0x20,%rsp  
  5.   400505:   89 7d fc                mov     %edi,-0x4(%rbp)  
  6.   400508:   48 89 75 f0             mov    %rsi,-0x10(%rbp)  
  7.   40050c:   c7 04 24 d8 07 60 00    movl   $0x6007d8,(%rsp)  
  8.   400513:   41 b9 00 00 00 00       mov    $0x0,%r9d  
  9.   400519:   41 b8 94 05 00 00       mov    $0x594,%r8d  
  10.   40051f:   b9 da 07 60 00          mov    $0x6007da,%ecx  
  11.   400524:   ba 00 00 00 00          mov    $0x0,%edx  
  12.   400529:   be 40 00 00 00          mov    $0x40,%esi  
  13.   40052e:   bf c8 05 40 00          mov    $0x4005c8,%edi  
  14.   400533:   b8 00 00 00 00          mov    $0x0,%eax  
  15.   400538:   e8 a3 fe ff ff          callq  4003e0     <printf@plt>  
  16.   40053d:   c9                      leaveq   
  17.   40053e:   c3                      retq     
  18.   40053f:   90                      nop 

其實只需要關心這行:

  1. mov    $0x4005c8,%edi 

這就是我們需要的地址:

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

+12是非常必要的,因為我們的填充數據是從12個字符長度的“%*c%hn%*c%hn”字符串后面開始的。

如果你的對填充數據很好奇,其實它是由以下的C代碼創建的:

  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #include<stdlib.h>  
  4. #include<unistd.h>  
  5. #include<sys/types.h>  
  6. #include<sys/stat.h>  
  7. #include<sys/socket.h>  
  8. #include<arpa/inet.h>  
  9. #include<netdb.h>  
  10. #include<signal.h>  
  11. #include<fcntl.h>  
  12.    
  13. int main(int argc, char *argv[])  
  14. {  
  15. int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
  16. struct sockaddr_in serv_addr;  
  17. bzero((char *)&serv_addr, sizeof(serv_addr));        
  18.     serv_addr.sin_family = AF_INET;  
  19.     serv_addr.sin_addr.s_addr = INADDR_ANY;  
  20.     serv_addr.sin_port = htons(8080);  
  21. bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));  
  22. listen(sockfd, 5);  
  23. while (1) {  
  24.     int cfd  = accept(sockfd, 00);  
  25.     char *s = "HTTP/1.0 200\r\nContent-type:text/html\r\n\r\n<h1>Hello world!</h1>";   
  26.     if (fork()==0) {  
  27.         write(cfd, s, strlen(s));  
  28.         shutdown(cfd, SHUT_RDWR);  
  29.         close(cfd);  
  30.     }     
  31. }  
  32. return 0;  

我做了額外的工作(即使在這個例子中并不是十分必要的)來移除這個填充數據中的所有NUL字符(因為我沒有從X86-64上的Shellcodes數據庫中找到一個NUL字符)。

Jeff Dean曾經使用一個printf()調用實現了一個web服務器。其他的工程師添加了數千行的注釋,但是仍然沒有弄清楚它是如何工作的。而這個程序正是如今的Google Search首頁

這給讀者留下了一道練習題,如果要評測web服務器,可以處理Google search的負載。

這部分的代碼可以從這里獲得。

對于認為這樣做是無用的人:它確實是沒有用的。我只是碰巧喜歡這種挑戰,而它為以下主題更新了我的記憶和知識:編寫填充代碼(已經很多年沒有寫過了),AMD64裝配(調用慣例,寄存器保護等等),系統調用,objdumpfini_array(最近一次我檢測的時候,GCC依然使用.dtors),printf格式化利用,gdb技巧(例如將內存塊寫入文件),還有低階的socket編程(過去幾年中我使用過boost)。

更新Ubuntu增加了一個安全特性,這個特性提供了在最終的ELF表區域中只讀重定位,為了能夠在ubuntu中運行這個例子,在編譯的時候添加以下命令行:

  1. -Wl,-z,norelro 

比如:

  1. gcc -Wl,-z,norelro test.c 

原文鏈接: tinyhack   翻譯: 伯樂在線 - 欣仔

譯文鏈接: http://blog.jobbole.com/64252/

責任編輯:林師授 來源: 伯樂在線
相關推薦

2024-03-08 12:45:00

C#Web服務器

2024-01-08 08:36:29

HTTPGo代理服務器

2019-04-24 15:06:37

Http服務器協議

2019-05-08 14:37:49

Web服務器HTTP

2022-02-22 11:57:32

BOAWeb服務器

2022-06-05 13:52:32

Node.jsDNS 的原理DNS 服務器

2017-12-12 15:24:32

Web Server單線程實現

2021-06-25 10:38:05

JavaScript編譯器前端開發

2023-05-10 08:05:41

GoWeb應用

2010-11-11 09:13:58

超高密度服務器HP戴爾

2022-04-01 15:18:42

Web 框架網絡通信

2016-03-01 14:37:47

華為

2020-10-29 16:00:03

Node.jsweb前端

2022-09-20 08:43:37

Go編程語言Web

2020-06-04 12:55:44

PyTorch分類器神經網絡

2020-08-27 11:39:05

JavaRESTful Web編程語言

2025-05-20 08:00:00

鏈式調用異步

2011-02-23 13:52:07

vsftpd

2012-08-24 15:13:34

2019-01-18 08:28:21

服務器程序架構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天堂色综合 | 91精品国产91久久久久久吃药 | 视频1区| 免费一看一级毛片 | 在线播放亚洲 | 精品欧美色视频网站在线观看 | 精品福利在线 | 久久久精彩视频 | 在线一区视频 | 欧美黄色录像 | 亚洲最大福利网 | 欧美日韩视频在线第一区 | 日韩视频免费看 | 精品视频一区二区三区在线观看 | 免费国产视频 | 手机日韩 | 日韩电影一区二区三区 | 亚洲专区在线 | 亚洲va在线va天堂va狼色在线 | 日本久久网 | 久久国产亚洲 | 噜久寡妇噜噜久久寡妇 | 色香蕉在线 | 天天看天天爽 | 国产一区www| 婷婷丁香在线视频 | 日韩手机在线视频 | 国产精品一区二区在线播放 | 久久久久久久久久久福利观看 | 天天干天天爽 | 免费成人国产 | 三级成人在线观看 | 亚洲性在线 | 新91视频网| 欧美精品一区二区三区四区 | 亚洲精品在线观看视频 | 久久久久久高潮国产精品视 | 99精品网| 午夜影院操 | 91免费在线看 | 国产亚洲欧美在线视频 |