PicoCTF2024 Writeup：Reverse Engineering


# 最初に

picoDTF2024のReverse Engineeringをやっていきます！！

# Writeup

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152468/5f1f6f87-a929-b6e0-54e0-ae479f3781a8.png)


Windowsのデバックの以下3問は初めて取り組む内容だったことと、Writeupを読んでも理解できなかったので、ちょっとまた今度取り組もう。。。

- WinAntiDbg0x100
- WinAntiDbg0x200
- WinAntiDbg0x300


## weirdSnake

Pythonバイトコードで記載されたファイルが与えられます

Copilotで元のPythonコードすると以下になりますが(コメント付き)、その出力が`NiF3jF^wJ_V▒]ok:2M1K;▒Mne7"a9otZeFj:`で文字化けしている。。。。

```
# 定数をロードしてリストを構築
input_list = [4, 54, 41, 0, 112, 32, 25, 49, 33, 3, 0, 0, 57, 32, 108, 23, 48, 4, 9, 70, 7, 110, 36, 8, 108, 7, 49, 10, 4, 86, 43, 102, 126, 92, 0, 16, 58, 41, 89, 78]

# キー文字列を構築
key_str = "J"
key_str += "_"
key_str += "o"
key_str += "3"
key_str += "t"


# キー文字列を文字コードのリストに変換
key_list = [ord(char) for char in key_str]

# キーリストの長さが入力リストの長さ以上になるまでキーリストを拡張
while len(key_list) < len(input_list):
    key_list.extend(key_list)

# 入力リストとキーリストをXORで結合し結果を得る
result = [a ^ b for a, b in zip(input_list, key_list)]

# 結果を文字列に変換
result_text = ''.join(map(chr, result))

```


ここから分からなくなったので、以下のWriteupを見てみると、key_strの連結方法が違っていた。。。。

https://zenn.dev/tetsurou/articles/8707464f8d493b


確かに、よくよく`snake`を見ると、5文字がすべて同じではないですね、、、

```

  2          84 LOAD_CONST              30 ('J')
             86 STORE_NAME               1 (key_str)

  3          88 LOAD_CONST              31 ('_')
             90 LOAD_NAME                1 (key_str)
             92 BINARY_ADD
             94 STORE_NAME               1 (key_str)

  4          96 LOAD_NAME                1 (key_str)
             98 LOAD_CONST              32 ('o')
            100 BINARY_ADD
            102 STORE_NAME               1 (key_str)

  5         104 LOAD_NAME                1 (key_str)
            106 LOAD_CONST              33 ('3')
            108 BINARY_ADD
            110 STORE_NAME               1 (key_str)

  6         112 LOAD_CONST              34 ('t')
            114 LOAD_NAME                1 (key_str)
            116 BINARY_ADD
            118 STORE_NAME               1 (key_str)
```

これに注意してプログラムを書き換えたら、うまくいきました！



## packer

outという実行ファイルが与えられる
実行してみるとパスワードを求められるので、逆コンパイルした実行ファイルからパスワードを見つける問題と思われる
```
                                                                                                
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ ./out 
Enter the password to unlock this file: aaa
You entered: aaa

Access denied

```


Ghidraを使ったりしていたのですが、手がかりがなく、あきらめて以下のWriteupをみたところ、upxでアンパックするみたいでした、、、そうかタイトルがpackerだからか、、、

https://zenn.dev/tetsurou/articles/8707464f8d493b


実際にやってみると、見つかりました！

```
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ upx -d out                                     
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
[WARNING] bad b_info at 0x4b718

[WARNING] ... recovery at 0x4b714

    877724 <-    336520   38.34%   linux/amd64   out

Unpacked 1 file.
                                                                                                  
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ strings out | grep pico
                                                                                                  
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ strings out | grep flag
Password correct, please see flag: 7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f62646438343839337d
(mode_flags & PRINTF_FORTIFY) != 0
WARNING: Unsupported flag value(s) of 0x%x in DT_FLAGS_1.
version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST)
flag.c
_dl_x86_hwcap_flags
_dl_stack_flags
```


## FactCheck


binという実行ファイルが一つ与えられます

```
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ file bin         
bin: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f837edf11d5c6ec47ce468ef605df9f1015af9f2, for GNU/Linux 3.2.0, not stripped
                                                                                                  
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ strings bin | grep flag

```

packerの問題の反省からupxでパッキングされているかも確認します
```
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ upx -d bin bin_unpack                          
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2024
UPX 4.2.2       Markus Oberhumer, Laszlo Molnar & John Reiser    Jan 3rd 2024

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: bin: NotPackedException: not packed by UPX
upx: bin_unpack: FileNotFoundException: bin_unpack: No such file or directory

Unpacked 0 files.
```


実行してみても何も起こらなかったです。。。
```                                                                                   
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ ./bin
                                                                                                  
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ 

```

Ghidraを使ってみると、`picoCTF{wELF_d0N3_mate_`というフラグの前半らしき文字列が。。。！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152468/ea515477-9895-8d5f-efdf-e331df2a50e2.png)


```
std::string::string(local_248,"picoCTF{wELF_d0N3_mate_",&local_249);
```



この文字列が使われている関数で、文字列が定義された後に、複数の文字列が定義されていて、フラグの前半の文字列に文字を追加していくのですが、4つはIf文で条件に合えば追加するようになっています。

条件に合致しているかどうかの判定はそこまで難しくなく、前半2つがTrueで、後半2つがFalseです。

後は文字列をつなげればフラグが手に入ります！

picoCTF{wELF_d0N3_mate_********}







## Classic Crackme 0x100

crackme100 という実行ファイルが与えられました

```
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ file crackme100  
crackme100: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1ea93b145c2407916c88a68aa90f0a373edeb42a, for GNU/Linux 3.2.0, with debug_info, not stripped
```


`Here is your flag: %s`からフラグは動的に作成しているみたい
```
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ strings crackme100| grep flag
picoCTF{sample_flag}
SUCCESS! Here is your flag: %s
_flags
_flags2
```

試しに実行してみると、パスワードが求められますね
```                          
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ ./crackme100 
Enter the secret password: aa
FAILED!
```



Ghidraを使うとmain関数にパスワードの判定ロジックがありました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2152468/10d10014-5a7f-bf83-b2c7-dc933b9ae592.png)

入力したinputがいろいろ変換されて、outputと等しくなればいいみたいです。

とりあえずoutputの文字列をそのまま入れてみましたが、ダメでした（念のための確認！）
```                                
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ ./crackme100
Enter the secret password: xjagpediegzqlnaudqfwyncpvkqneusycourkguerjpzcbstcc
FAILED!
```


判定ロジックがわかったから、あとはもとに戻すだけだ！と思ったのですが、これが難しい。。。。
Writeupをみてましたが、正直ビット計算が入るとよくわからなくなってしまう、、、

これは今後の課題としておいておきます。。。


# 最後に

今回学んだことは以下
- 生成AIに書かせたプログラムは結構間違っている・・・
- IDAは習得しないといけない。。。














# 備忘録

個人的に試した結果を記載しておきます。
問題を解くうえではあまり関係ないです。。。


## WinAntiDbg0x300の実行ファイルをGhidraで逆コンパイルした結果


```

/* WARNING: Control flow encountered bad instruction data */

void entry(void)

{
  char cVar1;
  undefined uVar2;
  char cVar3;
  byte bVar4;
  undefined4 uVar5;
  uint uVar6;
  HMODULE hModule;
  FARPROC pFVar7;
  int iVar8;
  int *piVar9;
  undefined4 *puVar10;
  uint uVar11;
  uint uVar12;
  uint uVar13;
  uint *puVar14;
  UINT unaff_EDI;
  undefined4 *puVar15;
  char *pcVar16;
  int *lpProcName;
  int *piVar17;
  int *piVar18;
  byte *pbVar19;
  byte *pbVar20;
  bool bVar21;
  bool bVar22;
  bool bVar23;
  undefined local_80 [72];
  undefined4 uStackY_38;
  
  puVar14 = &DAT_00416000;
  puVar15 = (undefined4 *)&DAT_00401000;
  uVar13 = 0xffffffff;
  do {
    uVar11 = *puVar14;
    bVar21 = puVar14 < (uint *)0xfffffffc;
    puVar14 = puVar14 + 1;
    bVar22 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar21);
    uVar11 = uVar11 * 2 + (uint)bVar21;
    do {
      if (bVar22) {
        uVar2 = *(undefined *)puVar14;
        puVar14 = (uint *)((int)puVar14 + 1);
        *(undefined *)puVar15 = uVar2;
        puVar15 = (undefined4 *)((int)puVar15 + 1);
      }
      else {
        uVar6 = 1;
        do {
          do {
            bVar21 = CARRY4(uVar11,uVar11);
            uVar12 = uVar11 * 2;
            if (uVar12 == 0) {
              uVar11 = *puVar14;
              bVar22 = puVar14 < (uint *)0xfffffffc;
              puVar14 = puVar14 + 1;
              bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
              uVar12 = uVar11 * 2 + (uint)bVar22;
            }
            uVar6 = uVar6 * 2 + (uint)bVar21;
            uVar11 = uVar12 * 2;
          } while (!CARRY4(uVar12,uVar12));
          if (uVar11 != 0) break;
          uVar12 = *puVar14;
          bVar21 = puVar14 < (uint *)0xfffffffc;
          puVar14 = puVar14 + 1;
          uVar11 = uVar12 * 2 + (uint)bVar21;
        } while (!CARRY4(uVar12,uVar12) && !CARRY4(uVar12 * 2,(uint)bVar21));
        if (2 < uVar6) {
          uVar2 = *(undefined *)puVar14;
          puVar14 = (uint *)((int)puVar14 + 1);
          uVar13 = CONCAT31((int3)uVar6 + -3,uVar2) ^ 0xffffffff;
          if (uVar13 == 0) {
            pcVar16 = &DAT_00401000;
            iVar8 = 0x193;
            do {
              cVar3 = *pcVar16;
              pcVar16 = pcVar16 + 1;
              while (((byte)(cVar3 + 0x18U) < 2 && (*pcVar16 == '\0'))) {
                uVar5 = *(undefined4 *)pcVar16;
                cVar3 = pcVar16[4];
                *(undefined1 **)pcVar16 =
                     &DAT_00401000 +
                     (CONCAT31(CONCAT21((ushort)uVar5 >> 8,(char)((uint)uVar5 >> 0x10)),
                               (char)((uint)uVar5 >> 0x18)) - (int)pcVar16);
                pcVar16 = pcVar16 + 5;
                iVar8 = iVar8 + -1;
                if (iVar8 == 0) {
                  lpProcName = &DAT_00418000;
                  do {
                    if (*lpProcName == 0) {
                      puVar15 = (undefined4 *)0x400ffc;
                      pbVar19 = (byte *)(lpProcName + 1);
                      while( true ) {
                        bVar4 = *pbVar19;
                        uVar13 = (uint)bVar4;
                        pbVar20 = pbVar19 + 1;
                        if (uVar13 == 0) break;
                        if (0xef < bVar4) {
                          uVar13 = CONCAT12(bVar4,*(undefined2 *)pbVar20) & 0xff0fffff;
                          pbVar20 = pbVar19 + 3;
                        }
                        puVar15 = (undefined4 *)((int)puVar15 + uVar13);
                        uVar5 = *puVar15;
                        *puVar15 = &DAT_00401000 +
                                   CONCAT31(CONCAT21(CONCAT11((char)uVar5,(char)((uint)uVar5 >> 8)),
                                                     (char)((uint)uVar5 >> 0x10)),
                                            (char)((uint)uVar5 >> 0x18));
                        pbVar19 = pbVar20;
                      }
                      uStackY_38 = 0x419548;
                      VirtualProtect(&IMAGE_DOS_HEADER_00400000,0x1000,4,(PDWORD)&stack0xffffffdc);
                    /* WARNING: Read-only address (ram,0x00400217) is written */
                      IMAGE_SECTION_HEADER_004001f0.Characteristics._3_1_ = 0x60;
                    /* WARNING: Read-only address (ram,0x0040023f) is written */
                      IMAGE_SECTION_HEADER_00400218.Characteristics._3_1_ = 0x60;
                      uStackY_38 = 0x41955d;
                      VirtualProtect(&IMAGE_DOS_HEADER_00400000,0x1000,uVar13,
                                     (PDWORD)&stack0xffffffdc);
                      do {
                      } while (&stack0x00000000 != local_80);
                    /* WARNING: Bad instruction - Truncating control flow here */
                      halt_baddata();
                    }
                    puVar15 = (undefined4 *)(&DAT_00401000 + lpProcName[1]);
                    piVar18 = lpProcName + 2;
                    hModule = LoadLibraryA((LPCSTR)((int)&DWORD_0041c9b0 + *lpProcName));
                    while( true ) {
                      cVar3 = *(char *)piVar18;
                      lpProcName = (int *)((int)piVar18 + 1);
                      if (cVar3 == '\0') break;
                      piVar9 = lpProcName;
                      piVar17 = lpProcName;
                      do {
                        piVar18 = piVar17;
                        if (piVar9 == (int *)0x0) break;
                        piVar9 = (int *)((int)piVar9 + -1);
                        piVar18 = (int *)((int)piVar17 + 1);
                        cVar1 = *(char *)piVar17;
                        piVar17 = piVar18;
                      } while ((char)(cVar3 + -1) != cVar1);
                      pFVar7 = GetProcAddress(hModule,(LPCSTR)lpProcName);
                      if (pFVar7 == (FARPROC)0x0) {
                    /* WARNING: Subroutine does not return */
                        ExitProcess(unaff_EDI);
                      }
                      *puVar15 = pFVar7;
                      puVar15 = puVar15 + 1;
                    }
                  } while( true );
                }
              }
            } while( true );
          }
        }
        bVar21 = CARRY4(uVar11,uVar11);
        uVar11 = uVar11 * 2;
        if (uVar11 == 0) {
          uVar11 = *puVar14;
          bVar22 = puVar14 < (uint *)0xfffffffc;
          puVar14 = puVar14 + 1;
          bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
          uVar11 = uVar11 * 2 + (uint)bVar22;
        }
        bVar22 = CARRY4(uVar11,uVar11);
        uVar11 = uVar11 * 2;
        if (uVar11 == 0) {
          uVar11 = *puVar14;
          bVar23 = puVar14 < (uint *)0xfffffffc;
          puVar14 = puVar14 + 1;
          bVar22 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar23);
          uVar11 = uVar11 * 2 + (uint)bVar23;
        }
        iVar8 = (uint)bVar21 * 2 + (uint)bVar22;
        if (iVar8 == 0) {
          iVar8 = 1;
          do {
            do {
              bVar21 = CARRY4(uVar11,uVar11);
              uVar6 = uVar11 * 2;
              if (uVar6 == 0) {
                uVar11 = *puVar14;
                bVar22 = puVar14 < (uint *)0xfffffffc;
                puVar14 = puVar14 + 1;
                bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
                uVar6 = uVar11 * 2 + (uint)bVar22;
              }
              iVar8 = iVar8 * 2 + (uint)bVar21;
              uVar11 = uVar6 * 2;
            } while (!CARRY4(uVar6,uVar6));
            if (uVar11 != 0) break;
            uVar6 = *puVar14;
            bVar21 = puVar14 < (uint *)0xfffffffc;
            puVar14 = puVar14 + 1;
            uVar11 = uVar6 * 2 + (uint)bVar21;
          } while (!CARRY4(uVar6,uVar6) && !CARRY4(uVar6 * 2,(uint)bVar21));
          iVar8 = iVar8 + 2;
        }
        uVar6 = iVar8 + 1 + (uint)(uVar13 < 0xfffff300);
        puVar10 = (undefined4 *)((int)puVar15 + uVar13);
        if (uVar13 < 0xfffffffd) {
          do {
            uVar5 = *puVar10;
            puVar10 = puVar10 + 1;
            *puVar15 = uVar5;
            puVar15 = puVar15 + 1;
            bVar21 = 3 < uVar6;
            uVar6 = uVar6 - 4;
          } while (bVar21 && uVar6 != 0);
          puVar15 = (undefined4 *)((int)puVar15 + uVar6);
        }
        else {
          do {
            uVar2 = *(undefined *)puVar10;
            puVar10 = (undefined4 *)((int)puVar10 + 1);
            *(undefined *)puVar15 = uVar2;
            puVar15 = (undefined4 *)((int)puVar15 + 1);
            uVar6 = uVar6 - 1;
          } while (uVar6 != 0);
        }
      }
      bVar22 = CARRY4(uVar11,uVar11);
      uVar11 = uVar11 * 2;
    } while (uVar11 != 0);
  } while( true );
}
```


## Classic Crackme 0x100

Ghidraで出力されたパスワード判定ロジックを記載しておきます



```

/* WARNING: Unknown calling convention */

int main(void)

{
  uint uVar1;
  int iVar2;
  size_t sVar3;
  char input [51];
  char output [51];
  int random2;
  int random1;
  char fix;
  int secret3;
  int secret2;
  int secret1;
  int len;
  int i_1;
  int i;
  
  builtin_strncpy(output,"xjagpediegzqlnaudqfwyncpvkqneusycourkguerjpzcbstcc",0x33);
  setvbuf(stdout,(char *)0x0,2,0);
  printf("Enter the secret password: ");
  __isoc99_scanf(&DAT_00402024,input);
  i = 0;
  sVar3 = strlen(output);
  for (; i < 3; i = i + 1) {
    for (i_1 = 0; i_1 < (int)sVar3; i_1 = i_1 + 1) {
      uVar1 = (i_1 % 0xff >> 1 & 0x55U) + (i_1 % 0xff & 0x55U);
      uVar1 = ((int)uVar1 >> 2 & 0x33U) + (uVar1 & 0x33);
      iVar2 = ((int)uVar1 >> 4) + input[i_1] + -0x61 + (uVar1 & 0xf);
      input[i_1] = (char)iVar2 + (char)(iVar2 / 0x1a) * -0x1a + 'a';
    }
  }
  iVar2 = memcmp(input,output,(long)(int)sVar3);
  if (iVar2 == 0) {
    printf("SUCCESS! Here is your flag: %s\n","picoCTF{sample_flag}");
  }
  else {
    puts("FAILED!");
  }
  return 0;
}

```