0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PicoCTF2024 Writeup:Reverse Engineering

Posted at

最初に

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

Writeup

image.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の連結方法が違っていた。。。。

確かに、よくよく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だからか、、、

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

┌──(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

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

入力した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;
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?