AlpacaHack Round 8 (Rev) の write-up
AlpacaHack Round 8 (Rev) に参加して、3 問解いて 12/316 位でした。
振り返り
masking tape
とりあえずバイナリを落としてきて実行します。 こういう態度は本当に終わっているんですが、まぁ運営を信じて実行しちゃいます(仮想環境なので一応大丈夫なはず、一応)。
% ./masking-tape #> usage: ./masking-tape <input> % ./masking-tape a #> wrong
何らかの正しい <input> を寄越せという話っぽさを感じます。
とりあえず r2 (radareorg/radare2) を使って見てみると、strcmp でなにかを比較しているようなので、引数を見てみます*1。
// hook-a.c #include <stdio.h> int strcmp(char const* s1, char const* s2) { printf("'%s' <=> '%s'\n", s1, s2); return 0; }
これを
% gcc-14 --shared -o hook-a.so hook-a.c
こうして
% LD_PRELOAD=./hook-a.so ./masking-tape a | xxd #> 00000000: 2708 2303 0313 0313 0301 2331 1311 c803 '.#.......#1.... #> 00000010: c803 1301 c813 1303 1313 1113 2327 203c ............#' < #> 00000020: 3d3e 2027 0327 0a27 0240 8008 0808 c8c8 => '.'.'.@...... #> 00000030: 8088 0880 8832 0832 8080 8032 0880 0808 .....2.2...2.... #> 00000040: 4888 80c8 2720 3c3d 3e20 2708 270a 636f H...' <=> '.'.co #> 00000050: 6e67 7261 747a 0a ngratz.
こう。少し遊んでみます。
% LD_PRELOAD=./hook-a.so ./masking-tape ab | xxd #> 00000000: 2708 2303 0313 0313 0301 2331 1311 c803 '.#.......#1.... #> 00000010: c803 1301 c813 1303 1313 1113 2327 203c ............#' < #> 00000020: 3d3e 2027 0313 270a 2702 4080 0808 08c8 => '..'.'.@..... #> 00000030: c880 8808 8088 3208 3280 8080 3208 8008 ......2.2...2... #> 00000040: 0848 8880 c827 203c 3d3e 2027 0827 0a63 .H...' <=> '.'.c #> 00000050: 6f6e 6772 6174 7a0a ongratz. % LD_PRELOAD=./hook-a.so ./masking-tape Al | xxd #> 00000000: 2708 2303 0313 0313 0301 2331 1311 c803 '.#.......#1.... #> 00000010: c803 1301 c813 1303 1313 1113 2327 203c ............#' < #> 00000020: 3d3e 2027 0823 270a 2702 4080 0808 08c8 => '.#'.'.@..... #> 00000030: c880 8808 8088 3208 3280 8080 3208 8008 ......2.2...2... #> 00000040: 0848 8880 c827 203c 3d3e 2027 0240 270a .H...' <=> '.@'. #> 00000050: 636f 6e67 7261 747a 0a congratz.
1 文字追加するごとに右辺が伸びたり伸びなかったりしそう? なんかのハッシュ的な機構が入ってて、予想するのは大変そう。 とりあえず左辺は 28 bytes なので、28 bytes 程度のフラグが答えになりそう感。
いろいろ試していると、byte ごとに干渉しなさそうなので、とりあえず 1 byte ずつ決めていけばよさそう。なのでそういう solver を書きます。
from pwn import * target1 = ( "\x08\x23\x03\x03\x13\x03\x13\x03\x01\x23\x31\x13\x11\xC8" "\x03\xC8\x03\x13\x01\xC8\x13\x13\x03\x13\x13\x11\x13\x23" ) target2 = ( "\x02\x40\x80\x08\x08\x08\xC8\xC8\x80\x88\x08\x80\x88\x32" "\x08\x32\x80\x80\x80\x32\x08\x80\x08\x08\x48\x88\x80\xC8" ) context.log_level = "error" def escape(s): return s.replace("'", r"'\''") flag = "" for i in range(len(target1)): for c in range(ord(" "), ord("~") + 1): c = chr(c) p = process( f"LD_PRELOAD=./hook.-aso ./masking-tape '{escape(flag + c)}'", shell=True ) recv1 = p.recvline()[:-1] recv2 = p.recvline()[:-1] p.close() expected1, actual1 = recv1[: len(target1)], recv1[len(target1) :] expected2, actual2 = recv2[: len(target2)], recv2[len(target2) :] if ( len(actual1) == len(actual2) == i + 1 and expected1[: i + 1] == actual1 and expected2[: i + 1] == actual2 ): flag += c break else: exit(1) print(flag)
% python3 solve-a.py
#> Alpaca{********************}
よーぅし
hidden
これもとりあえず実行。
% ./hidden #> usage: ./hidden <input> % ./hidden a #> wrong
あ〜さっきと同じ感じね。
今回は memcmp で比較しているみたいです?
なにやら GDB が main を見つけてくれないみたいで困った。
(gdb) b main Function "main" not defined. Make breakpoint pending on future shared library load? (y or [n]) n
r2 的には s main とかができたので、何らかのことをして隠されているのでしょうか。とりあえず puts を呼んでいる箇所で止めたりしてみます。
(gdb) b puts
Breakpoint 1 at 0x10b0
(gdb) r a
Starting program: /mnt/hidden a
warning: Error disabling address space randomization: Operation not permitted
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, __GI__IO_puts (str=0x555555556020 "wrong") at ./libio/ioputs.c:33
warning: 33 ./libio/ioputs.c: No such file or directory
(gdb) bt
#0 __GI__IO_puts (str=0x555555556020 "wrong") at ./libio/ioputs.c:33
#1 0x0000555555555545 in ?? ()
#2 0x00007ffff7dc23b8 in __libc_start_call_main (main=main@entry=0x5555555553e1, argc=argc@entry=2, argv=argv@entry=0x7fffffffed18) at ../sysdeps/nptl/libc_start_call_main.h:58
#3 0x00007ffff7dc247b in __libc_start_main_impl (main=0x5555555553e1, argc=2, argv=0x7fffffffed18, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffed08)
at ../csu/libc-start.c:360
#4 0x0000555555555145 in ?? ()
(gdb) x/10i 0x0000555555555545
0x555555555545: mov $0x0,%eax
0x55555555554a: mov -0x18(%rbp),%rdx
0x55555555554e: sub %fs:0x28,%rdx
0x555555555557: je 0x55555555555e
0x555555555559: call 0x5555555550d0 <__stack_chk_fail@plt>
0x55555555555e: mov -0x8(%rbp),%rbx
0x555555555562: leave
0x555555555563: ret
0x555555555564: endbr64
0x555555555568: sub $0x8,%rsp
なにやら r2 で見た main っぽい命令が見つかったので一旦満足。アドレスの下 1.5 byte も一致していました。
とりあえずまた似たようなことをやってみます。
// hook-b.c #include <stdio.h> int memcmp(void const* s1, void const* s2, size_t n) { printf("memcmp(%p, %p, %zu)\n", s1, s2, n); for (size_t i = 0; i < n; ++i) { printf("[%zu]: %#04x %#04x\n", i, *((unsigned char*)s1 + i), *((unsigned char*)s2 + i)); } return 0; }
% LD_PRELOAD=./hook-b.so ./hidden a #> memcmp(0x5555555592a0, 0x555555558040, 108) #> [0]: 0xfc 0xdc #> [1]: 0xea 0x86 #> [2]: 0x6a 0x1a #> [3]: 0xfb 0x9a #> [4]: 0000 0xdd #> [5]: 0000 0x93 #> [6]: 0000 0x9b #> [7]: 0000 0x35 #: #> [104]: 0000 0xb0 #> [105]: 0000 0xa2 #> [106]: 0000 0x99 #> [107]: 0000 0x91 #> congratz
これも結局ハッシュめいたものを通して一致すればおめでとう〜という感じっぽい?
% LD_PRELOAD=./hook-b.so ./hidden Alpaca{
#> memcmp(0x5555555592a0, 0x555555558040, 108)
#> [0]: 0xdc 0xdc
#> [1]: 0x86 0x86
#> [2]: 0x1a 0x1a
#> [3]: 0x9a 0x9a
#> [4]: 0xdd 0xdd
#> [5]: 0x93 0x93
#> [6]: 0x9b 0x9b
#> [7]: 0x41 0x35
#> [8]: 0000 0xd3
それっぽさがあるので、それっぽい solver を書きます。
from pwn import * target = ( b"\xDC\x86\x1A\x9A\xDD\x93\x9B\x35\xD3\x74\xDA\xEE\xE8\x5A\x3C\xC5" b"\x1C\x64\x33\x47\xD2\x3B\x28\xF3\xCC\x5A\x48\x8B\x74\x0C\x4B\x87" b"\x38\xD6\x80\x40\x51\xE6\x4A\x27\xA1\x73\x52\x0F\x93\x06\x54\x3D" b"\x65\x13\xFB\xC8\x65\xAF\xD2\x67\xB3\x09\xEF\x7D\x23\xA6\x76\xE5" b"\x13\x10\x13\xFF\x34\x8D\xAE\xD0\x9C\x2C\x4D\xF3\xA1\xBC\x46\x2F" b"\x98\x87\xB6\x57\x1A\xA2\x17\xF1\xF0\xE5\xB0\xBA\x9B\x6D\xB5\xA7" b"\xAC\x6A\x5E\xAC\xE8\xF6\x90\xD8\xB0\xA2\x99\x91" ) context.log_level = "error" def escape(s): return s.replace("'", r"'\''") flag = "" for i in range(len(target)): for c in range(ord(" "), ord("~") + 1): c = chr(c) p = process(f"LD_PRELOAD=./hook-b.so ./hidden '{escape(flag + c)}'", shell=True) recv = p.recvline()[:-1] p.close() if target[: len(flag) + 1] == recv[: len(flag) + 1]: print(c, end="", flush=True) flag += c break else: exit(1) print()
% python3 solve-b.py
#> Alpaca{**************** ... ***}
よーぅし。
vcipher
とりあえず実行してみます。
% ./vcipher #> Input 32-character flag: a #> Error: Flag must be exactly 32 characters. % ./vcipher #> Input 32-character flag: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #> Input flag: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #> Processing ... #> Processing ... #> Processing ... #> Processing ... #> Processing ... #> Processing ... #> Processing ... #> Processing ... #> The flag is incorrect.
お、さっきとは違いますね。
r2 で afl してみると C++ 感があり、ウワーという気持ちになります。
s main v していろいろ見るに、チェックはこのあたりが関係していそうです。
│ │ ┌─> 0x00003a05 8b3c86 mov edi, dword [rsi + rax*4] │ │ ╎ 0x00003a08 393c83 cmp dword [rbx + rax*4], edi │ │ ╎ 0x00003a0b 0f45d1 cmovne edx, ecx │ │ ╎ 0x00003a0e 48ffc0 inc rax │ │ ╎ 0x00003a11 4883f808 cmp rax, 8 │ │ └─< 0x00003a15 75ee jne 0x3a05
ということで、そのあたりに breakpoint を打ちたいです。
(gdb) b main Breakpoint 1 at 0x3660 (gdb) r Starting program: /mnt/vcipher warning: Error disabling address space randomization: Operation not permitted [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, 0x0000555555557660 in main () (gdb) x/i 0x0000555555557a05 0x555555557a05 <main+933>: mov (%rsi,%rax,4),%edi
それっぽさがありますね。
Breakpoint 2, 0x0000555555557a05 in main () (gdb) x/8wx $rsi 0x5555555620a0 <_ZL14CORRECT_OUTPUT>: 0x345a7191 0xdcc4950a 0x8ad73f4e 0x6006deee 0x5555555620b0 <_ZL14CORRECT_OUTPUT+16>: 0xb474f6a4 0x9620574d 0x7fba5668 0x45cb397e (gdb) x/8wx $rbx 0x7fffffffeba8: 0x1558f6b2 0x1ca7b66f 0x03f6762c 0x094537e9 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0xe48238fe
ここの値が同じになるような入力を与えればよさそうな気がします。一応確かめておきましょう。
(gdb) set *0x7fffffffeba8 = 0x345a7191 (gdb) set *0x7fffffffebac = 0xdcc4950a (gdb) set *0x7fffffffebb0 = 0x8ad73f4e (gdb) set *0x7fffffffebb4 = 0x6006deee (gdb) set *0x7fffffffebb8 = 0xb474f6a4 (gdb) set *0x7fffffffebbc = 0x9620574d (gdb) set *0x7fffffffebc0 = 0x7fba5668 (gdb) set *0x7fffffffebc4 = 0x45cb397e (gdb) c Continuing. The flag is correct! [Inferior 1 (process 30123) exited normally]
よさそうですね。
というところで、じゃぁどんな感じでここが変わるのかというのを調べていきます。
% gdb -ex 'b *0x0000555555557a05' -ex 'r' -ex 'x/8wx $rbx' ./vcipher <<< xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #: #> 0x7fffffffeba8: 0x1558f6b2 0x1ca7b66f 0x03f6762c 0x094537e9 #> 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0xe48238fe #: % gdb -ex 'b *0x0000555555557a05' -ex 'r' -ex 'x/8wx $rbx' ./vcipher <<< xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy #: #> 0x7fffffffeba8: 0x1558f6b2 0x1ca7b66f 0x03f6762c 0x094537e9 #> 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0xc48238fe #: % gdb -ex 'b *0x0000555555557a05' -ex 'r' -ex 'x/8wx $rbx' ./vcipher <<< yxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #: #> 0x7fffffffeba8: 0x1558f692 0x1ca7b66f 0x03f6762c 0x094537e9 #> 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0xe48238fe #: % gdb -ex 'b *0x0000555555557a05' -ex 'r' -ex 'x/8wx $rbx' ./vcipher <<< Alpaca{xxxxxxxxxxxxxxxxxxxxxxxx} #: #> 0x7fffffffeba8: 0x345a7191 0x1cc4950f 0x03f6762c 0x094537e9 #> 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0x448238fe #:
ふんふん? とりあえず、さっきの正解と見比べてみます。
0x5555555620a0: 0x345a7191 0xdcc4950a 0x8ad73f4e 0x6006deee 0x5555555620b0: 0xb474f6a4 0x9620574d 0x7fba5668 0x45cb397e 0x7fffffffeba8: 0x345a7191 0x1cc4950f 0x03f6762c 0x094537e9 0x7fffffffebb8: 0xf095f7a7 0xf7e4b764 0xfd337721 0x448238fe 0x7fffffffeba8: 0xoooooooo 0x.oooooo. 0x........ 0x........ 0x7fffffffebb8: 0x........ 0x........ 0x........ 0xo......o
o でマークした部分は正解のものと一致していそうなので、1 byte ごとに 8 bits ぶん決まりそう?みたいな気持ちになります。
挙動を見た感じだと、3][22][11][00][3 みたいな感じでシフトされていそうな気配があります。
そういえば、入力が 32 bytes で、エンコードされた列も 32 bytes なので、(フラグが複数通りあり得たら嫌なので)全単射になっているんだろうなということを思ってはいました。
ということで結局 1 byte ごとに決める solver を書くのですが、一回の実行にめちゃくちゃ時間がかかるので、めちゃくちゃ時間がかかりそうです。
import sys from pwn import * target = [ 0x345A7191, 0xDCC4950A, 0x8AD73F4E, 0x6006DEEE, 0xB474F6A4, 0x9620574D, 0x7FBA5668, 0x45CB397E, ] context.log_level = "error" flag_raw = ["!"] * 32 mask = [0x00000FF0, 0x000FF000, 0x0FF00000, 0xF000000F] dec = [ lambda x: x >> 4, lambda x: x >> 12, lambda x: x >> 20, lambda x: (x >> 28) | ((x & 0xF) << 4), ] i = int(sys.argv[1]) flag_raw[i] = chr(int(sys.argv[2], 16)) target_i = dec[i % 4](target[i // 4] & mask[i % 4]) print(f"target: {target_i:#04x}") while flag_raw[i] <= "~": c = flag_raw[i] flag = "".join(flag_raw).replace("'", r"'\''") print("current:", flag) p = process( f"printf '%s\n' '{flag}' | gdb -ex 'b *0x0000555555557a05' -ex 'r' -ex 'x/8wx $rbx' vcipher", shell=True, ) p.recvuntil(b"--Type <RET> for more, q to quit, c to continue without paging--") recv1 = p.recvline()[:-1] recv2 = p.recvline()[:-1] p.close() words1 = [*map(lambda x: int(x, 16), recv1.decode().split(":")[1][1:].split("\t"))] words2 = [*map(lambda x: int(x, 16), recv2.decode().split(":")[1][1:].split("\t"))] words = words1 + words2 print(hex(dec[i % 4](words[i // 4]) & 0xFF)) if (target[i // 4] & mask[i % 4]) == (words[i // 4] & mask[i % 4]): print(c, flush=True) break flag_raw[i] = chr(ord(flag_raw[i]) + 1)
とりあえずこんな感じで、添字と開始文字を渡して全探索できるコードを書きました。 これを複窓で 20 並列くらいさせれば余裕でしょと思ったのですが、3–4 窓くらいでだいぶ限界み(プロセスの生成がめちゃ遅い)を感じたのでやめました。
% python3 after/solve-c.py 7 41 #> target: 0xad #> current: !!!!!!!A!!!!!!!!!!!!!!!!!!!!!!!! #> 0x83 #> current: !!!!!!!B!!!!!!!!!!!!!!!!!!!!!!!! #> 0x85 #: #> current: !!!!!!!V!!!!!!!!!!!!!!!!!!!!!!!! #> 0xad #> V
Alpaca{V...} ということなので、
からエスパーするに V3r1l0g... とかなのかな?と予想したりしました。当たっていたのでウケました。
なにやら上位 4 bits は固まって現れそう?というのと、それっぽい文章になっていそうというのからエスパーして、がちゃがちゃ試しました。 手作業で 40 分くらい(コードを修正しつつ)がんばりながら、フラグは手に入れたので一応満足です。
冷静になると、フラグの長さが既知で、各 byte ごとに並列してできるので、'!' * 32, '"' * 32, ... みたいにして探索すればいいんですよね(ということに、上記を書いてから「まともな解法わからんな〜」と考えながらようやく気づきました)。
from pwn import * target = [ 0x345A7191, 0xDCC4950A, 0x8AD73F4E, 0x6006DEEE, 0xB474F6A4, 0x9620574D, 0x7FBA5668, 0x45CB397E, ] mask = [0x00000FF0, 0x000FF000, 0x0FF00000, 0xF000000F] dec = [ lambda x: x >> 4, lambda x: x >> 12, lambda x: x >> 20, lambda x: (x >> 28) | ((x & 0xF) << 4), ] def get(words, i): return dec[i % 4](words[i // 4]) & 0xFF table = [[0] * 256 for _ in range(32)] for c in range(0x0, 0x100): print(f"current: {c:#04x}") p = process("gdb vcipher", shell=True) p.sendline(b"b *0x00005555555577c0") p.sendline(b"b *0x0000555555557a05") p.sendline(b"r") p.recvuntil(b"Input 32-character flag: ") p.sendline(b"0" * 32) p.recvuntil(b"Breakpoint 1,") p.recvline() p.sendline(b"p $rsp + 0x8") sp = int(p.recvline()[41:55].decode(), 16) p.sendline(f"x/a {hex(sp)}".encode()) s = int(p.recvline()[35:49].decode(), 16) p.sendline(f"set *(long*){hex(s+0x00)} = {0x0101010101010101 * c}".encode()) p.sendline(f"set *(long*){hex(s+0x08)} = {0x0101010101010101 * c}".encode()) p.sendline(f"set *(long*){hex(s+0x10)} = {0x0101010101010101 * c}".encode()) p.sendline(f"set *(long*){hex(s+0x18)} = {0x0101010101010101 * c}".encode()) p.sendline(b"c") p.recvuntil(b"Breakpoint 2,") p.recvline() p.sendline(b"x/8wx $rbx") recv1 = p.recvline()[:-1] recv2 = p.recvline()[:-1] p.close() words1 = [*map(lambda x: int(x, 16), recv1.decode().split(":")[1][1:].split("\t"))] words2 = [*map(lambda x: int(x, 16), recv2.decode().split(":")[1][1:].split("\t"))] words = words1 + words2 for i in range(32): table[i][c] = get(words, i) for i in range(32): res = map(lambda x: f"{x:#04x}", table[i]) print(f'[{i}]: {", ".join(res)}')
入力は適当に与えてしまって、後からデバッガでよい感じの入力を与えたことにすれば、空白文字なりなんなりの制限がある文字列も「与えた」ことにできるんですよね。というので、そういうのを書きました。
どうやら 0x80 以上の byte を与えたときは全単射じゃないっぽそうでしたが、それ未満では下記のような規則になっていそうでした。
0x00: {9,8,b,a,d,c,f,e,1,0,3,2,5,4,7,6}{b,9,f,d,3,1,7,5}
0x01: {7,6,5,4,3,2,1,0,f,e,d,c,b,a,9,8}{f,d,b,9,7,5,3,1}
0x02: {a,b,8,9,e,f,c,d,2,3,0,1,6,7,4,5}{5,7,1,3,d,f,9,b}
0x03: {d,c,f,e,9,8,b,a,5,4,7,6,1,0,3,2}{1,3,5,7,9,b,d,f}
0x04: {9,8,b,a,d,c,f,e,1,0,3,2,5,4,7,6}{6,4,2,0,e,c,a,8}
0x05: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{b,9,f,d,3,1,7,5}
0x06: {3,2,1,0,7,6,5,4,b,a,9,8,f,e,d,c}{a,8,e,c,2,0,6,4}
0x07: {0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}{1,3,5,7,9,b,d,f}
0x08: {9,8,b,a,d,c,f,e,1,0,3,2,5,4,7,6}{2,0,6,4,a,8,e,c}
0x09: {9,8,b,a,d,c,f,e,1,0,3,2,5,4,7,6}{7,5,3,1,f,d,b,9}
0x0a: {c,d,e,f,8,9,a,b,4,5,6,7,0,1,2,3}{f,d,b,9,7,5,3,1}
0x0b: {3,2,1,0,7,6,5,4,b,a,9,8,f,e,d,c}{0,2,4,6,8,a,c,e}
0x0c: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{e,c,a,8,6,4,2,0}
0x0d: {a,b,8,9,e,f,c,d,2,3,0,1,6,7,4,5}{3,1,7,5,b,9,f,d}
0x0e: {6,7,4,5,2,3,0,1,e,f,c,d,a,b,8,9}{4,6,0,2,c,e,8,a}
0x0f: {6,7,4,5,2,3,0,1,e,f,c,d,a,b,8,9}{0,2,4,6,8,a,c,e}
0x10: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{a,8,e,c,2,0,6,4}
0x11: {a,b,8,9,e,f,c,d,2,3,0,1,6,7,4,5}{f,d,b,9,7,5,3,1}
0x12: {f,e,d,c,b,a,9,8,7,6,5,4,3,2,1,0}{9,b,d,f,1,3,5,7}
0x13: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{f,d,b,9,7,5,3,1}
0x14: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{6,4,2,0,e,c,a,8}
0x15: {b,a,9,8,f,e,d,c,3,2,1,0,7,6,5,4}{b,9,f,d,3,1,7,5}
0x16: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{e,c,a,8,6,4,2,0}
0x17: {b,a,9,8,f,e,d,c,3,2,1,0,7,6,5,4}{f,d,b,9,7,5,3,1}
0x18: {8,9,a,b,c,d,e,f,0,1,2,3,4,5,6,7}{2,0,6,4,a,8,e,c}
0x19: {c,d,e,f,8,9,a,b,4,5,6,7,0,1,2,3}{7,5,3,1,f,d,b,9}
0x1a: {2,3,0,1,6,7,4,5,a,b,8,9,e,f,c,d}{3,1,7,5,b,9,f,d}
0x1b: {e,f,c,d,a,b,8,9,6,7,4,5,2,3,0,1}{f,d,b,9,7,5,3,1}
0x1c: {7,6,5,4,3,2,1,0,f,e,d,c,b,a,9,8}{f,d,b,9,7,5,3,1}
0x1d: {d,c,f,e,9,8,b,a,5,4,7,6,1,0,3,2}{3,1,7,5,b,9,f,d}
0x1e: {b,a,9,8,f,e,d,c,3,2,1,0,7,6,5,4}{8,a,c,e,0,2,4,6}
0x1f: {1,0,3,2,5,4,7,6,9,8,b,a,d,c,f,e}{e,c,a,8,6,4,2,0}
たとえば、0x02: {a,b,8,9,e,f,c,d,2,3,0,1,6,7,4,5}{5,7,1,3,d,f,9,b} は、「添字が [2] の byte は 00 のとき a5 が返ってくる」「01 のとき a7」「07 のとき ab」「08 のとき b5」... のような意味で書いています。45 が返ってくるのは 70 のときで、これは p のことですね。
結局この表はなんですか?(?)ちゃんと †rev† すればわかる感じですか? 特に Verilog まわりのことはよくわかっていません。
こういう表になる前提であれば、そもそも 00 01 02 03 ... 07 08 10 18 20 ... 70 78 の 23 通りだけ試せばよさそうな気がしてきました(実際にはフラグに特殊文字は入らなさそうだからちょっと減りそう)。
所感
rev は、(まだ体系立てた勉強をしていないせいもあるかもですが)なんというか ad hoc っぽい気持ちになるというか、「今回はそういう簡単なエンコードをされていたからできたけど、そうじゃなかったらどうしようもなくない?」みたいな気持ちになります。 (解きようがない問題は出題されない気もしますが?)
と思ったんですが、そもそも自分がやったのは rev ではなくて実験とエスパーな気がしてきました。(r2 や gdb などで多少の assembly を読んでいるとはいえ)decompile しようとしたりせず雰囲気でやっているのが微妙そうです。
あまり長くない時間のコンテストで答えを出す前提だと仕方なさもありそうですが、復習はした方がよさそうだなと思いました。
pwn だと ROP なり ret2whatever なりの概ね体系立った「まぁざっくりこういう方針のことをやるよね」というのがあると思うんですが、rev だとどういう感じなんですかね。デバッガを使いつつ実験しながら脳筋でやるのは正統派ではなさそう(というか正統派でなくてほしい)みたいな気持ちはあります。時には必要ではありそうだとは思いつつですが。
とりあえず、(小さめの整数)/(それなりの整数) を見れたのでうれしい気持ちになりました。
おわり
おわりです。