RFpwner

Reverse-Engineering

2025-03-04

配置连接

1.运行ssh-keygen -f key -N ''命令,这会在当前目录下生成key和key.pub文件,分别是公钥和私钥。

2.cat key.pub打印key.pub文件内容。

3.将打印出的内容复制粘贴到Settings->SSH Key中。

4.ssh -i key hacker@dojo.pwn.college连接至靶机

ssh -i key hacker@dojo.pwn.college

视频内容

intro

QQ_1731939439682

这个过程中,信息总会丢失一些,所以逆向就是挖掘这些

QQ_1731939622787

前向工程工具

QQ_1731940359988

编译过程中会删除掉所有的宏定义,注释,之后生成汇编代码,并且替换掉了变量,用地址和偏移进行替换,通过strip 命令,可以移除调试符号和其他不必要的信息,从而减少文件大小并且提高加载效率,

QQ_1731940160453

gcc -g可以包括所有的,包括像类型,变量名变量大小等调试信息

function and frames

QQ_1731940466390

QQ_1731940589345

QQ_1731940673003

函数其实可以被表示为一个图,每一个块都是将一条条执行的指令,然后块被边连接着,也就是各种条件或者无条件跳转,之后通过理解这些触发跳转的条件,来理解函数逻辑

QQ_1731940867906

蓝线代表着无条件跳转,绿线代表着条件跳转,

image-20241118224510754

局部变量放在了栈上面,

QQ_1731941491422

call之后先压栈下一条指令的地址,之后存储rbp,

退栈不物理清除数据,因此可能导致数据泄漏,

QQ_1731941685339

data access

QQ_1731941873567

栈上的数据

QQ_1731941936296

elf节中的变量

image-20241118230116314

这些一般都和指令有固定的偏移,通常通过rip 相对寻址去进行访问

QQ_1731942152969

会发现,程序被映射到内存中两次,一次是代码,一次是用于数据段,因此,代码和数据是从两个不同的区域进行访问的,

堆中的数据

QQ_1731942313133

访问堆的指针通常存放在栈上,第一个存放在栈上的是数据,直接取出来rsp,rsp相当于是他的地址,之后解引用就可以获得数据,而第二个相当于将访问数据的指针存放在了栈上,获取到指针之后还需要堆指针进行进一步的解引用才能获取到后续的数据

数据结构

QQ_1731942546591

获得该类数据你需要知道他都有什么东西,然后怎么存储的,怎么访问的怎么使用的,然后才能逆向出正确的数据

静态逆向工具

指的是,不运行的时候就能分析的工具,

QQ_1731942696072

image-20241118231523162

比如checksec后发现了有12个符号,之后通过nm -a来列举所有的符号,

QQ_1731942981591

课程里用的binary ninja cloud,免费

QQ_1731943334981

左上角这里,这里可以显示更多的信息,包括指令详细的操作码字节等等,

QQ_1731943413054

这里可以显示更熟悉的线性的界面,右键指令可以进行注释

QQ_1731943510694

这里的高级分析,能够分析的更加详细,并且进行反编译,

QQ_1731943704073

QQ_1731943737736

但是他有可能抽象错细节,所以在这个课程里最好还是只用汇编视图

Dynamic

QQ_1732157745858

一个是追踪库调用,一个是追踪系统调用

QQ_1732157926525

强烈建议加入init文件的

info proc map,能够查看映射的地址

QQ_1732158363246

QQ_1732158430150

位置相关代码始终加载在同一个内存位置,但是内存无关代码并不是,gdb尝试永远在这个范围加载,最简单的就是通过set $base

QQ_1732158581019

通过-M可以指定汇编类型

QQ_1732158757967

gdb自带的重放比较低效,因此,rr更高效,但pwncollege不能用rr,kira比较简洁,用于逆向工程,还有reverse step in,rsi,会反向进行移动,前提是前面进行了record,但是有时候不准,当再次进行录音的时候,不会再次系统调用,只是重播录音

real world app

challenge

Level1.0

QQ_1732081107293

看一下程序

QQ_1732081126665

看这个样子,read读入了输出,打印出来对应的ascii码,然后,跟他对应的去比,得到最后的结果,对就给flag,那输入他要的就行

level1.1

没啥区别啊感觉,

QQ_1732161158309

然后那个地址点进去,是数据段的一个数据

QQ_1732161177340

Level2.0

QQ_1732166871690

1和4倒换jvsyo

那就是yvsjo,显示不对,那就是josyv

Level2.1

gdkjs

那就是gskjd

QQ_1732167278042

buf是int,4个字节,hibyte很有可能就是第四个字节,因此对应的byte就是第三个,所以

是1和3互换了,gjkds

Level3.0

QQ_1732167464896

看这个样子,换了两次,第一次0和4互换之后,1和3互换,

hvfsy

ysfvh

level3.1

jcaup

puacj

Level4.0

QQ_1732168685059

看起来是冒泡

jlvxy

level5.0

QQ_1732169761183

看这个样子是全都异或了0x50,那就他要的再xor一次0x50就抵消了

from pwn import *
def str_to_byte(input_string: str, key: int = 0x50) ->str:
input_bytes = input_string.encode('utf-8') # 将字符串转换为字节

# 对每个字节异或指定值
xor_result = bytes([b ^ key for b in input_bytes])

# 将异或结果转换回字符串
return xor_result.decode('utf-8', errors='ignore')
str="6?5%("
result=str_to_byte(str,0x50)
print(result)

首先encode将其转换为字节,之后异或,然后转换回来

level5.1

QQ_1732171545231

变成了0x46

4</‘,27h,’7

那应该就是4</‘7

QQ_1732171775083

直接在双引号里’,就已经是单引号了

Level6.0

QQ_1732171929291

其实就是每三个字节每三个字节的进行异或,并且还进行了逆转,还进行了排序,

QQ_1732174268726

reverse主要把整个字符串反过来,1然后sort进行了一次冒泡排序,所以,对应的expected应该和这个反过来,首先既然是冒泡排序过的,那这个expected本身就应该是一个已经排序过的,果然,QQ_1732174477205

所以只要对应的字节没有错就行,接下来,那这个reverse也没有任何的用哇,但是他每三个字节每三个字节的进行一次对应,如何确保最开始的和最终的那个三字节的对应是一样的呢,所以干脆就不管这些,直接全都逆转一遍,一定是没有错的

果然就是什么都不用管,只需要去对应字节就行,因为排序和那个都是失效的

from pwn import *
def process_hex_list(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 3 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0xDA)
elif i % 3 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x92)
elif i % 3 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xAD)

# 转换为字符串
return ''.join(chr(b) for b in processed_bytes)

# 示例十六进制列表
hex_list = [0xA0, 0xAC, 0xB4, 0xB5, 0xB7, 0xB7, 0xC1, 0xC1,
0xC6, 0xCB, 0xD9, 0xDC, 0xE3, 0xE5, 0xE7, 0xE8,
0xEB, 0xFB]

# 调用函数
result = process_hex_list(hex_list)
print(result)
p=process('/challenge/babyrev-level-6-0')
#ques=p.recvuntil(b'Ready to receive your license key!')
#print(ques.decode())
p.sendline(result.encode())
sleep(5) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

最后一个sleep很关键,不然的话调用recv的时候还没有接受完整,同时上面这个对list进行处理之后转换为str的方式可以借鉴,感觉很妙,通过append进行一个个的附加

Level6.1

xqivthjqkczwowzxtdi

0x13个字节,也就是19,

QQ_1732176954926

这不就啥也没干没

哦,坑在这里,buf是一个8字节的,而读取了19个字节,因此,后面的v9,v10,一共12个字节里有11个也属于这个范围里,注意中间夹杂里一次,v9的第三个字节,等于第12个字节和v10的最低位,也就是第17个字节进行了对换,所以,因为进行了一次反转,就是第三个和第八个进行了一次互换

QQ_1732189975804

QQ_1732190017931

Level7.0

QQ_1732190707860

异或变成了以组为单位,其次,输入了26个字符,经过了,异或,逆转,排序,逆转,逆转,那就是说最后实际还是逆转过一次的,把expected,进行一次逆转,之后直接进行异或就是最后的答案,不对,排序在逆转之后,并且之后有两次逆转,所以根本不需要逆转,

QQ_1732192115688

排序是排成一个前小后大的,’a’ \x80

解决了

因为当join的时候,一些不能显示的字符打印成了\x80,这个直接杯join进了字符串,字符串实际已经不能转变为原来的那些raw字节了,发生了变化,因此直接把原始字节传输过去就行了

from pwn import *
def process_hex_list(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 5 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x4d)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0xa5)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xa4)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x39)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 4: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x88)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')

# 转换为字符串
#r1 = ''.join(chr(b) for b in processed_bytes)
#print(r1)
#print(r1.encode('latin1'))
return bytes(processed_bytes)

# 示例十六进制列表
hex_list = [0x21, 0x25, 0x27, 0x27, 0x2B, 0x39, 0x43, 0x49, 0x52, 0x5B, 0x5B,
0xC0, 0xC2, 0xC2, 0xC6, 0xCD, 0xCE, 0xD2, 0xD5, 0xD6, 0xD7, 0xE0,
0xE2, 0xEB, 0xF8, 0xFE]

# 调用函数
result = process_hex_list(hex_list)
print(result)
p=process('/challenge/babyrev-level-7-0')
#ques=p.recvuntil(b'Ready to receive your license key!')
#print(ques.decode())
p.sendline(result)
sleep(5) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

Level7.1

QQ_1732607204789

QQ_1732607216265

看这个样子,读了0x1c,28个字节,上面存储的时候buf只是一个int64,8个字节,所以,这里v13的第五个字节相当于是第13个字节,然后,v15相当于是第25个字节,这两个进行了互换,之后他们进行了逆序,然后进行了按字节与的操作,之后再次逆序,然后冒泡排序,那么顺序同样还是没有啥用,直接找对应的字节

QQ_1732607310786

from pwn import *
def process_hex_list(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 5 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x2c)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x1)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xf3)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x40)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')
elif i % 5 == 4: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xee)
print('processed_bytes[%d]: ',i)
print(hex(processed_bytes[i]))
print('\n')

# 转换为字符串
r1 = ''.join(chr(b) for b in processed_bytes)
print(r1)
print(r1.encode('latin1'))
return bytes(processed_bytes)

# 示例十六进制列表

orign_hex_list = [0x29, 0x2B, 0x2C, 0x30, 0x36, 0x43, 0x48, 0x49, 0x4B, 0x59,
0x5D, 0x60, 0x68, 0x71, 0x72, 0x75, 0x75, 0x81, 0x81, 0x82,
0x83, 0x83, 0x84, 0x87, 0x87, 0x91, 0x97, 0x9B]

orign_hex_list.reverse()

result = process_hex_list(orign_hex_list)

end_hex_list = list(result)

end_hex_list.reverse()

end_hex_list[13], end_hex_list[24] = end_hex_list[24], end_hex_list[13]

end_result = bytes(end_hex_list)

print(result)
p=process('/challenge/babyrev-level-7-1')
#ques=p.recvuntil(b'Ready to receive your license key!')
#print(ques.decode())
p.sendline(end_result)
sleep(5) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

为了避免老眼昏花,直接把他的所有东西都逆转进行了一遍得到结果

Level8.0

读取0x25个字节,也就是37个字节,交换索引为2和30的两个字节,之后进行了排序,之后全都异或0x98,之后进行了倒序,之后,按照三个字节为一组进行异或,然后再逆序,之后6个字节为一组进行异或,当我全都翻过来,到了排序后,那就是一个已经排序完毕的QQ_1732620949503

这里的buf是一个24字节的数组

QQ_1732621517119

QQ_1732621670667

QQ_1732626247023

QQ_1732626339895

问题出在了

QQ_1732627430323

这里没有修改原本的元素,没有解决

from pwn import *
def process_list_6(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 6 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x65)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 6 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x5)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 6 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xdf)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 6 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x36)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 6 == 4: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x10)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 6 == 5: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xdf)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')

# 转换为字符串
#r1 = ''.join(chr(b) for b in processed_bytes)
#print(r1)
#print(r1.encode('latin1'))
return bytes(processed_bytes)


def process_list_3(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 3 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x86)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 3 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x90)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 3 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x6f)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')

# 转换为字符串
#r1 = ''.join(chr(b) for b in processed_bytes)
#print(r1)
#print(r1.encode('latin1'))
return bytes(processed_bytes)

# 示例十六进制列表

hex_list = [0xF3, 0x79, 0xB5, 0xA2, 0x6A, 0xB3, 0xF7, 0x7D, 0xB0, 0xA6,
0x69, 0xBC, 0xFE, 0x77, 0xBA, 0xAE, 0x7E, 0xA7, 0xE0, 0x68,
0xA4, 0xB2, 0x7A, 0xA2, 0xE7, 0x6E, 0xA1, 0xB7, 0x79, 0xAF,
0xEA, 0x63, 0xAE, 0xB8, 0x74, 0xAD, 0xE8]

#every 6 bytes xor
bytes_6=process_list_6(hex_list)

#transfer to lsit
list_6 = list(bytes_6)

print('-----------this is after xor 6bytes---------------')
print([hex(x) for x in list_6])

#reverse
list_6.reverse()

print('-----------this is after xor reverse---------------')
print([hex(x) for x in list_6])

#every 3 bytes xor
byte_3 = process_list_3(list_6)

#transfer to list
list_3 = list(byte_3)

print('-----------this is after xor 3bytes---------------')
print([hex(x) for x in list_3])

#reverse
list_3.reverse()

print('-----------this is after reverse---------------')
print([hex(x) for x in list_3])

#xor 0x98
xor_value = 0x98
list_xor = [x ^ xor_value for x in list_3]

print('-----------this is after xor 0x98---------------')
print([hex(x) for x in list_xor])


list_xor[2] , list_xor[30] = list_xor[30] , list_xor[2]


end_result = bytes(list_xor)

p=process('/challenge/babyrev-level-8-0')
#ques=p.recvuntil(b'Ready to receive your license key!')
#print(ques.decode())
p.sendline(end_result)
sleep(5) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

问题出在这个反了吗的

QQ_1732628281590

源程序反着来的吗的,解决

Level8.1

观察源程序,read0x25,之后进行逆序,7字节异或,之后第6个字节和第31个字节互换,然后,5字节异或,排序,4字节异或,4字节异或,

from pwn import *
def process_list_7(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 7 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x8f)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 7 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x7)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 7 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x6e)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 7 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0xa2)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 7 == 4: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xf8)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 7 == 5: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xb6)
elif i % 7 == 6: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xbc)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')

# 转换为字符串
#r1 = ''.join(chr(b) for b in processed_bytes)
#print(r1)
#print(r1.encode('latin1'))
return bytes(processed_bytes)

def process_list_5(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 5 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x3d)
elif i % 5 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x77)
elif i % 5 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xc1)
elif i % 5 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x64)
elif i % 5 == 4: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x66)
return bytes(processed_bytes)

def process_list_4_1(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 4 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x49)
elif i % 4 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x4e)
elif i % 4 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x47)
elif i % 4 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x78)
return bytes(processed_bytes)

def process_list_4_2(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 4 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x93)
elif i % 4 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0xcd)
elif i % 4 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0xcd)
elif i % 4 == 3: # 第二个节
processed_bytes.append(hex_list[i] ^ 0x39)
return bytes(processed_bytes)

def process_list_3(hex_list):
# 用于存储处理后的字节
processed_bytes = []

# 遍历列表,按每三个字节处理
for i in range(len(hex_list)):
if i % 3 == 0: # 第一个字节
processed_bytes.append(hex_list[i] ^ 0x6f)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 3 == 1: # 第二个字节
processed_bytes.append(hex_list[i] ^ 0x90)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')
elif i % 3 == 2: # 第三个字节
processed_bytes.append(hex_list[i] ^ 0x86)
# print('processed_bytes[%d]: ',i)
# print(hex(processed_bytes[i]))
# print('\n')

# 转换为字符串
#r1 = ''.join(chr(b) for b in processed_bytes)
#print(r1)
#print(r1.encode('latin1'))
return bytes(processed_bytes)

# 示例十六进制列表

hex_list = [0xDA, 0x80, 0x8E, 0x45, 0xCB, 0x97, 0x93, 0x65, 0xF3, 0xD0,
0xDC, 0x21, 0xB8, 0xED, 0x1B, 0xD9, 0x44, 0x21, 0x22, 0xE9,
0x70, 0x2F, 0x38, 0xF3, 0x68, 0x36, 0x3D, 0xF8, 0x19, 0x5B,
0x56, 0xA5, 0x36, 0x70, 0x7C, 0xBA, 0x21]

#every 4_2 bytes xor
bytes_4_2=process_list_4_2(hex_list)

#transfer to lsit
list_4_2 = list(bytes_4_2)

#every 4_1 bytes xor
bytes_4_1=process_list_4_1(list_4_2)

#transfer to lsit
list_4_1 = list(bytes_4_1)

#every 5 bytes xor
bytes_5=process_list_5(list_4_1)

#transfer to lsit
list_5 = list(bytes_5)

#swap
list_5[5] , list_5[30] = list_5[30] , list_5[5]

#every 5 bytes xor
bytes_7=process_list_7(list_5)

#transfer to lsit
list_7 = list(bytes_7)

list_7.reverse()

end_result = bytes(list_7)

p=process('/challenge/babyrev-level-8-1')
#ques=p.recvuntil(b'Ready to receive your license key!')
#print(ques.decode())
p.sendline(end_result)
sleep(2) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

Level9.0

这题没太看懂,先看看让干啥吧

QQ_1732630490453

首先看这个,他read0x1a,也就是26个字节,但是buf只有8字节,buf,v16,v17和v18的低2字节都在read里,

QQ_1732630593382

看挑战程序的话,就是

QQ_1732630685400

更改了这几个位置的字节,那就是v5是填写的偏移,v4是填写的目标字节的值,v5是16位的数,之后,读取了之后,他通过md5进行了哈希,因此不可逆,之后,帖子说这个v12是程序加载基址,那么,原来如此,这里可以任意patch,所以说,可以直接将最后win的那个判断改成不相等才跳过,那这样的话就直接win了

QQ_1732631837626

jnz是0x75,而jz是0x74,他是

QQ_1732631891483

在不等于这里,他是不等于才跳走,所以改成等于条件不成立就不跳走了

Level9.1

QQ_1732632303479

226b处

Level10.0

只允许一个字节了,

QQ_1732632591400

level10.1

0x23c1

level11.0

QQ_1732632841665

说是之后会检查,然后让修改两次,所以我才回头还要修改回来

是对s1和s2的内容进行了一个相同性检查,所以,正常的修改,触发了完整性检查,为什么呢, 懂了,还需要把那个检查完整性的改了,

QQ_1732633555585

222b

拿下

Level11.1

2559,247b,这里有一个坑,就是他是一个拓展过的jnz,是0f 85,后面跟着32位的偏移量,而之前的是跟着8位的,所以这里应该是247c,变成0x84

Level12.0

image-20241202191234867

主程序没有看到什么特殊的,之后运转了下面那个函数,传入了v3,也就是256个字节的0,

image-20241202191402026

在这个函数里面,首先读入了8个字节,放在了a1[60]的位置,然后,赋值了一系列的,从a1[92]到a1[99]的8个字节,之后v1被赋值为memcmp的返回值,比较的就是这两个8字节,如果v1是非0,那不对,如果说v1为0才会打印,那就是要这两个位置都相同呗,

image-20241202195333308

要发的字节就是这些了,那么发送的方式有很多种,第一种是通过struct中的pack函数将数字转换为二进制数据并且按照指定格式打包

a1 = [47, 66, 117, -18, -4, 89, -54, 53]

# 使用 struct.pack 将每个整数打包为字节
# 'b' 格式符表示 signed char(-128 到 127),这样负数会被转换为 2 的补码
data = struct.pack('8b', *a1)

第二种是通过array模块的array创建一个字节数组

# 要发送的数值
a1 = [47, 66, 117, -18, -4, 89, -54, 53]

# 使用 array 模块创建一个包含字节的数组
# 'b' 表示有符号字节(signed char)
byte_array = array.array('b', a1)

# 发送字节数据
p.send(byte_array.tobytes())

第三个是通过手动转换,因为一个字节的话,负数用的是他的补码形式

a1 = [47, 66, 117, -18, -4, 89, -54, 53]

# 手动将每个整数转换为字节(负数转补码)
data = bytearray(((x + 256) % 256) for x in a1)

注意,bytes和b’’不一样,bytes是一个内建类型,表示不可变的字节序列,而b‘’是通过在字符串前面添加b表示这是一个字节序列

from pwn import *
p=process('/challenge/babyrev-level-12-0')
a1 = [47, 66, 117, -18, -4, 89, -54, 53]

# 将每个数值转换为字节,并打包成二进制数据
# 使用 'b' 格式符,表示一个字节(signed char),范围是 -128 到 127
# 如果需要处理负数,struct 会将其转换为 2 的补码表示
data = bytes([((x + 256) % 256) for x in a1])

或者
data = bytes([0x2f,0x42,0x75,0xee,0xfc,0x59,0xca,0x35])
p.send(data)
sleep(2) # 等待程序输出完整

out=p.recv(4096)
print(out.decode())

Level12.1

image-20241202201331027

Level13.0

让我们深入研究逆向工程混淆代码!此挑战使用基于 VM 的混淆:逆向工程自定义模拟器和架构以了解如何获取标志!如果你很聪明的话,你就不需要逆向太多的 VM 代码。

image-20241202201840855

这里来看,他读取了8个字节到a1+55这个位置,之后

image-20241202202057907

目的仍然是a1+87的8个字节和a1+55的8个字节相同,这个函数传进来了一个256字节的起始位置,

三个参数,第二个参数来看,1是b,64是c,2是a,

首先看看这个样子,stm *0x87就是0xcb,stm *0x88为0x21,也就是说以b为基底,然后c为一个1,每次加一下他,那8个字节就是,0xcb,0x21,0xb3,0xb0,0xd4,0xae,0x9c,0x1

Level13.1

image-20241202205036562

这8个字节应该说,

image-20241202205331646

传入的仍然是256个字节的0,之后,

image-20241202205405061

image-20241202205613645

sub1533又跳到了sub1415,然后这里是,a3为位置,a2是对应的变量代表的值

那也就是说16这等于88,然后4等于1,这一个应该是那个索引,之后2等于222,

image-20241202205910190

0x1687这个函数,首先,传入了a1,16,2,这个16应该就是下标索引,2的话就是2里面的值,也就是说a1[88]=222,以此类推8个字节分别是222,86,175,83,92,217,104,188

Level14.0

image-20241202210541161

imm的作用已经知道了,现在看一下sys的作用,第一个sys传进去了a1,32,8,能看出来32就是最开始的b,而8是a,那就是说传入了b和a,

image-20241202210713305

image-20241202211326046

在sys里面,有一个选择结构,是根据a2进行选择的,32是0x20,所以是一个读取操作,a1[258]就到了main函数的v4的第三个字节,但是整个v4都是0啊,没太看懂,

image-20241202215534330

Write_register(a1,8,v9)

但是通过returnimage-20241202215055964

能看到,如果a3不为0的话,这里a3就是8,那么就会执行这个,image-20241202215212631

return了a1[256]给v11,v12是

image-20241202215400452

应该就是一个字符,这里也就是a,最后打印出来的就是image-20241202215439457

对应的0x4的值是a1[256]

回到比较结果

image-20241202221430148

比较了四个字节,比较的是137和105开始的四个字节

137开始的四个字节是0x67,0x30,0x9d,0xba

看了半天,结果直接输入就行了,我服了,那我在分析nm呢,那从结论返回推过程,看一下怎么输入到a1+105的,首先a=0,b=105,c=4,

之后sys (0x20,a)

原来如此,我明白了,前面imm赋值过程中其实就改变了那几个数,这下就明了了,

image-20241202224449181

首先第一个imm,

image-20241202224504315

image-20241202224516811

32 ,105,那就是将a1[257]变成了105

1 ,4 就是将a1[258]变成了4

8,0 就是把a1[256]变成了0,接下来再进入到之前的流程

image-20241202224638561

sys的read过程中,v5=4,然后256-105>4,所以if并不触发,之后就是v6=sys_read,相当于read(0,&a[105],4)读取了4个字节,他妈的,分析完毕!结束!win!

level14.1

image-20241202225936574

image-20241202230240268

image-20241202230428697

← Back to Home