トップ 最新 追記

日々の破片

著作一覧

2021-08-13

_ 久々にpdf-reader

pdf-readerを使って久々に処理をしている。

1つバグ(というか考慮不足)を見つけたが、問題となるPDFを生成できない=スペックを書けないので、PRしてないのがあってパッチして(というか派生クラスを作って)利用しているが忘れそうなのでメモ。

現象は、メディアボックスに余白がたくさんある場合(たとえばYのトップが600、ボトムが200)に発生する。

この例だとテキストが表示される領域の高さは400となる。フォントサイズを仮りに20と置くと20行の領域をpdf-readerは確保する。

row_multiplierは、(600-200)/20の20となる。

ここでY座標580(テキストの座標はトップが大)のテキストがあると、pdf-readerは20 - (580 / 20) から-9行目にテキストを配置しようとする。もちろん負値の座標には配置できないので該当テキストは非表示扱いとなり、page#textでは取得できない。

解決させるには、マージンボトムに相当する200をオフセットとしてテキスト座標から引けば良い。そうすると 20 - ((580 - 200) / 20) で1が求められ、0から数えて1行目というそれなりに正しい位置にテキストが出力される。

  class MyLayout < PDF::Reader::PageLayout
    def initialize(runs, mediabox)
      super
      @y_offset = mediabox[1]  # メディアボックスのトップ座標をオフセットとして保持
    end
    def to_s
      return "" if @runs.empty?
 
      page = row_count.times.map { |i| " " * col_count }
      @runs.each do |run|
        x_pos = ((run.x - @x_offset) / col_multiplier).round
        y_pos = row_count - ((run.y - @y_offset) / row_multiplier).round
        # puts "x: #{x_pos}, y: #{y_pos}, #{run.y}, #{row_multiplier}, r_count: #{row_count}, c_count: #{col_count}, text: #{run.text}" 
        if y_pos <= row_count && y_pos >= 0 && x_pos <= col_count && x_pos >= 0
          local_string_insert(page[y_pos-1], run.text, x_pos)
        end
      end
      interesting_rows(page).map(&:rstrip).join("\n")
    end
  end
  class MyReceiver < PDF::Reader::PageTextReceiver
    def content
      MyLayout.new(@characters, @mediabox).to_s
    end
  end
  def page_pdf_reader_text(page)
    receiver = MyReceiver.new   # おれさまレイアウターを使う
    page.walk(receiver)
    receiver.content
  end

で、原因はわかったのでPR作ろうかとWordで上下にたくさんマージンをとったページを作ってPDF化したが、Wordは意味的に正しいメディアボックス(要はデバイス自身のサイズということでA4相当)を作っているので再現できない。

再現ファイルを作って提供もできないし、元ネタのPDFはスペック用に提供するにはでかすぎる(上にフォントが日本語だから視認も難しかろう)なので何か手段を見つけるまでは、放置せざるを得なくなった。

X座標については誰かが見つけたらしくオフセット処理が入っているだけに惜しい。

別件として、ToUnicodeを持たないフォントであっても、OrderingがJapanの場合の簡単な変換方法を見つけた。以前メモした覚えがあるが見当たらないので再度書くと、popplerの変換ファイルを利用すると楽。

適当にインストールしたUbuntuだと /usr/share/poppler/cidToUnicode/にAdobe-Japan1 というファイルがあって、これが単純に00が1行目のコードとなっている。

0000
00a0
0021
0022
0023
...

なので、これを使う。pdf-readerのFontをオーバーライドすれば良いのだが、面倒なのでraw_contentをそのまま利用して、

  JAPAN = []
  Patname.new('/usr/share/poppler/cidToUnicode/Adobe-Japan1').each_line do |line|
    JAPAN << line.to_i(16)
  end
  JAPAN.freeze
 
  def to_text(hs)
    hs.split('').each_slice(4).map(&:join).map{|o| JAPAN[o.hex]}.pack('U*')
  end 
  def page_text(page)
    content = page.raw_content
    all = []
    pos = 0
    while m = /(\[?<.+?)T(J|j)/.match(content, pos) do
      texts = $1
      if texts[0] == '['
        pos0 = 0
        while m0 = /<([^>]+)>/.match(texts, pos0) do
          all << to_text($1)
          pos0 = m0.end(0)
        end
      else
        all << to_text(texts[1..-2])
      end
      pos = m.end(0)
    end
    all.join('')  
  end

(テキストのポジションが飛びまくるPDFの場合は、この方法で得たテキストは無茶苦茶なので、やはりpdf-readerのtext-runnerに任せるようにしたほうが良い)


2021-08-15

_ 世界を売った男

陳浩基の世界を売った男読了。2日かからなかった。

網内人が結構おもしろかったので買ったのだがなかなか読まずに先延ばしにしていたのを読んだのだった。

いきなりボウイの世界を売った男の歌詞が出て来て鼻白む。嫌いな曲ではないが、どういう了見の作品なんだ?

読み始めると、いきなり殺人事件の現場でオカルトっぽく終わり物語が始まる。

記憶(6年分)を失った刑事が冒頭の殺人事件の取材をする記者と2人で過去を追いかける。

刑事は現場の違和感から犯人は公的に犯人とされている男(逃走中に運転している車を暴走させて数人殺している)では無いのではないか、と疑っている。その疑いは徐々に確信に変わる。

物語は刑事と記者2人の取材に合わせて章分けされているが、各章の最後に刑事と一見無関係なスタントマン2人のPTSD治療のための医者の記録が挟まる。

徐々にこの2つが合わさってくる。

真犯人は途中でわかるし動機もわかってしまう。

だが、それを大団円させるための仕掛けは思いもつかなかった。きれいに円環が閉じる。見事なものだ。

ドンデンドンデン(Rシュトラウスのツァラトゥストラの冒頭のチャーンチャチャーチャーンチャチャーの後の部分の打楽器)

特に車の落ちのつけ方はおもしろい。愉快ではないが、タイヤ吹っ飛ばしたり隠したり、まあそういわれてもしょうがなさ過ぎる。

ボウイもそれなりに物語にからんでくるのだが、冒頭の引用部分の

Oh no, not me

I never lost control

You're face to face

With the man who sold the world

売ってしまった世界が何であるかとか(というか世界とは人生そのものであるわけだ)、確かに制御を失ってはいないとか、世界を売った男と顔を見合わせているとか、完全に作品そのものを説明しきっていて感心した。

最後、島田荘司という作家(つまらん短編小説集で唯一余情に溢れていた名品の作家としか知らん)が、本格派の登場だ、歓迎するとか書いていて、なるほど確かに整合性が取れているから、確かに推理小説なのだなとわかった(普通にミステリーとして読んでいた)。

世界を売った男 (文春文庫)(陳 浩基)


2021-08-18

_ シャーマンキングがいろいろおもしろかった

アマゾンを訪れたら妙なデコ坊の書影が出て来て、絵柄が良いのでクリックするとシャーマンキングだった。確か、昔、少年ジャンプで読んだ覚えがあるが講談社だなぁと不思議に思ったりしたが、なんとなく1~5巻まで買って読み始めて、最終的に全巻買って読んでしまった。

いろいろな意味でおもしろかった。

ジャンプ(努力・友情・勝利)漫画とはいろいろな意味で異なるし、大して読んではいないとはいえそれでもある程度の傾向は掴んでいるはずの「普通の」マンガの文脈とえらくずれているし、画が好きだ。

極端な特徴は次の3点だろう。

・生殖としてのセックス

・本気の多様性受容(リアルの表出)

・表明しない結論

特に最初の点があまりに特徴的に思える。

ジャンプといえば最初の飛躍はハレンチ学園なわけだが、そこに書かれているハレンチというのは、小学生の男子の、女子の裸を見たいとか、おっぱい触ってみたいとかの、要は好奇心ベースものだ。で、これは少年マンガというジャンルにおけるコードとしては問題なく許容されている。

で、それより上の層用のマンガになれば、普通にセックスが出てくるし、少年誌でもチャンピオンでバキが徹底的に書いてみせたし、おそらく援交と虐待というリアルによって少女マンガでも扱われなくもない。

が、これらは少年誌の好奇心ベースのセックス(未満)に対して、快楽ベースのセックスと言える。

それが虐待やレイプのような一方的なものであれ、愛し合った二人の結果であれ、快楽としてのセックスという視点からの書き方だ。

が、シャーマンキングの場合、(少年誌のコードと、おそらく作者自身の考えから)一切直接的には書かれないが、明白に子孫を残すための生殖行為として描かれている。(徹底的にほのめかしだけにしているのは、上であげた3つの特徴の3点目でもある本作の特徴。たとえば、それまで「婚約者」と主人公のことを周囲に告げていた女主人公が「夫」と呼ぶように変えていたりする)

そのわりに、露悪的にリアルな小道具は持ち込んでいて、高速道路のサービスエリアを書けば背景にラブホテルが林立するし、北海道から東京に来ている兄と妹が宿泊するのはラブホテルだし(こちらは全く怪しい関係ではないようだが)こういった説明抜きの設定は多い(その極端な例が、アイアンメイデンの変身後の鍵穴)。

こういった、もろもろの少年マンガのもろもろのコードの外し方が、最終的には人気投票最下位になり打ち切りになったのだろうと想像されるが(おそらく最大の原因は、ハオという最後の敵に対してどう対処するかについての説明をしていないことと(おそらく作者はぼかしたつもりはなくて自明の選択だと思ったのではなかろうか?)で読者が物語のゴールを見失ったこと、全チームが決勝戦に出場することになって勝負が曖昧になったこと、意外と戦闘のバリエーションが乏しいこと(これは作者本人の興味が完全にそれてしまったのではないかと思う)、死んでも簡単に蘇生できるような仕組みを導入したこと(それも含めた戦略が出て来ているわけだが小学生にはわかりにくいだろう)で戦闘のスリルのようなものがなくなったこと、女性主人公たちの立ち位置がほぼジェンダーを無視していること(普通の漫画の登場人物の感性を持つ主人公の役割をアイヌの少年と中国人の少年(途中まで)に負わせているが、主役が全然そのつもりがないのは読者にはわかるのだろう)といった、もろもろのカタルシスを読者に与える仕組みをなくしてしまったことが原因だろう。ちょうど、セックスの扱いから快楽が完全に抜け落ちていることと軌を一にしている。

が、そこが良いのじゃないかな。というか、だからおもしろい。

20世紀の最後から21世紀の最初への架け橋として強烈な時代性を持つ作品だ。

SHAMAN KING ~シャーマンキング~ KC完結版(1) (少年マガジンエッジコミックス)(武井宏之)


2021-08-29

_ padding-oracleはおもしろい

例によってアスキードワンゴの嘉平さんにネットワークプロトコルハッカーズガイドをもらった。

この本は、抜群におもしろい。

たかだか300ページ強にこれでもかとネットワークプロトコル(といっても物理層はさすがに無い)の諸要素について解説している。

当然、やたらとすっ飛ばしているので、読者はこの本をガイドとして自力で深堀りする必要はある。

とはいえ、読むだけでもえらくおもしろい。

おもしろいのは、(最初に戻って)これでもかと諸要素について解説しているからだ。本当に奥が深くてネットワークはおもしろい!

無くても良いとはいえ、直接実行するスクリプトはPython、Wireshark(キャプチャを取るので必須)に自力で解析処理を記述するためにLua、サンプルの解析用のちゃちなチャットプログラムにC#を使っているので、これらは読めたほうが良い。(とはいえ、Java、C、Ruby、Perl、PHPあたりをまじめに使っていれば読めるはず)

というか、そもそもおれがRubyを使い始めたのは、C++でネットワークアプリケーションのクライアントやサーバーを書きまくっていたころに、手軽にソケットを叩いてテスト用のクライアントやサーバーを作れるWin32で実行できるスクリプト言語が必要だったからなのだった。というわけでネットワークプログラミングにはC系(今だとGoやRustなのだろうけど)とスクリプト(RubyとかPythonとか)の両方が使えるほうがいずれにしても良いと思う。

で、こちらも考えながら読んでいたりするので、まだ全体の2/3くらいなのだが、途中、セキュリティについての解説がある。最終的にはPKIになるとはいえ、これまた諸要素について乱数、暗号、署名、証明書などについて解説がある。

暗号化の中でブロック暗号化としてECB(使うな)とCBCが出てくる(これは当然なのだが、当然とは言わずに説明してある)。で、ブロックということはブロックに満たない場合にどうするかとしてパディングについて説明が続く。

で、CBC+PKCS#7の弱点としてパディングオラクル攻撃について2ページ程度の説明がある。おそらく、パディングオラクル攻撃について説明することで、CBCに必要となるIV(初期化ベクタ)やパディングについて明確に説明できるからだろう。と同時に、作法としてナイーブに成功失敗をクライアントに返してはならないことの説明にもなる。

というわけで、おもしろいので、以下、パディングオラクル攻撃(パディングを答えを教えてくれる水晶玉として利用する攻撃)のサンプルコードを書いてみた。実装はP.108に書いてあるとおり。下の例はAES-128で暗号化した16バイト1ブロックの暗号文とIVから平文を求める。複数ブロックの場合はIVではなく直前のブロックを利用することになる。

# coding: utf-8
require 'openssl'
 
# 攻撃者は知らない
KEY = "\f\xC0\xCE+\x04\xE8\x99Fe\xEF\xE6\xB1\xB9\x93\xDB!".force_encoding('ASCII-8BIT')
# 暗号化されたメッセージ(攻撃者は当然知っている)
CRYPTED = "\x9B\xF2;\xFD.\x92\x1F\x0F\x9C\x13\xA2\x8B\x9EY\xE5\xF9".force_encoding('ASCII-8BIT')
# IVはメッセージと共に拾えるものとする
IV = "6\xBCYZ\xA4\xB6\xAD)cat\xFB\x9F\xC9\xC5\xB2".force_encoding('ASCII-8BIT')
 
# 復号の成功、失敗を返すサーバー側のAPI
def blackbox(iv, crypted)
  cipher = OpenSSL::Cipher.new('aes-128-cbc')
  cipher.decrypt
  cipher.key = KEY
  cipher.iv = iv
  cipher.update(crypted) + cipher.final
  true
rescue
  false
end
 
# paddingを作成するヘルパ
def dummy_iv(offset, encoded)
  ret = ''.force_encoding('ASCII-8BIT')
  return ret if offset == 1
  (1...offset).each do |i|
    c = encoded[i - 1] ^ offset
    ret = c.chr + ret
  end
  ret
end
 
encoded = []  # IV適用前の暗号化ブロック
plain = []    # 平文(padding付き)
# IVを変形してパディング1から16に相当するIV適用前のブロックを得る
(1..16).each do |padding|
  org = IV[16 - padding]
  result = []
  tail = dummy_iv(padding, encoded)
  # 総当たりで暗号文とキーにマッチするパティング相当のコードを得る
  (0..255).each do |x|
    new_iv = IV[0...(16 - padding)] + x.chr + tail
    if blackbox(new_iv, CRYPTED)
      result << x
      break if result.length >= 2
    end
  end
  if result.length == 1
    plain << (result[0] ^ padding ^ org.getbyte(0))
    encoded << (result[0] ^ padding)
  else # 2回出現するのは実際のパディングと一致する場合
    r = result.find {|e| e != org}
    encoded << (r ^ padding)
    plain << (r ^ padding ^ org.getbyte(0))
  end
  puts plain.inspect if $DEBUG
  puts encoded.inspect if $DEBUG
end
puts plain[plain[0]..-1].reverse.map(&:chr).join()
ネットワークプロトコルハッカーズガイド キャプチャ、解析、エクスプロイトの理論と実践 (アスキードワンゴ)(James Forshaw)

2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|

ジェズイットを見習え