jtwp470’s blog

日記とかプヨグヤミングとか

Internetwache CTF 2016 Writeup

こんにちは.たんごです.授業も終わり就活も佳境になっている今日この頃.やるべきことをすべて放り出しCTFに取り組んでいました. Internetwache CTFは比較的簡単なCTFで私のような初心者にも非常に簡単に答えを出すことが出来たのでとっつきやすいCTFという感じでしたがrevやexpがちょっとrevらしく, exploitらしくない問題が出題されていたりとちょっと首を傾げそうな感じです.

最終順位は180位 970点であともうちょっとで1000点超えというような位置でした. 兎にも角にも珍しく問題を大量に解いたり特定ジャンルを全完したりと楽しいCTFでしたので解けた問題のWriteupを載せておこうかなと思います.

Misc

Misc 50

Description: My friend really can't remember passwords. So he uses some kind of obfuscation. Can you restore the plaintext?

ファイルを解凍するとみた感じ8進数でダンプされたファイルが出てきます.適当に数字部分をPythonに投げつけて文字列に変換するとどうやらbase64っぽい文字列が出てきたのでデコードします.

$ echo "V2VsbCBkb25lIQoKRmxhZzogSVd7TjBfMG5lX2Nhbl9zdDBwX3kwdX0K" | base64 -D
 Well done!

 Flag: IW{N0_0ne_can_st0p_y0u}

Flag: IW{N0_0ne_can_st0p_y0u}

Misc 60 Quick Run

Description: Someone sent me a file with white and black rectangles. I don't know how to read it. Can you help me?

全然わかりませんでした.しかしふとこの中身のファイルはbase64なんじゃないかと思いデコードしてみたところ何かQRっぽいことが発覚しました.

f:id:jtwp470:20160222014559p:plain

ですのでスマホでスキャンしてFlagを得ました.

出た文字列: FIagis:IW{QR_C0DES_RUL3}

Flag: IW{QR_C0DES_RUL3}

Misc 70 Rock with the wired shark!

Description: Sniffing traffic is fun. I saw a wired shark. Isn't that strange?

ファイルを展開するとパケットキャプチャしたファイルが.Wiresharkで開いてパケットを見ているとBASIC認証のページからzipファイルをダウンロードしているようです.ですのでExport Objectsを使ってzipファイルを展開します. 出てきたzipファイルを展開しようとするとパスワードを求めてきます. パスワードなんだよっと思いましたがBasic認証で使っているパスワードを入力すると展開できました.

Flag: IW{HTTP_BASIC_AUTH_IS_EASY}

Misc 80 404 Flag not found

解けないでいたらチームメンバーが解いてくれました.多分彼がWriteupを書いてくれることでしょう.

Misc 90 BarParty

ファイルを展開すると切断されたバーコードが! 仕方ないのでコンビニで写真を印刷しカッターで切りパズルのように合わせました.(辛かった)

f:id:jtwp470:20160222015310j:plain

あとは頑張って読み取りました.

Flag: IW{Bar_B4r_C0d3s}

Web

Web 80 0ldsk00lBlog

WebページにアクセスするとGitで管理したみたいな事が書いてあります.そこで /.git にアクセスすると403が返ってきます. しかし /.git/HEAD とかすると中身が表示されます.なのでGitリポジトリがそのまま公開されているようですね.

ここで片っ端からGitオブジェクトを手動でダウンロードしました.

ダウンロードしたリポジトリのファイルツリーは以下の様な感じでした.

.git
├── COMMIT_EDITMSG
├── HEAD
├── ORIG_HEAD
├── config
├── index
├── logs
│   ├── HEAD
│   └── refs
│       └── heads
│           └── master
├── objects
│   ├── 14
│   │   └── d58c53d0e70c92a3a0a5d22c6a1c06c4a2d296
│   ├── 19
│   │   └── 49446afea12e0937044fdabe8cc101c87f7c54
│   ├── 25
│   │   └── a3f35784188ac1c9bf48a94e5a9c815bcb598c
│   ├── 26
│   │   └── 858023dc18a164af9b9f847cbfb23919489ab2
│   ├── 33
│   │   └── a5c0876603d7a6f9729637f36030bbabb2afa3
│   ├── 3b
│   │   └── e70be50c04bab8cd5d115da10c3a9c784d6bae
│   ├── 4b
│   │   └── 825dc642cb6eb9a060e54bf8d69288fbee4904
│   ├── 55
│   │   └── 08adb31bf48ae5fe437bdeba60f83982356934
│   ├── 75
│   │   └── 03402e4d48be951cddda34aae6e01905bb5c98
│   ├── 8c
│   │   └── 46583a968da7955c13559693b3b8c5e5d5f510
│   ├── 91
│   │   └── f09a7948e02d891d3a39c058a634a8752aba20
│   ├── 95
│   │   └── a5396e62ca5c9577f761ebe969f52d3b6a9235
│   ├── b1
│   │   └── 5ec824e8f45356b9d829586e9950eb3e6c621a
│   └── db
│       └── a52097aba3af2b30ccbc589912ae67dcf5d77b
└── refs
    └── heads
        └── master

20 directories, 22 files

/.git/objects/ 以下はハッシュ化されているのでダウンロードするのは難しいと思いますがコミットログの各ハッシュでまずダウンロードし次にgit log --statでFatal Error と出てくるハッシュを同様にダウンロードすることで何とか全部ダウンロードすることが出来ました.

git diffの結果.

diff --git a/index.html b/index.html
deleted file mode 100644
index 5508adb..0000000
--- a/index.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<html>
-<head>
-       <title>0ldsk00l</title>
-</head>
-<body>
-       <h2>2000</h2>
-       <p>
-               Oh, did I say that I like kittens? I like flags, too: IW{G1T_1S_4W3SOME}
-       </p>
-
-       <h2>1990-2015</h2>
-       <p>
-               Hmm, looks like totally forgot about this page. I should start blogging more often.
-       </p>
-
-       <h2>1990</h2>
-       <p>
-               I proudly present to you the very first browser for the World Wide Web. Feel free to use it to view my awesome blog.
-       </p>
-
-       <h2>1989</h2>
-       <p>
-               So, yeah, I decided to invent the World Wide Web and now I'm sitting here and writing this.
-       </p>
-</body>
-</html>
\ No newline at end of file

Flag: IW{G1T_1S_4W3SOME}

.git/ 配下にどのようなファイルがあるのか調べるきっかけになったので非常に勉強になる問題でした.

Web 90 TexMaker

LaTeXのコードを入力するとPDFファイルを生成するWebサービス.問題をみた瞬間にOSコマンドインジェクションかディレクトリトラバーサルもどきで頑張ってPHPファイルとかを読むみたいな問題だろうなと予測しました.

はじめにOSコマンドインジェクションを試してみました.どうやら\input{} コマンドで普通にコマンドが利用できるようでしたがBLACKLISTに登録されているみたいで実行できず諦めました.

次にLaTeX内で何とかファイルを読めないかといろいろ調べてみるとTeX Hackとかいう論文を見つけました. これ通りに/etc/passwdを読んでみるときちんと読めることが確認できました.

そこで ./../index.php を読もうとして指定してもエラーになってしまいます.先ほどの論文の注釈を参考にちょっと変えたコードを書くとindex.phpも読むことが出来ました. どうやらcatcodeというコマンドでLaTeX特殊文字を飛ばしたりすることができるみたいですね?.多分#LaTeX特殊文字でエラーになってしまうのを回避させようとしているのだと思います.

このようなノリで調べているとどうやら index.phpはconfig.phpを読み, config.phpはflag.phpを読んでいます. 最後にflag.phpを読むコードを書きます.

\openin5=./../flag.php
\def\readfile{%
\catcode`\$=1
\read5 to\curline
\ifeof5 \let\next=\relax
\else \curline˜\\
\let\next=\readfile
\fi
\next}%
\ifeof5 Couldn't read the file!%
\else \readfile \closein5
\fi

エラーメッセージのところにFlagが書かれています.

f:id:jtwp470:20160222021149p:plain

Flag: IW{L4T3x_IS_Tur1ng_c0mpl3te}

Rev

Rev 50 SPIM

Description: My friend keeps telling me, that real hackers speak assembly fluently. Are you a real hacker? Decode this string: "IVyN5U3X)ZUMYCs"

どうみてもMIPSのコードです.本当にありがとうございました. というわけで簡単にC言語に変換して実行しました.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    char *flag = "IVyN5U3X)ZUMYCs";

    if (strlen(flag) > 15) {
        return 1;
    }
    int t1;
    for (t1 = 0; t1 < 15; t1++) {
        int a0 = flag[t1];
        a0 = a0 ^ t1;
        putchar(a0);
    }
    printf("\n");
    return EXIT_SUCCESS;
}

後は実行.

Flag: IW{M1P5_!S_FUN}

Rev 70 ServerfARM

ARMのバイナリ.適当にディスアセンブルしてコードを読むとどうやらprintfしまくっているみたい. なのでその文字を片っ端から吐き出して繋げればFlagが得られました.

Flag: IW{S.E.R.V.E.R>=F:A:R:M}

Rev 90 The Cube

ルービックキューブの問題.チームメンバーが解いてくれましたので関わってはいないのですがFlagの形式もわかっているので120通りのFlagを提出すれば当たるやろwって思っていました.(ゲス顔)

Code

Code 50 A numbers game

プログラミング問題 その1

問題例としては

x - 17 = 10

みたいなのが与えられるのでxの値は何か?を求める問題.つまり1次方程式を解くプログラムを書けばいいということになる. さてPythonには便利なことにSymPyと呼ばれる記号計算ライブラリがあります.これを使えば簡単に実装できます.

import socket
from sympy import *
from sympy.parsing.sympy_parser import parse_expr


HOST = "188.166.133.53"
PORT = 11027


def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw')


def read_until(f, delim='\n'):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data


s, f = sock(HOST, PORT)

var("x")  # x を変数として扱う
print(read_until(f))

while True:
    exp = read_until(f)
    print("<< : " + exp)
    # exp = "Level 1.: x - 17 = 13"
    exp = exp.split(".: ")[1].split(" = ")
    ans = solve(Eq(parse_expr(exp[0]), int(exp[1])))
    print(">> : " + str(ans))
    s.send(str(ans[0]).encode())
    print(read_until(f))

100回解くとフラグが得られました.

Flag: IW{M4TH_1S_34SY}

Code 60 It's Prime Time!

表示されている数字の次の素数を求めよというような問題.例えば3なら5を返してあげる. いちいち与えられた数の次の素数を求めるというようにすると時間がかかると思ったので最初に素数表を作成しておき辞書引きするような感じで書いた.

import socket
import re


HOST = "188.166.133.53"
PORT = 11059


def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw')


def read_until(f, delim='\n'):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data


def prime_table(n):
    lis = [True for _ in range(n+1)]
    i = 2
    while i * i <= n:
        if lis[i]:
            j = i + i
            while j <= n:
                lis[j] = False
                j += i
        i += 1

    table = [i for i in range(n + 1) if lis[i] and i >= 2]
    return table


prime_table = prime_table(100000)  # 100000でのテーブルを予め作成しておく

s, f = sock(HOST, PORT)
m = re.compile(r"Level (\d+).: Find the next prime number after (\d+):")

print(read_until(f))
while True:
    exp = read_until(f)
    print("<< : " + exp)
    exp = m.match(exp)
    y = int(exp.group(2))
    n = list(filter(lambda x: x > y, prime_table))[0]
    print(">> : " + str(n))
    s.send(str(n).encode())
    print(read_until(f))

実行してわかりましたが100くらいまでの素数表で足りる問題でした.

Flag: IW{Pr1m3s_4r3_!mp0rt4nt}

Code 70 A numbers game 2

今度は符号化したコードを与えられるのでそれをデコードしてみました.するとCode 50で解いたような1次方程式が出てきたのでCode 50のコードをパクる感じで書きました.なおずっと返す値もエンコードして返さないといけないということを忘れていて1時間溶かしましたorz

import socket
import string
from sympy import *
from sympy.parsing.sympy_parser import parse_expr

HOST = "188.166.133.53"
PORT = 11071


def xor(x, y):
    return x ^ y


def encode(eq):
    out = []
    for c in eq:
        q = bin(xor(ord(c), (2 << 4))).lstrip("0b")
        q = "0" * ((2 << 2)-len(q)) + q
        out.append(q)
    b = ''.join(out)
    pr = []
    for x in range(0, len(b), 2):
        c = chr(int(b[x:x+2], 2)+51)
        pr.append(c)
    s = '.'.join(pr)
    return s


def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw')


def read_until(f, delim='\n'):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data


maps = {encode(x): x for x in string.printable}


def check(exp):
    ret = ""
    for i in range(0, len(exp), 4):
        t = ".".join(exp[i:i+4])
        m = maps.get(t)
        if m:
            ret += m
        else:
            print(t)
            break
    return ret


def main():
    s, f = sock(HOST, PORT)

    print(read_until(f))
    while True:
        exp = read_until(f)
        print("<< : " + exp)
        exp = exp.split(".: ")[1].replace("\n", "").split(".")
        exp = check(exp)  # x - 12 = 10 みたいな形式になる
        print(" == " + exp)
        exp = exp.split(" = ")
        ans = solve(Eq(parse_expr(exp[0]), int(exp[1])))
        print(">> : " + str(ans[0]))
        s.send(encode(str(ans[0])).encode())
        print(read_until(f))


if __name__ == "__main__":
    # print(check(["3", "4", "4", "4"]))
    main()

Flag: IW{Crypt0_c0d3}

Crypto

暗号は1つしか解けなかったです.精進が必要.

Crypto 60 Oh Bob!

与えられた公開鍵をダンプして中の合成数を表示してみました.

$ openssl rsa -pubin -in bob.pub -text -noout
Modulus (228 bit):
    0d:56:4b:97:8f:9d:23:35:04:95:8e:ed:8b:74:43:
    73:28:1e:d1:41:8b:29:f1:ec:fa:80:93:d8:cf
Exponent: 65537 (0x10001)

すると非常に弱い鍵であることがわかりました.この程度であれば一般的なコンピューターのCPUでも数分で素因数分解できるはずですので適当なマシンを拝借してぶん回しました.

あとは得られた素数2つを使って秘密鍵を生成しそれを用いて暗号文を解読しました.

Flag: IW{WEAK_RSA_K3YS_4R3_SO_BAD!}

まとめ

解けたのはこんな感じでした.あと主催チームがさり気なく問題を全部公開しているので見ると勉強になっていいかなと思います.

github.com

あとチームのSlackにBOTを導入したところ結構便利になったのでこれからもう少し機能を追加してやりたいなという気持ちですw

f:id:jtwp470:20160222234245p:plain

今月号のきららMAXのごちうさも可愛くて最高だったのでみんな買いましょう!

Sharif CTF 2016 Writeup

こんにちは.たんごです. もう大学も3年が終わりますがまだ実験のレポートが進んでいませんw とりあえずやらないといけないことが多いなか, Sharif CTFが行われていたのでチームg0tiu5aのメンバーとして参加しました.250ptで248位でした(糞雑魚)

以下は解けた問題のWriteupと解けなかったけど他人のWriteup読んで実際にやってみた記録です.

解けた問題

Rail Fence Cipher (Crypto)

名前でググるRail Fence Cipherにあるように既知の暗号方式であることがわかりました. そこで適当にPythonのソルバーなどを検索して出てきたコードに暗号文を投下して適当に回しました.

key = 21でした.

Kick Tort Teen (Forensics)

Excel形式のファイルが出てきます.WindowsExcelなどで開くとExcel VBAマクロがあることがわかるので実行してみるが特に変化はなし.そこでコードを読むとどうやらfileXYZ.dataとかいうファイルを生成しているみたい.

$ file fileXYZ.data
fileXYZ.data: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

あとはこれを実行してみるとフラグが得られます.

Dumped (Forensics)

やるだけ問.strings RunMe.DMP | grep Sharif

dMd (Reverse)

何故か電車の中でRevした問題.フラグの元となる値はバイナリに直書きされています. GDBで動かしながらアセンブラコードを読んでいると以下の様な動作であることが分かりました.

  1. ユーザーから入力を受け取る
  2. ユーザーの入力値をMD5に変換する
  3. そのまま書かれているMD5値とユーザーの入力値をMD5化したものを比較して正しければThe key is valid! みたいな感じでおしまい.

中に書かれているMD5値は780438d5b6e29db0898bc4f0225935c0でした.これに対応する値はMD5(MD5(grape))ということを検索して知りましたのでその値がフラグでした.

解けなかったけど他人のWriteup読んで理解した問題

Sec-Coding 1 (Misc)

C++で書かれたコードにある脆弱性とかを潰せみたいな問題.提出サーバーにアクセス集中していたのか全然アクセスできなかったのが非常に辛かったです. ずっとvector charの大きさが0のときが未定義動作らしいのでその脆弱性を潰したかったのですがどうやっても潰れずに諦めました.

競技終了後, 別にvector char使う必要なくね?と思いStringにして提出したところ通ったのが辛いです.

We lost the Fashion Flag! (Forensics)

解凍するとtar.gzとfashion.modelというファイルが得られました.tar.gzを展開してもわけわからんファイルが10000くらいでてきて謎いです.結局どうすれば解けるのかわからず終わってしまいました.

他の人のWriteupを読みながら進めているとどうやらfashion.modelはfzipというもので圧縮されているらしいですね.

 $ hexdump -C fashion.model| head -2
00000000  08 00 00 00 46 65 6d 74  6f 5a 69 70 00 00 00 00  |....FemtoZip....|
00000010  00 00 01 00 34 7d 27 2c  20 27 63 74 66 27 3a 20  |....4}', 'ctf': |

なんじゃそりゃ~って感じですが適当にググります. https://github.com/gtoubassi/femtozipっぽいので使ってみる.

あとは10000くらいあるファイルがテキスト化されるのでgrepして出てきたフラグを提出すれば良いらしい.

 $  grep "forensic" out/* | grep 2016 | grep "'points': 100"
11180:{'category': 'forensic', 'author': 'staff_3', 'challenge': 'Fashion', 'flag': 'SharifCTF{2b9cb0a67a536ff9f455de0bd729cf57}', 'ctf': 'Shairf CTF', 'points': 100, 'year': 2016}
11223:{'category': 'forensic', 'author': 'staff_5', 'challenge': 'Fashion', 'flag': 'SharifCTF{41160e78ad2413765021729165991b54}', 'ctf': 'Shairf CTF', 'points': 100, 'year': 2016}
11908:{'category': 'forensic', 'author': 'staff_2', 'challenge': 'Fashion', 'flag': 'SharifCTF{8725330d5ffde9a7f452662365a042be}', 'ctf': 'Shairf CTF', 'points': 100, 'year': 2016}
273:{'category': 'forensic', 'author': 'staff_3', 'challenge': 'Fashion', 'flag': 'SharifCTF{1bc898076c940784eb329d9cd1082a6d}', 'ctf': 'Shairf CTF', 'points': 100, 'year': 2016}
9401:{'category': 'forensic', 'author': 'staff_6', 'challenge': 'Fashion', 'flag': 'SharifCTF{c19285fd5d56c13b169857d863a1b437}', 'ctf': 'Shairf CTF', 'points': 100, 'year': 2016}

Flag: 2b9cb0a67a536ff9f455de0bd729cf57

ググる力が大切だなと思う問題でした.

uagent (Forensics)

pcapファイルをWireSharkで開くとZIPファイルを大量に分割してダウンロードしていることがわかりました. 最初はIPアドレスが実際に存在するようなのでアクセスしてダウンロードを試みましたが結局できず,次にpcapに含まれるZIPの分割ファイル郡をWiresharkの機能を用いて取り出し, 適当にシェルスクリプトを書いてファイルをくっつける作業をしましたが出来たファイルが壊れていて解凍できなかったりそのファイルを直して解凍できるようにしても今度はそのZIPファイルのパスワードがわからず終了してしまいました.

競技終了後Writeupを読んでみると SharifCTF 2016 Uagent (Forensics 100) Writeup · xil.se のような解き方があったようです.Scapyは便利なようですね.私も実際にWriteupを見ながらコードを書きました.

雰囲気から察するにHTTP 206でダウンロードされるのがZIPファイル, User-Agentにあるsctf-app/[base64]/の部分がそのZIPファイルの鍵となっていたようです. 次からネットワーク問を解くときにはWiresharkだけでなくScapyも使ってみることが重要だなと学ぶことが出来た問題でした.

Network Forensics (Forensics)

パケットをざっと見てWifiのパスワードを何とかしてゲットしてデコードしてうまいことやるのかな?と思っていましたがパスワードをどうやって取るのかわかりませんでした.

rom-0というファイルは得ていましたがそれをどう使うのかも謎でWriteupを読んで実際にWPAのパスワードを得るとこれこそハッキングだーという感じがしました.

良いWriteup: opcode.ninja

以下はTODO. 書くよ

PhotoBlog (Web)

hackme (Web)

Asian Cheetah (Misc)

Android App (Reverse)

Serial (Reverse)

SRM (Reverse)

感想

個人的には2016年が始まって参加したOnline CTFのうち前2つのCTFがフラグ形式が不明でRecon問題すぎたのが辛かったので今回のCTFは非常に解きやすかったという印象が強かったです.しかしスコアサーバーが雑魚いのかよくアクセスできなくなったり文字入力にいちいちCAPTCHAを入れてくるのはうざかったかなあ. チーム目標として100 team以上解けている問題は解けるようにしたいですし, Pwn問題に1問も手を出せなかったのでPwn修行したいですし, きちんと時間をかけて取り組みたいなぁという気持ちです. まだまだ雑魚雑魚ですがこれからも暇を見ながらいろんなCTFに参加したいです.

最後に

このCTFのやっている間僕は大学の友人と大洗に行ってアンコウ鍋をおいしくいただき更にはガルパン聖地巡礼的なことをしていました.ガルパンはいいぞ!

f:id:jtwp470:20160207205741j:plain

アンコウ鍋 美味しかった.

f:id:jtwp470:20160207205749j:plain

〆の雑炊.天国にも登るようなうまさ

f:id:jtwp470:20160207205752j:plain

大洗磯前神社.痛絵馬がめっちゃいっぱいありました.海が綺麗

f:id:jtwp470:20160207205758j:plain

大洗マリンタワー.

pixivのインターンに行ってバグと戦ってきた

こんにちは.たんごです.去る12/12, 13はpixivでバグ取りインターンに参加していました.

f:id:jtwp470:20151217152629j:plain

recruit.pixiv.net

このインターンシップに参加するためにはまず事前課題を解きそれをGitHubのプルリクエストを使って提出するというなかなかない形式のものでした.

事前課題

github.com

GitHubにあるこの課題を解きました.PHPで書かれていましたが比較的読みやすく分かりやすかったのですがPHP7の入ったVagrantパッケージがなかなか降ってこなかったため自前でUbuntuVagrantパッケージを作成しその中に環境を整えて課題に取り組みました.PHPを使うにはApacheやNginxといったものにPHP FPMなどを使って構成するのが当たり前だと思っていたのですがphp -Sというコマンドを使えばそのようなものを使わずともサーバーとして動いてくれるのは知らずびっくりしました.

そんなこんなでコードを読んでいくと以下の様なバグをすぐ見つけることが出来ました.

こんな綺麗なコードを書けるんだから脆弱性残すなよって感じです.まぁまぁな時間をかけて提出締め切りの1日前くらいにプルリクエストを送りました.

解いて思いましたがWebセキュリティの基礎として徳丸本を読んでいても実際に手を動かさないと知識は身につかないと思います.私自身CSRF脆弱性については知識は持っていましたがどのように実装すれば脆弱性を回避できるのかなど詳細を知りませんでしたが今回の課題を通しきちんと理解することもでき勉強にもなると思います.もしWebセキュリティに興味のある人がいればこの課題を解いてみるのも良いかもしれません.

インターン

最初に参加者とメンターで自己紹介を1分で行うというのがありました.メンターさんが最初の自己紹介で「好きなLispは...」と言い始めたのがきっかけでみんなLispを取り上げたり好きなエディタや言語などを好き好きに行っていたので非常に面白かったです.また参加者にはVimmerもEmacserもいましたが戦争を起こすこともなくみんな仲良かったですw

さて,当日はpixivの前日12:00頃スナップショットを撮った本当のソースコードにあるバグと戦いました.参加するまでは意図的に混入させたりそういう擬似的なもののコード内にあるバグ取りをするものだと思っていたので本物のコードを触るとは思っても見ませんでした.バグ自体は15個程度あり,簡単なものから2日間ではまず解けないだろうとメンターさんがおっしゃるレベルのものまで取り揃えられていました.私は全部で5個ほど解決しましたがすべて簡単なものだったのでまだまだ精進が足りないなぁといった感じです.また私は手こずっていたためプルリクが取り込まれるのも遅くマージされることはありませんでしたが他のインターン生の速く出したものがサービスに取り込まれるそうなので羨ましいです.このインターンで学んだことは何事も早い者勝ちということです.社会は厳しいですね.

環境

環境自体は開発サーバーにSSHでログインしてpixiv社内からアクセスできるという仕組みでした.以下の記事がめっちゃ詳しいです.

inside.pixiv.net

基本的にはリモートサーバー上にあるファイルをいじらないといけないので私はEmacsのtramp-modeを使っていました.(Emacs最高!) もし来年以降参加する方がいればSSH経由でファイルを編集できる環境を構築しておくと捗ると思います!

あとオフィス環境もめちゃくちゃ良かったです.フリードリンク,フリー味噌汁でしたw

f:id:jtwp470:20151217152533j:plain

pixiv名物絵馬 f:id:jtwp470:20151217152621j:plainf:id:jtwp470:20151217152638j:plain

最高ですね.私も2次元に囲まれて仕事したいです.

感想

楽しかったです.(ゴミ日本語力) メンターさんや他のインターン生のレベルも高く刺激されました.自分にはまだ足りないことも見つかりましたしこれからも精進していきたいと思います.

あとめっちゃどうでもいいですが自分がGitHubにログインしている時だけpixivに所属しているふうに表示されるのが嬉しいです.

f:id:jtwp470:20151217154331p:plain

Pythonで並列実行をする

最近沢山の画像から特定の部分のみを抜き出すというような処理をしているのだが特に各画像との間に関連はないので1プロセスずつ動かすより数プロセス同時に動かして並列実行をすることを調べた.

今回は例として適当に100000枚の画像がありそれらは000000.jpg - 100000.jpgという感じでファイルが存在しているとした.またこれらの画像を適当な関数の処理にかけて別のディレクトリに保存する.例えばPythonでそれらの100000枚の画像に以下の様な関数処理を施すとしよう.

# source, destともにファイルのパス
# sourceは元ファイルのパス, destは処理したファイルを保存するパス
def example(source, dest):
    print(source + "を処理して" + save + "に保存しました")

こんなときLinuxMacであれば次のような感じで実行するといい感じに並行実行してくれる.

$ find $SOURCE_PATH -type f | gawk -F/ '{print $NF}' | xargs -I{} -P $n python3 logo_cutter.py $SOURCE_PATH/{} $SAVE_PATH/{}

xargsのPオプションはなんとすごいことにこのプロセスを並行実行してくれる.

なのでこれで困っていなかったのだが実家のマシンはWindowsLinuxは入っていない.また何度もPythonを呼び出すのはコストが掛かりそうな気がする. そこでPythonで並列実行できないか調べてみた.

するとどうやら標準ライブラリにmultiprocessingなる強そうなライブラリを発見した. きちんとWindowsでもサポートしてくれるらしい.

調べて次のようなコードを書いた.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import multiprocessing as mp
import os
import sys
import time


def hoge(source, dest):
    time.sleep(0.01)
    print("PID:" + str(os.getpid()) + ": " + source + "を" + dest + "に保存しました")


def wrapper_hoge(tuple_data):
    return tuple_data[0](tuple_data[1], tuple_data[2])


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("python3 example.py <SOURCE_PATH> <DEST_PATH>")
        sys.exit(1)
    source_path = sys.argv[1]
    dest_path = sys.argv[2]
    # 入力値がパスであるかの確認
    assert os.path.isdir(dest_path) and os.path.isdir(save_path), True
    source = [os.path.abspath(source_path + x) for x in os.listdir(source_path)]
    dests = [os.path.abspath(dest_path + x) for x in os.listdir(dest_path)]

    p = mp.Pool(4)  # プロセス数
    data = [(hoge, x, y) for x, y in zip(dests, saves)]
    p.map(wrapper_hoge, data)

簡単に解説すると

source = [os.path.abspath(source_path + x) for x in os.listdir(source_path)]

の部分は

find $SOURCE_PATH -type f

と同じ処理をしている. あとPythonは変数に関数オブジェクトを格納することができるので

data = [(hoge, x, y) for x, y in zip(dests, saves)]

をすることができる.

今回の場合デッドロックとかが起こることもないので非常に簡単な実装で実現することができた.これでWindowsでもxargs等を使わずとも利用できる. あと最近勉強しだしたGoでは関数に並列実行しろ的な命令をつけるだけでやってくれるらしいので暇があればそれも調べたい.

参考文献