picoCTF Investigative Reversing 0 Writeup Using Ghidra

Ghidraのデコンパイル誤りを訂正する訓練になるので記録に残しておく。

https://play.picoctf.org/practice?page=1&search=Investigative%20Reversing

問題としては，ELFバイナリ「mystery」と PNG画像「mystery.png」が渡される。

mystery.png を確認すると，フッターの後ろにフラグっぽいのが見える。
フラグの長さは26バイト。
が， 0x80 など ASCII 外のコードがある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/f9d50b06-0e57-94df-da1e-d3f16209ddb1.png)

Ghidraでデコンパイルしてみる

```c
void main(void)
{
  FILE *__stream;
  FILE *__stream_00;
  size_t sVar1;
  long in_FS_OFFSET;
  int local_54;
  int local_50;
  char local_38 [4];
  char local_34;
  char local_33;
  char local_29;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  __stream_00 = fopen("mystery.png","a");
  if (__stream == (FILE *)0x0) {
    puts("No flag found, please make sure this is run on the server");
  }
  if (__stream_00 == (FILE *)0x0) {
    puts("mystery.png is missing, please run this on the server");
  }
  sVar1 = fread(local_38,0x1a,1,__stream);
  if ((int)sVar1 < 1) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("at insert");
  fputc((int)local_38[0],__stream_00);
  fputc((int)local_38[1],__stream_00);
  fputc((int)local_38[2],__stream_00);
  fputc((int)local_38[3],__stream_00);
  fputc((int)local_34,__stream_00);
  fputc((int)local_33,__stream_00);
  local_54 = 6;
  while (local_54 < 0xf) {
    fputc((int)(char)(local_38[local_54] + '\x05'),__stream_00);
    local_54 = local_54 + 1;
  }
  fputc((int)(char)(local_29 + -3),__stream_00);
  local_50 = 0x10;
  while (local_50 < 0x1a) {
    fputc((int)local_38[local_50],__stream_00);
    local_50 = local_50 + 1;
  }
  fclose(__stream_00);
  fclose(__stream);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
```

fputc をネットで調べるとファイルに追記する関数とわかる。
よって，この ELF は， flag.txt を読んで，フラグに何らかの細工（暗号化）をして， mystery.png の末尾に付加するものと予測する。

この処理を見ていく中で最大の問題は，変数 local_29 に対する代入が無く，値が不明ということだ。
ピンク先生は「アサインされていない変数」という言い方をされていた。
英語で書くと　local_29 is not assigned という感じ。

こういうのはたいていFunction Signatureを変更するか，別の変数の型を変更すると自動的に直る場合が多い。

Ghidra のデコンパイル誤りを正していく。
local_38 は flag.txt の内容が入るバッファなので，変数名を flag に。
そして一番重要なのが，型。
前記 mystery.png をバイナリエディタで確認した時，フラグに長さが 26バイト だったので，
local_38 の型を char[26] に変更する。

```c
void main(void)

{
  long lVar1;
  FILE *__stream;
  FILE *__stream_00;
  size_t sVar2;
  long in_FS_OFFSET;
  int local_54;
  int local_50;
  char flag [26];
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  __stream_00 = fopen("mystery.png","a");
  if (__stream == (FILE *)0x0) {
    puts("No flag found, please make sure this is run on the server");
  }
  if (__stream_00 == (FILE *)0x0) {
    puts("mystery.png is missing, please run this on the server");
  }
  sVar2 = fread(flag,0x1a,1,__stream);
  if ((int)sVar2 < 1) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("at insert");
  fputc((int)flag[0],__stream_00);
  fputc((int)flag[1],__stream_00);
  fputc((int)flag[2],__stream_00);
  fputc((int)flag[3],__stream_00);
  fputc((int)flag[4],__stream_00);
  fputc((int)flag[5],__stream_00);
  local_54 = 6;
  while (local_54 < 0xf) {
    fputc((int)(char)(flag[local_54] + '\x05'),__stream_00);
    local_54 = local_54 + 1;
  }
  fputc((int)(char)(flag[15] + -3),__stream_00);
  local_50 = 0x10;
  while (local_50 < 0x1a) {
    fputc((int)flag[local_50],__stream_00);
    local_50 = local_50 + 1;
  }
  fclose(__stream_00);
  fclose(__stream);
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
```

ビンゴ！
アサインされていない変数 local_29 が消えた。

さらに読みやすくする
local_54　の変数名を i に
__stream_00　の変数名を __stream_png に
local_50 の変数名を j に

```c
void main(void)

{
  long lVar1;
  FILE *__stream;
  FILE *__stream_png;
  size_t sVar2;
  long in_FS_OFFSET;
  int i;
  int j;
  char flag [26];
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  __stream_png = fopen("mystery.png","a");
  if (__stream == (FILE *)0x0) {
    puts("No flag found, please make sure this is run on the server");
  }
  if (__stream_png == (FILE *)0x0) {
    puts("mystery.png is missing, please run this on the server");
  }
  sVar2 = fread(flag,0x1a,1,__stream);
  if ((int)sVar2 < 1) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("at insert");
  fputc((int)flag[0],__stream_png);
  fputc((int)flag[1],__stream_png);
  fputc((int)flag[2],__stream_png);
  fputc((int)flag[3],__stream_png);
  fputc((int)flag[4],__stream_png);
  fputc((int)flag[5],__stream_png);
  i = 6;
  while (i < 0xf) {
    fputc((int)(char)(flag[i] + '\x05'),__stream_png);
    i = i + 1;
  }
  fputc((int)(char)(flag[15] + -3),__stream_png);
  j = 0x10;
  while (j < 0x1a) {
    fputc((int)flag[j],__stream_png);
    j = j + 1;
  }
  fclose(__stream_png);
  fclose(__stream);
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
```

コードが読みやすくなったのであとはソルバーを書くだけ

```python
# enc_string was extracted from behind the footer of mystery.png
enc_string = "picoCTK\x80k5zsid6q_3d659f57}"

enc = list(enc_string)
#print(enc)
    
ans = []
    
i = 0
while i < 0x6:
    ans.append(ord(enc[i]))
    i = i + 1
    
while i < 0xf:
    ans.append(ord(enc[i]) - 0x5)
    i = i + 1
    
ans.append(ord(enc[15]) + 0x3)

i = 0x10
while i < 0x1a:
    ans.append(ord(enc[i]))
    i = i + 1

print(''.join(map(chr,ans)))
```