作者 主題: C語言-網路的封包  (閱讀 6111 次)

0 會員 與 1 訪客 正在閱讀本文。

stlee

  • 鑽研的研究生
  • *****
  • 文章數: 817
    • 檢視個人資料
C語言-網路的封包
« 於: 2010-05-26 15:17 »
把這陣子研究用C語言與網路上的伺服器一問一答的工具和大家分享一下
如果有觀念錯誤或不對的地方請先進前輩們不吝指教,感謝^^!


首先....要能以"網址反查出網址"??這是在說什麼呀?
喔!簡單講就是在程式裏面不能以例如yahoo.com.tw或google.com當成參數來連上網路
所以您必須要有:
代碼: [選擇]
main()
{
  hostname[]="yahoo.com.tw";
  char *addr;
  int i;
  
  addr=malloc(128);
  memset(addr,'\0',127);
  i=lee_hostname2addr(hostname,addr);
  printf("********** i=%d addr=%s hostname=%s **********\n",i,addr,hostname);
}

int lee_hostname2addr(char *hostname,char *addrs)
{
  struct hostent *hp;
  struct in_addr in;
  struct sockaddr_in local_addr;

/*printf("hostname=%s \n",hostname);
*/
  if(!(hp=gethostbyname(hostname)))
  {
    fprintf(stderr,"lee_hostname2addr()反查錯誤!hostname參數需先以menset()函數清空的空間\n");
    return(-1);
  }
  memcpy(&local_addr.sin_addr.s_addr,hp->h_addr,sizeof(hp->h_addr));
  in.s_addr=local_addr.sin_addr.s_addr;
  strcpy(addrs,inet_ntoa(in));

/*printf("IP=%s addrs=%s size=%d ...lee_hostname2addr\n",inet_ntoa(in),addrs,sizeof(hp->h_addr));
*/
  return(1);
}

/*接受addr+port字串將之拆成addr及port
如176.38.182.18:8044這樣的一個字串會被拆成176.38.182.18--->以字串型態傳回
及8044--->轉成數字後設定給*po*/
char *lee_addrport(char *host,int *po)
{
  char *addr,*port;
  int di,sn,i,n;

  sn=strlen(host);
  di=lee_memchunt(host,":",0,strlen(host),FROM);
  addr=calloc(sizeof(char),di+1);
  port=calloc(sizeof(char),sn-di);

  for(i=0;i<di;i++)
    *(addr+i)=*(host+i);
  for(i=di+1,n=0;i<sn;i++,n++)
    *(port+n)=*(host+i);
//printf("host=%s addr=%s port=%s \n",host,addr,port);
  *po=atoi(port);
  free(port);
  return(&*addr);
}


然後就可以跟他來個一問一答了
首先是問的部份
代碼: [選擇]
/*
使用者端傳送資料
*/
//void lee_send(int fd,char *fmap,int mapsz,char *fkw,char *ekw)
void lee_send(int fd,char *sendmem,int sendmemsz)
{
  char *tmp;
  int i,tmpsz;

 /*除錯*/
printf("\n[傳送開始]***********************************************************[傳送開始]\n");
/**/
  /*傳去的資料量要減一是字串陣列的末尾'\0'字元否則封包都會多傳一個'\0'  */
/*  if(write(fd,tmp,tmpsz-1)>0 )
    printf("錯誤:傳送失敗\n");
/**/
/*  if(send(fd,tmp,tmpsz-1,0) < 0)
    printf("錯誤:傳送失敗\n");
/**/
  if(write(fd,sendmem,sendmemsz)<=0 )
    printf("錯誤:傳送失敗\n");
/*除錯*/
for(i=0;i<sendmemsz;i++)
  printf("%c",*(sendmem+i));
printf("[傳送完畢]***** lee_send()... sendmemsz=%d *****[傳送完畢]\n",
       sendmemsz);
/**/

//  free(tmp);
}

再來當然是接收他的答了,這裡可能稍微複雜一點,因為發回來的資料可能
1.資料有壓縮過
2.資料編碼方式跟您目前系統的不一樣
3.資料的長度到底是多少,該宣告多大的空間來接收
4.目前還沒遇到的其他各種狀況

關於第1點前陣子有討論過了有興趣可以看一下
http://phorum.study-area.org/index.php/topic,61245.0.html

這裡貼一下比較"正常狀況下"可以解決第3點問題的代碼
代碼: [選擇]
char *lee_recvc(int fd,int *recvsz)
{
  char ch[8];
  char *tmp;
  int i,gi,page;


  page=1;
  tmp=malloc((1024*page) * sizeof(char));/*宣告空間*/
  *recvsz=gi=0;
/*除錯*/
printf("\n[接收開始]***********************************************************[接收開始]\n");
/**/
  while(/*recv(fd,ch,1,0)>0*/ read(fd,ch,1)>0 )/*接收資料*/
  {
    *(tmp+gi)=ch[0];//只複製一個字元時這樣寫應該比strncpy(tmp+gi,ch,1);快很多
    gi+=1;
    if(gi > page*1024)
    {
      page+=1;
      tmp=realloc(tmp,(1024*page));
    }
  }
  *recvsz=gi;
  if(lee_memchunt(tmp,"Could not connect Database",0,gi,FROM) !=-1)
            printf("Could not connect Database  對方主機無回應!\n");

/*除錯*/
for(i=0;i<gi;i++)  printf("%c",*(tmp+i));
printf("\n[接收完畢]***** lee_recv()...page=%d gi=%d *recvsz=%d *****[接收完畢]\n",
       page,gi,*recvsz);
/**/
  return(&*tmp);
}

然後是整合問與答兩者的連線函數
代碼: [選擇]
char *lee_http(char *haddr,
               char *sendtmp,int sendtmpsz,
               int *recvsz/*int code*/)
{
  struct sockaddr_in address;
  int fd,len;
  char *addr,*recvtmp;
  int hap,port;


  if(haddr==NULL)
  {
    *recvsz=0;
    recvtmp=calloc(sizeof(char),1);
    return(recvtmp);
  }

//printf("lee_http()...haddr=%s[%d] addr=%s port=%d \n",haddr,strlen(haddr),addr,port);
  /*addr格式是xx.xx.xx.xx:zzzz 其中的:zzzz是指埠號,以*/
  if((hap=lee_memchunt(haddr,":",0,strlen(haddr),FROM)) !=-1)
    addr=lee_addrport(haddr,&port);//將addr及埠號分解
  else
  {
    addr=haddr;
    port=80;
  }
//printf("lee_http()...haddr=%s addr=%s port=%d \n",haddr,addr,port);


  /*建立連線*/
  fd = socket(AF_INET,SOCK_STREAM, 0);
  address.sin_family = AF_INET;
  address.sin_addr.s_addr = inet_addr(addr);
  address.sin_port = htons(/*80*/port);
  len = sizeof(address);
  if(connect(fd, (struct sockaddr *)&address, len) == -1)
    printf("錯誤:連線失敗\n");
  /*傳資料給伺服器*/
  lee_send(fd,sendtmp,sendtmpsz);
  /*接收伺服器的資料*/
//  recvtmp=lee_recv(fd,&*recvsz);
recvtmp=lee_recvc(fd,&*recvsz);

printf("\nlee_http()...haddr=%s addr=%s port=%d \n",haddr,addr,port);



  close(fd);/*中斷連線*/
  if(hap!=-1)
    free(addr);

  return(recvtmp);

}



等一下!等一下!......這裡說的一問一答到底都問些啥答些啥
其實就是我們以Wireshark所擷取的封包是也,把所擷取的封包照著發一次就好啦!
不過Wireshark擷取的封包必須先處理一下才能拿來用
要不然您也可以學我以前看著螢幕一個字一個字的照著敲然後用strcat()串接起來發出去
不過保證您玩個三天準備看醫生~~~~算了,不囉唆來看看怎麼取用Wireshark擷取的封包吧

首先當然是要有Wireshark啦,什麼!你沒有!!!google一下就有啦^^!
好的,這裡不是Wireshark教學所以自己網路上學一下,其實沒很困難,只要您能從上面看到這裡
相信也是"住巷子裡的"所以抓下來的封包就給他存起來吧

如果您是在win系統存起來的應該是*.pcap的檔案
這時請不要帥氣的左鍵連點兩下點開他而是以右鍵點一下,然後選[開啟檔案]->[WordPasd]
什麼,用Word開啟他....是的....其實用筆記本開就可以了=.=
然後給他另存新檔,檔案類型保持(或選擇) 文字文件
存起來的檔案就大概長這樣子
代碼: [選擇]
??\##P?�J??P#��###U#�K��##?##?###??###?{?d## ?#!E##�jY@#@##|o�#
??\##P}??�Z�P#��?##GET /images/leelib/xyzxxx.gif HTTP/1.1
Accept: */*
Accept-Language: zh-tw
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB6.4; InfoPath.1)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

U#�K v #########?{#??#�d## ##!E##��/@#?��??\o�#
#P#?�Z�}?�P##��##HTTP/1.1 304 Not Modified
Date: Thu, 20 May 2010 10:56:19 GMT
Server: Apache/2.2.3 (Unix) PHP/5.2.5
Connection: close

(以上封包有略為修改)

然後把GET部份的封包用lee_http()發出去就能得到HTTP/1.1 304 Not Modified這個相同的回應了嗎?
基本上是這樣沒錯的,不過其中有個"眉角"
就是您必須在每一行資料列後面跟著一個"\r\n"這樣的換列字元
不過這個"\r\n"您直接敲進去又不行,也就是說您把他改成這樣子
代碼: [選擇]
GET /images/leelib/xyzxxx.gif HTTP/1.1\r\n
Accept: */*\r\n
Accept-Language: zh-tw\r\n
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB6.4; InfoPath.1)\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: Keep-Alive\r\n
\r\n
這肯定是行不通的,因為這個\r\n是"兩個字元",如果直接在文書處理器敲進去的則是4個字元的\r\n

這時如果您手上有vi或另一台電腦是Linux(裏面有vi)那麼將WordPasd存起來的那個檔案利用gmaile傳到Linux電腦上...
(不一定要靠gmaile啦,不過我都是用gmaile傳的,因為.......他免費=..=")

然後如果有轉碼問題(像我ubuntu附的文字編輯器無法自動辨識)則可以用OpenOffice開啟(開的時候會自動轉碼)
然後給他存下去,當然以保留目前格式方式存

再來以vi 打開這個檔案時您會發現在每一列的後面有個奇怪符號像這個樣子
代碼: [選擇]
GET /images/leelib/xyzxxx.gif HTTP/1.1^M
Accept: */*^M
Accept-Language: zh-tw^M
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB6.4; InfoPath.1)^M
Accept-Encoding: gzip, deflate^M
Connection: Keep-Alive^M
^M
是的!你得到他了
不過.....這一個^M你就算自己敲上去也沒用
因為他的顏色會明顯與您自己敲進去的字元的顏色不一樣(但這個字元在vi內是可以複製的)



這個\r\n還有第2種方法可以取得,前面有說過用strcat()函數串接吧
代碼: [選擇]
strcat(tmp,"GET /images/leelib/xyzxxx.gif HTTP/1.1\r\n");
strcat(tmp,"Accept: */*\r\n");
.....
.....
這樣子的方式其實也是可以的,不過如果我們如果可以把每一個GET或POST動作存在檔案裏面變成其中的一個步驟而已
那麼........事情應該會變得簡單多了
當然,讓事情變"簡單"以前,肯定是有點小複雜的,這就是我們要研究的地方啦.....待續


« 上次編輯: 2010-05-27 17:33 由 stlee »
程式是人寫的,別讓工具的限制成為您想像力的極限
~程式中最重要的部份應該是註解而不是程式碼,這是因為解讀註解一定比解讀程式碼簡單
~程式寫好後約一個月就會忘的差不多了,所以花點時間把註解寫好至少能讓自己(或別人)看的懂當初在寫什麼

stlee

  • 鑽研的研究生
  • *****
  • 文章數: 817
    • 檢視個人資料
回覆: C語言-網路的封包
« 回覆 #1 於: 2010-05-27 14:37 »
比較理想的狀況是我們把封包抓下來存成文字檔以後經由程式讀取該文字檔內容
自動分析後執行相同的動作,當然...這已經不是理想狀況,而是--->你.作.夢 ;D

那好吧,退而求其次,把該文字檔稍微調整調整呢!
嗯....這就是我現在正在做的事情了
比如進入某網站後會有一些圖片連結,或是文字連結,點取這些連結後又進入另一頁面
或輸入某些資料就進入正式頁面..........

其實如果您仔細看Wireshark所擷取的封包(過濾出只有HTTP的封包)後您會發現
大部份都是符合一種一問一答的狀況的,也就是在一個GET或POST後面才會有個HTTP
如果其中您拿掉嵌入在裏面的java或其他代碼(也就是大家都看不懂的英文)後發現
這些個HTTP後面跟著的某些您能看得懂的中文不就是某些頁面的文字嗎?
所以HTTP就是伺服器[答]回來的封包,而GET或POST就是我們要[問]過去的啦!

那麼簡單的說把Wireshark抓到的關於GET以及POST的部份封包照抄發回去就是很完美的自動化上網程式乎!
嗯...這個....看下去您就知道困難點在哪裡了
首先一個口令一個動作是我們要求程式能充份摹仿我們的封包,但卻又不能一模一樣,而是要求能唯妙唯肖
所以首先我們必須把這些EGT與POST的封包存在一起然後能有個函數能讀取我們所指定的動作
就像這個樣子
代碼: [選擇]
[stp-1]
.....
.....
[/stp-1]
[stp-2]
.....
.....
[/stp-2]
[stp-3]
.....
.....
[/stp-3]


讀檔應該不困難所以不囉唆,至於字串擷取函數這裡貢獻一個參考參考
代碼: [選擇]
/*003-1在一記憶體區塊內擷取*fkw與*ekw的相關字串(不會消除'\0')
*/
char *lee_rtvmem2sl(char *mem,int foot,int end,char *fkw,char *ekw,int code,int *getsize)
{
  char *tmp,*lenstr;
  int fslen,eslen,fp,ep,sn;
  int i,n,f,e;

  if(fkw=='\0')
    fslen=1;
  else
    fslen=strlen(fkw);

  if(ekw=='\0')
    eslen=1;
  else
    eslen=strlen(ekw);

  f=lee_memchunt(mem,fkw,foot,end,FROM);
  if(f>=0)
    e=lee_memchunt(mem,ekw,f+fslen,end,FROM);
  if(f==-1 || e==-1 || e-f==1 )/*搜尋(起,終)的識別字在*s裡面找不到*/
  {
    tmp=malloc(1);
    *(tmp+0)='\0';
    *getsize=0;
    return(tmp);
  }

  /*依code模式設定foot與end值讓以下迴路進行複製*/
  lee_rtvcodeset(code,&fp,&ep,e,fslen,eslen,f,e);
  /*要傳回以指定方式擷取出字串的char型別的字串長度
  在code參數加上此選項相當於在呼叫者函數內的strnprintf()搬進此函數內
  使用方法比如取身+尾則code傳入 RETRBR | RETSLEN 即可取得所擷取長度的char型態字串
  而不是所擷取出來的字串*/
  if(code & RETSLEN)
  {
    sn=ep-fp;//RETRB只取身時的sn是不含'\0'及頭or尾的實際長度
    lenstr=calloc( sizeof(char),21);
    snprintf(lenstr,21,"%d",sn);
    *getsize=strlen(lenstr);
    return(lenstr);
  }
  else/*傳回指定方式所擷取的字串*/
  {
  /*mem最後一個字元本身是字串結束的'\0'時不必多加一個'\0'給擷取出的字串*/
    if(*(mem+(ep-1)) == '\0')
      sn=ep-fp;
    else
      sn=ep-fp+1;
    tmp=malloc(sn * sizeof(char));
    for(i=fp,n=0; n<sn ;i++,n++)/*複製*/
      *(tmp+n)=*(mem+i);
    *(tmp+sn-1)='\0';
    *getsize=sn;
    return(tmp);
  }
}
lee_rtvmem2sl()擷取模式的設定
代碼: [選擇]
/*001字串擷取函數擷取模式的設定,主要依code決定要將*foot與*end設定為何值
int sslen 此值需傳入以strlen()算出來的值不可傳入總size值
*/
void lee_rtvcodeset(int code,int *foot,int *end,
                    int sslen,int fslen,int eslen,int f,int e)
{

  if( code & RETRB )/*只取身*/
  {
    *foot=fslen+f;
    *end=e;
  }
  else if( code & RETRF )/*只取頭前*/
  {
    *foot=0;
    *end=f;
  }
  else if( code & RETRE )/*只取尾後*/
  {
    *foot=eslen+f;
    *end=sslen;
  }
  else if( code & RETRHB )/*頭+身*/
  {
    *foot=f;
    *end=e;
  }
  else if( code & RETRBR )/*身+尾*/
  {
    *foot=fslen+f;
    *end=eslen+e;
  }
  else if( code & RETRHF )/*頭+頭前*/
  {
    *foot=0;
    *end=f+fslen;
  }
  else if( code & RETRRE )/*尾+尾後*/
  {
    *foot=e;
    *end=sslen;
  }

  else if( code & RETRHBR )/*頭+身+尾*/
  {
    *foot=f;
    *end=eslen+e;
  }
  else
  {
    *foot=0;
    *end=0;
  }
}

會一直被用到的字串搜尋函數
代碼: [選擇]
/*在一記憶體*mem內搜尋字串*hstr從footp找到endp,參數dir控制:往前(逆)INFO 往後(順)FROM 向搜尋
以strstr()也可找子字串但會受限於 '\0' 字串結束字元,
本函數不會受'\0'限制所以可搜尋2,3維陣列的全文搜尋,但要注意foot~endp的範圍不可超
過*mem實際宣告的大小即當mem[10][18]時endp不可大於10*18
*hstr必需是以'\0'結尾的字串

傳回的是旗標值,即mem[n]的索引n值,如果是二維陣列以下方式可求得

 char mem[sy[sx];
 int hun,x,y,max;
 
 max=sy*sx;
 hun=lee_memchunt(mem,hstr,0,max,FROM);
 ans=div(hun,sx);          ~~~~~起點,終點可以從指定段落開始(不是0及max)
 y=ans.quot;                    但終點不可超出max
 x=ans.rem;
 strncat(s,mem+(y*sy)+x,strlen(hstr));
           ~~~~~~~~~~~~
細微差別:不論順向或逆向起點及終點在呼叫者函數皆不用傳入游標所在旗標+1或-1這是因為:
逆向時第一層迴路起始是i=endp-1而第二層迴路是k=i+1(單一字元及游標本身在待尋字串內可正確)
順向時第一層迴路起始是i=footp而第二層迴路是k=i+1(游標在待尋字串左邊1個位元可正確)
*/
int lee_memchunt(char *mem,char *kws,int footp,int endp,int dir)
{
  int kwsn,i,n,k,x;

  if(endp<1 || footp<0)/*無法搜尋的參數*/
    return(-1);

  if(*kws=='\n' || *kws=='\0')/*待搜尋字串*kws本身是換行'\n'或字串結束'\0'字元*/
  {
    for(i=footp;i<endp;i++)
      if(*(mem+i)=='\0' || *(mem+i)=='\n')
        return(i);/*footp~endp中間有'\0'或'\n'*/
    return(endp);/*footp~endp中間沒有'\0'或'\n'*/
  }

  kwsn=strlen(kws);
  if(dir==INFO)/*往前(逆向)搜尋,INFO*/
  {
    x=0;
    for(i=endp-1;i>=footp;i--)/*因傳回k+1所以從endp-1找到footp*/
    {
      if(*(mem+i) == *(kws+x))/*mem內第一個符合hstr+(sn+0)的字元*/
      {
        for(n=x+1,k=i+1;n<kwsn;n++,k++)/*再找出從第2到第sn個符合的字元*/
          if(*(mem+k) != *(kws+n))/*有不符合的字元即跳出*/
            break;
        /*結束(跳出)迴路後判斷n值是否比對到第sn個字元*/
        if(n>=kwsn)/*已比對到第sn位元即已找到字串*/
          return(i);
        else/*沒找到字串則再從第*(hstr+0)個字元開始找*/
          x=0;
      }
    }
  }
  else/*往後(順向)搜尋,FROM*/
  {
    x=0;/*順向時要搜尋的字串hstr是從第0個字元開始比對*/
    for(i=footp;i<endp;i++)/*從footp找到endp*/
    {
      if(*(mem+i) == *(kws+x))/*mem內第1個符合hstr+0的字元*/
      {
        for(n=x+1,k=i+1;n<kwsn;n++,k++)/*再找出從第2到第sn個符合的字元*/
          if(*(mem+k) != *(kws+n))/*有不符合的字元即跳出*/
            break;
        /*結束(跳出)迴路後判斷n值是否比對到第sn個字元*/
        if(n>=kwsn)/*已比對到第sn位元即已找到字串*/
          return(i);
        else/*沒找到字串則再從第*(hstr+0)個字元開始找*/
          x=0;
      }
    }
  }
  return(-1);
}


這樣我們就可以給他寫一段這樣的代碼來取得所需要的各步驟了
代碼: [選擇]
  char *fmap,*sendtmp;
  int fmapsz,sendtmpsz;

  fmap=lee_fout2tmp(fname,&fmapsz,NULL,NULL);/*取出設定檔*/
.....
.....
.....
  /*取得檔案內的目前所需(fkw,ekw所指定的)封包字串*/
  sendtmp=lee_rtvmem2sl(fmap,0,fmapsz,"[stp-1]\n","[/stp-1]",RETRB,&sendtmpsz);

lee_rtvmem2sl()的fkw的部份因為存在檔案裏面的首標籤都有個換列號且不需傳送給伺服器,且RETRB是只取身所以會排除該換列字元
再看一次:因為存成這樣,所以必須"[stp-1]\n"而不是"[stp-1]",因為lee_rtvmem2sl()會把第1個\n換列號給你一起取到sendtmp去....就多出一個\n了
代碼: [選擇]
[stp-1]
GET / ...
...
[/stp-1]


要"[stp-1]"則需存成這個樣子

[stp-1]GET / ...
...
[/stp-1]


這樣子有了解lee_rtvmem2sl()的用法了嗎?
而ekw末標籤不排除\n換列號則剛好上傳伺服器的\r\n的最後一個字元就是需要該\n字元所以不予排除


程式是人寫的,別讓工具的限制成為您想像力的極限
~程式中最重要的部份應該是註解而不是程式碼,這是因為解讀註解一定比解讀程式碼簡單
~程式寫好後約一個月就會忘的差不多了,所以花點時間把註解寫好至少能讓自己(或別人)看的懂當初在寫什麼

stlee

  • 鑽研的研究生
  • *****
  • 文章數: 817
    • 檢視個人資料
回覆: C語言-網路的封包
« 回覆 #2 於: 2010-05-28 16:38 »
好了!基本的東西大概就只差一樣了,就是我們要發出去的封包並不是與抓下來的封包一模一樣的
不過也差不了多遠,只要把該頁多抓個幾次互相比對以後大概就知道哪些是可以照抄哪些是需要替換的了
最簡單的講法就是日期了,總不能您發過去的封包每一次都是那個抓下來的日期吧

那麼現在問題來了......問題在哪裡?......當然是怎麼個換法呀@@!
弄個標籤如何,也就是說弄一個像[stp-1]這樣一種的標籤,然後讓程式找到特定標籤後予以替換
代碼: [選擇]
/*將mem內所有的oris字串替換成reps字串
tmp=lee_memcrepa(tmp,&tmpsz,"<~username~>",username);
tmp自己是參數同時也是傳回空間的接收體,因為本函數會變更tmpsz
以及釋放原tmp所指空間而以函數內宣告的空間傳回去所以必須讓空間指標自己替換自己
這樣一來在呼叫者函數不用宣告多個空間指標來存放每一次的替換結果或交互free()掉上一次替換結果
因為要的是替換後的結果,而被替換前的空間都會在替換完後馬上被free掉
*/
char *lee_memcrepa(char *mem,int *memsz,char *oris,char *reps)
{
  int repfoot,i,n,k,osn,rsn,newsz;
  char *tmp;

  newsz=*memsz;
  tmp=malloc(newsz * sizeof(char));
  repfoot=lee_memchunt(mem,oris,0,*memsz,FROM);
  if(repfoot==-1)
  {
    *memsz=newsz;
    return(mem);
  }
  osn=strlen(oris);
  rsn=strlen(reps);

/*printf("repfoot=%d oris=%s reps=%s newsz=%d *memsz=%d \n",
          repfoot,oris,reps,newsz,*memsz);
getchar();
/**/
  for(i=0,n=0;i < *memsz;i++,n++)
  {
    if(i==repfoot)
    {
      i+=osn;//將複製旗標加上被替換字串長度相當於忽略被替換字串
      repfoot=lee_memchunt(mem,oris,i,*memsz,FROM);//從被忽略處再找下一個oris字串
      /*當被替換字串長度比替換字串長度短時rsn-osn會得出正值,反之會得到負值
        在其後再加以判斷,如原空間不足則重宣告(加大)空間,而原空間長度足夠裝下替換後的長度
        則只需要控制newsz即可達到要求,不需重新宣告空間*/
      newsz+=rsn-osn;
      if(osn<rsn)//當伺服器傳來的uid字串會比自定義的替換識別字串來得長時需重宣告(加大)空間
        tmp=realloc(tmp,newsz * sizeof(char));
      for(k=0;k<rsn;k++,n++)//替換字串複製到新的空間
        *(tmp+n)=*(reps+k);

    }
    *(tmp+n)=*(mem+i);
  }
  free(mem);
  *memsz=newsz;
  return(&*tmp);
}

如果有很多個要替換咧?
當然是弄個二維陣列然後以迴路跑一趟嚕
代碼: [選擇]
..........................................................................................
範例:在程式內宣告好的字串陣列
char string[STRN][9]={"ARC","ARKW","SASECFKW","ENDRKW"};
char **sp;

gplkwp=lee_mem2sp((char *)string,sizeof(string));string需做轉型否則會有warning(警告)產生
for(i=0;i<nstrn;i++)
  printf("string[%d]=%s",i,*(sp+i));

...........................................................................................

char **lee_mem2sp(char *mem,int size)
{

  char **sp;
  int n,x;
  sp=lee_mem2spn(mem,size,&n,&x);
  return(sp);
}


char **lee_mem2spn(char *mem,int size,int *strn,int *maxlen)
{

  char **sp;
  int i,n,x;
  if(size<1)
  {
    sp=malloc( 1 * sizeof(char *));
    *(sp+0)=&*(mem+0);
    *strn=0;
    *maxlen=0;
    return(sp);
  }


  *strn=lee_memcstrn(mem,size);/*先計算*mem共有幾列資料或有幾個元素*/
  sp=malloc( *strn * sizeof(char *));
  *maxlen=0;
  /*以下迴路的邏輯與lee_memcstrn()相同*/
  for(i=0,n=0;i < size ;i++)
  {
    if(*(mem+i)!='\0' && *(mem+i)!='\n')/*略過資料列之間連續的'\0'或'\n*/
    {
      *(sp+n)=&*(mem+i);
      n+=1;
      x=0;
      while(i < size)/*此迴路讓i略過本列資料到達本列的'\0'或'\n以開始找下列資料旗標所在*/
      {
        if(*(mem+i)=='\0' || *(mem+i)=='\n')
          break;
        i+=1;
        x+=1;
      }
      if(*maxlen<x)
        *maxlen=x;
    }
  }
  return(sp);
}

/*計算記憶體區塊內有幾列有效的資料(共有幾個元素是以'\0'或'\n'分隔開的)
可略過連續的'\0'或'\n'

char *mem 存放所有資料的記憶體空間,就是從此空間擷取所需資料
int ssz 記憶體空間*s的長度
*/
int lee_memcstrn(char *mem,int size)
{
  int i,strn;
  /*本迴路是已經假設各列資料間有可能為了排列美觀而在各列資料間有換列號'\n'字元的想法寫成
  因為*s除了有可能是一個已擷取好的不定寬度二維字串陣列外也有可能是一個剛從檔案映射的記
  體區塊,這種映射檔案的記憶體區塊大多有連續換行號的情形存在,而固定寬度的二維字串陣列在各
  元素間更充斥'\0'字元(有初始化過的)*/
  for(i=0,strn=0;i<size;i++)
  {
    if(*(mem+i)!='\0' && *(mem+i)!='\n')/*略過資料列之間連續的'\0'或'\n*/
    {
      strn+=1;
      while(i<size)/*此迴路讓i略過本列資料到達本列的'\0'或'\n以開始找下列資料旗標所在*/
      {
        if(*(mem+i)=='\0' || *(mem+i)=='\n')
          break;
        i+=1;
      }
    }
  }
  return(strn);
}


然後第1種使用方法就是宣告一個陣列,定位後跑個迴路,不過這並不適用在一問一答的狀況下
而是在某頁中有陣列需要讀取然後整批的在下一次的GET或POST中將這些陣列裡的資料代進去
代碼: [選擇]
char betfkw[20][41]={"xxx","xxx",
        "active\" value=\"","type\" value=\""};

sp=lee_mem2spn((char *)betfkw,sizeof(betfkw),&spn,&x);
for(i=0;i<spn;i++)
{
  tmp=lee_rtvmem2sl(*recvtmp,0,*recvtmpsz,*(sp+i),"\"",RETRB,&n);
  memset(renums,'\0',11);
  snprintf(renums,11,"[>%d<]",i);
  sendtmp=lee_memcrepa(sendtmp,&sendtmpsz,renums,tmp);
  free(tmp);
}
這裡注意一下,如果要擷取的字串內含有"或'字元則在前面加一個\字元
至於為什麼,您在vi裏面敲進去就能了解了

上面說的是陣列的方法,那在一問一答時有的會在某頁裡夾個uid或cookie然後其下數頁都要帶著這個字串咧
比如這一種的封包
代碼: [選擇]
GET /aaa/xxx.php?uid=3cf216db5f9b24b2 HTTP/1.1
Accept: */*
Accept-Language: zh-tw
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB6.4; InfoPath.1)
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

那他前面(或更前面)一定有一個封包是帶著這一個uid字串的,所以......
前面不是有個lee_rtvmem2sl()函數嗎!?這個函數專門幹這件事情的啦 ;)
然後搭配lee_memcrepa()就可以了
如果要從頭帶到尾,且中間常常加進比如gid啦type啦code呀......
代碼: [選擇]
main()
{
  char **orikw,**retkw;
  int orisn,retsn;

  orikw=lee_memsspins(-1,"[>username<]",1,INSERT,orikw,&orisn);
  retkw=lee_memsspins(-1,username,1,INSERT,retkw,&retsn);
  orikw=lee_memsspins(orisn,"[>passwd<]",1,INSERT,orikw,&orisn);
  retkw=lee_memsspins(retsn,passwd,1,INSERT,retkw,&retsn);

  go(&orikw,&orisn,&retkw,&retsn);
}

int go(char ***orikw,int *orisn,
       char ***retkw,int *retsn)
{
  int i;

  /*將封包字串內需要替換的部份全部換起來*/
  for(i=0;i<*orisn;i++)
    sendtmp=lee_memcrepa(sendtmp,&sendtmpsz,*(*(orikw)+i),*(*(retkw)+i));
}

二維字串陣列指標插入
代碼: [選擇]
/*二維字串指標陣列行插入(會產生一個字串實體空間及一個指向該空間的二維指標)
在*sp的第flag處插入insnum個insstr字串(依模式code可以不理會或補足,補到尾端)
*/
char **lee_memsspins(int flag,char *insstr,int insnum,int code,
                     char **sp,int *strn)
{
  char **nsp,**stp;
  int i,n,k,x,inn,size;

  if(*strn<1 || flag<0)//**sp未初始化(**sp無指向內容)
  {
    nsp=lee_memcp2spn(insstr,strlen(insstr),&*strn,&i);
    return(nsp);
  }

  if(code==CPMTTY)/*補足到flag*/
  {
    if(*strn>flag)/*元素數量已大於插入點不用補*/
      return(sp);
    else
      inn=flag - *strn + insnum;
  }
  else if(code==IGNORE)/*不足不理會*/
  {
    if(*strn>flag)/*元素數量大於插入點需插入*/
      inn=insnum;
    else
      return(sp);
  }
  else if(code==INSERT)/*插入*/
  {
    if(*strn<=flag)/*插入點在尾端之後效果同CPMTTY(補足)*/
      inn=flag - *strn + insnum;
    else
      inn=insnum;
  }
  else/*ENDMOS不足的補尾端或從插入點補入   ALLMOS全部補尾端*/
  {
    if(*strn<=flag)
      flag+=insnum-1;
    inn=insnum;
  }
  stp=lee_memns2sp(insstr,inn);
  /*宣告重新定址的指標空間*/
  nsp=malloc((*strn + inn) * sizeof(char *));

/*lee_wmucls(stdscr,0,27,90,30,B0F7,1);
for(i=0;i<inn;i++)
  mvprintw(28,i*7,"%s",*(stp+i));
mvprintw(29,0,"flag=%d insstr=%s *strn=%d insnum=%d inn=%d ",flag,insstr,*strn,insnum,inn);
refresh();
/**/

  if(*strn<flag || code==ALLMOS)
  {
    for(i=0;i<*strn;i++)
      *(nsp+i)=*(sp+i);
    for(i=*strn,n=0;n<inn;i++,n++)
      *(nsp+i)=*(stp+n);
  }
  else
  {
    for(i=0,x=0;i<*strn+inn;i++)
    {
      if(i==flag)
      {
        for(k=i,n=0;n<inn;k++,n++)
          *(nsp+k)=*(stp+n);
        i=k-1;
      }
      else
      {
        *(nsp+i)=*(sp+x);
        x+=1;
      }
    }
  }
  free(stp);/*stp所指內容已被*nsp複製(只複製位址)故本二維指標陣列已無用需予釋放*/
  free(sp);
  *strn+=inn;
  return(nsp);
}

/*宣告strn個以'\0'結尾的str字串後定址傳回一個二維指標陣列
*/
char **lee_memns2sp(char *str,int strn)
{
  char **sp,*tmp;
  int i,len;

  len=strlen(str);
  if(strn<=0 || len<=0)
  {
    sp=(char **)malloc( 1 * sizeof(char **));
    sp=NULL;
    return(sp);
  }
  sp=(char **)malloc(strn * sizeof(char **));
  for(i=0;i<strn;i++)
  {
    tmp=(char *)malloc( len * sizeof(char) + 1);
    strcpy(tmp,str);
    *(tmp+len)='\0';
    *(sp+i)=tmp;
  }
  return(sp);
}

然後我們檔案裡的封包就可以存成這樣來替換每一次登入網頁所需要的各項"變數"了
代碼: [選擇]
[betpost]
POST /aaa/xxx.php HTTP/1.1^M
Referer: http://999.com/aaa/xxx.php?gid=[>gid<]&type=[>type<]&uid=[>login_uid<]^M
Accept-Language: zh-tw^M
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0)^M
Content-Type: application/x-www-form-urlencoded^M
Content-Length: [>contlen<]^M
^M
uid=[>login_uid<]&active=[>1<]&type=[>2<]&gid=[>gid<]&num=[>num<]&con=[>3<]&io=[>4<]&max=[>5<]&min=[>6<]&sing=[>6<]&reing=[>7<]&pt=[>8<]
[/betpost]

如此一來自動化網頁登入是不是給他變得彷彿有點輕鬆了呢!?
不過事情都不是想像那麼簡單的~~~~~不過基本上大概就是這樣而已了

程式是人寫的,別讓工具的限制成為您想像力的極限
~程式中最重要的部份應該是註解而不是程式碼,這是因為解讀註解一定比解讀程式碼簡單
~程式寫好後約一個月就會忘的差不多了,所以花點時間把註解寫好至少能讓自己(或別人)看的懂當初在寫什麼

stlee

  • 鑽研的研究生
  • *****
  • 文章數: 817
    • 檢視個人資料
回覆: C語言-網路的封包
« 回覆 #3 於: 2010-05-31 14:08 »
再來呢!是一些需要自行計算的替換,比如字串長度例如POST封包\r\n\r\n後面要帶多少Byte必須在表頭裏面告訴伺服器的
代碼: [選擇]
/*特殊的替換:Content-Length標頭是\r\n\r\n後的字串長度所以必須等全替換完再來計算*/
  if(code & CONTLEN)
  {
    tmp=lee_rtvmem2sl(sendtmp,0,sendtmpsz,"\r\n\r\n","\n",RETRB|RETSLEN,&tmpsz);
    sendtmp=lee_memcrepa(sendtmp,&sendtmpsz,"[>cont_len<]",tmp);
    free(tmp);
  }
還有時間
代碼: [選擇]
/*特殊的替換: 時間字串*/
  if(code & SYSTIME)
  {     
    sys_time=lee_timestr(addsec,0);
    sendtmp=lee_memcrepa(sendtmp,&sendtmpsz,"[>sys_time<]",sys_time);
    free(sys_time);
  }


我最多只要加一分鐘....XDDD

char *lee_timestr(int addsec,int code)
{
  char *sys_time;
  char *timeform[]={"%a, %d %b %Y %H:%M:%S GMT",//Sun, 23 May 2010 07:55:36 GMT
                    "%D %r",// 05/23/10 08:10:14 AM
                    NULL};
  time_t clock;
  struct tm *tm;
  int s,m,h;

  if(code<0)
    code=0;

  sys_time=calloc(sizeof(char),128);

  /*製作時間字串*/
  time(&clock);
  tm=gmtime(&clock);
  if(addsec>0)
  {
    s=tm->tm_sec;//秒
    m=tm->tm_min;//分
    h=tm->tm_hour;//時/24
    s+=addsec;
    if(s>=60)
    {
      s-=60;
      m+=1;
    }
    if(m>=60)
    {
      m-=60;
      h+=1;
    }
    if(h>=24)
      h-=24;
    tm->tm_sec=s;
    tm->tm_min=m;
    tm->tm_hour=h;
  }
  strftime(sys_time,128,timeform[code],tm);
  return(sys_time);
}

這些呢用一個窗口函數包起來,然後以一個int code來加以控制,以一個int控制(傳入)多個動作(參數)以前討論過了
http://phorum.study-area.org/index.php/topic,51286.msg261980.html#msg261980

然後我們的封包大概長這樣,就可以把時間字串或每次都不一定會相同的字串長度字串視為一個變數以標籤替換之即可
代碼: [選擇]
If-Modified-Since: [>sys_time<]
Content-Length: [>cont_len<]
.........................[/code]
« 上次編輯: 2010-05-31 14:13 由 stlee »
程式是人寫的,別讓工具的限制成為您想像力的極限
~程式中最重要的部份應該是註解而不是程式碼,這是因為解讀註解一定比解讀程式碼簡單
~程式寫好後約一個月就會忘的差不多了,所以花點時間把註解寫好至少能讓自己(或別人)看的懂當初在寫什麼